datafield-1.x-dev/src/Plugin/Field/FieldWidget/Base.php
src/Plugin/Field/FieldWidget/Base.php
<?php
namespace Drupal\datafield\Plugin\Field\FieldWidget;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\datafield\Plugin\DataFieldTypePluginManager;
use Drupal\datafield\Plugin\DataFieldWidgetPluginManager;
use Drupal\datafield\Plugin\Field\FieldType\DataFieldItem as FieldItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Base class for data field formatters.
*/
abstract class Base extends WidgetBase {
/**
* Constructs a WidgetBase object.
*
* @param string $plugin_id
* The plugin_id for the widget.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the widget is associated.
* @param array $settings
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $keyValue
* The key value service.
* @param \Drupal\datafield\Plugin\DataFieldTypePluginManager $pluginType
* The plugin type manager.
* @param \Drupal\datafield\Plugin\DataFieldWidgetPluginManager $pluginWidget
* The plugin widget service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, protected KeyValueFactoryInterface $keyValue, protected DataFieldTypePluginManager $pluginType, protected DataFieldWidgetPluginManager $pluginWidget, protected CacheBackendInterface $cache) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($plugin_id, $plugin_definition,
$configuration['field_definition'], $configuration['settings'],
$configuration['third_party_settings'],
$container->get('keyvalue'),
$container->get('plugin.manager.data_field.type'),
$container->get('plugin.manager.data_field.widget'),
$container->get('cache.default'),
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
$settings = [
'inline' => FALSE,
'widget_settings' => [],
];
return $settings + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$field_settings = $this->getFieldSetting('field_settings');
$settings = $this->getSettings();
$widget_settings = $settings['widget_settings'];
$subfields = array_keys($columns = $this->getFieldSetting("columns"));
if (!empty($setting = $settings['widget_settings'])) {
uasort($setting, [
'Drupal\Component\Utility\SortArray',
'sortByWeightElement',
]);
$subfields = array_keys($setting);
}
$types = FieldItem::subfieldTypes();
$field_name = $this->fieldDefinition->getName();
$element['inline'] = [
'#type' => 'checkbox',
'#title' => $this->t('Display as inline element'),
'#default_value' => $settings['inline'] ?? FALSE,
];
$widgetTypes = $this->pluginType->getDefinitions();
$option_widget = $default_widget = [];
foreach ($widgetTypes as $widget_type) {
$default_widget[$widget_type['id']] = $widget_type['default_widget'];
}
$pluginWidget = $this->pluginWidget;
$widget_types = $pluginWidget->getDefinitions();
foreach ($widget_types as $widget_type) {
foreach ($widget_type['field_types'] as $type) {
$option_widget[$type][$widget_type['id']] = $widget_type['label'];
}
}
foreach ($subfields as $subfield) {
if (empty($columns[$subfield])) {
continue;
}
$item = $columns[$subfield];
$type = $item['type'];
$optWidget = $option_widget[$type] ?? [];
if (in_array($type, ['date', 'datetime_iso8601']) && count($widget_settings[$subfield]) == 1) {
$dateType = $item['datetime_type'];
$widget_settings[$subfield]["type"] = $dateType;
if (in_array($dateType, ['time', 'week', 'month', 'year'])) {
$optWidget = [$dateType => $option_widget[$type][$dateType]];
}
}
$element['widget_settings'][$subfield] = [
'#type' => 'details',
'#title' => ($field_settings[$subfield]['label'] ?? $subfield) . ' - ' . $types[$type],
'#open' => FALSE,
];
$form['#storage_settings'] = $item;
$form['#field_settings'] = $field_settings[$subfield] ?? [];
$form['#settings'] = $widget_settings[$subfield] ?? [];
$form['#machine_name'] = $subfield;
$form['#field_name'] = $field_name;
$widget_type = $widget_settings[$subfield]["type"] ?? $default_widget[$type];
$plugin = $pluginWidget->createInstance($widget_type);
$widgetSettings = [];
if (method_exists($plugin, 'settingsForm')) {
$widgetSettings = $plugin->settingsForm($form, $form_state);
}
if (empty($widget_settings)) {
$widget_settings = [];
}
$options = [
'block' => $this->t('Above'),
'hidden' => $this->t('Hidden'),
];
if ($settings['widget_settings'][$subfield]['type'] != 'datetime') {
$options['inline'] = $this->t('Inline');
$options['invisible'] = $this->t('Invisible');
}
$title = $item['name'] . ' - ' . $types[$type];
$element['widget_settings'][$subfield] = [
'#title' => $title,
'#type' => 'details',
'type' => [
'#type' => 'select',
'#title' => $this->t('Widget type'),
'#default_value' => $widget_type,
'#required' => TRUE,
'#empty_option' => $this->t('- Select -'),
'#options' => $optWidget,
'#description' => $this->t('Save and back again to get options.'),
],
'label_display' => [
'#type' => 'select',
'#title' => $this->t('Label'),
'#default_value' => $widget_settings[$subfield]['label_display'] ?? '',
'#options' => $options,
'#empty_option' => $this->t('- Select -'),
'#access' => static::isLabelSupported($item['type']),
],
'field_display' => [
'#type' => 'checkbox',
'#title' => $this->t('Show field'),
'#default_value' => $widget_settings[$subfield]['field_display'] ?? TRUE,
],
'weight' => [
'#type' => 'weight',
'#title' => $this->t('Weight for fields'),
// '#title_display' => 'invisible',
'#default_value' => $widget_settings[$subfield]['weight'] ?? 0,
'#weight' => 20,
],
] + $widgetSettings;
}
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary(): array {
$settings = $this->getSettings();
$field_settings = $this->getFieldSettings();
$widgetTypes = $this->pluginType->getDefinitions();
$widget_settings = $settings['widget_settings'];
$default_widget = [];
foreach ($widgetTypes as $widget_type) {
$default_widget[$widget_type['id']] = $widget_type['default_widget'];
}
$pluginWidget = $this->pluginWidget;
$summary = [];
if (!empty($settings['inline'])) {
$summary[] = $this->t('Display as inline element');
}
if (!empty($settings['field_reference'])) {
$summary[] = $this->t('Field reference search: %field', ['%field' => $settings['field_reference']]);
}
foreach ($field_settings["columns"] as $subfield => $item) {
$subfield_type = $item['type'];
$summary[] = new FormattableMarkup(
'<b>@subfield - @subfield_type</b>',
[
'@subfield' => $field_settings["field_settings"][$subfield]['label'] ?? $subfield,
'@subfield_type' => strtolower($subfield_type),
]
);
if (!empty($settings['widget_settings'][$subfield]['type'])) {
$summary[] = $this->t('Widget: @type', ['@type' => $settings['widget_settings'][$subfield]['type']]);
}
if (!empty($settings['widget_settings'][$subfield]['label_display']) && static::isLabelSupported($settings['widget_settings'][$subfield]['type'])) {
$summary[] = $this->t('Label display: @label', ['@label' => $settings['widget_settings'][$subfield]['label_display']]);
}
$type = $item['type'];
$widget_type = $widget_settings[$subfield]["type"] ?? $default_widget[$type];
$plugin = $pluginWidget->createInstance($widget_type);
if (method_exists($plugin, 'settingsSummary')) {
$settingsSummary = $widget_settings[$subfield];
if (!empty($field_settings["field_settings"][$subfield])) {
$settingsSummary += $field_settings["field_settings"][$subfield] ?? [];
}
$summary = [...$summary, ...$plugin->settingsSummary($settingsSummary)];
}
}
return $summary;
}
/**
* Transfer value foreach widget.
*
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state): array {
$storage_settings = $this->getFieldSettings();
$settings = $this->getSetting('widget_settings');
$fieldName = $this->fieldDefinition->getName();
$path = array_merge($form['#parents'], [$fieldName]);
$key_exists = NULL;
$userInputs = NestedArray::getValue($form_state->getUserInput(), $path, $key_exists);
if (!empty($userInputs) && !empty($values) && is_array($userInputs) && count($userInputs) != count($values)) {
$values = $userInputs;
}
$typeDefinitions = $this->pluginType->getDefinitions();
foreach ($values as $delta => &$value) {
$checkEmpty = [];
if (!empty($value[0])) {
$value = $value[0];
}
foreach ($storage_settings['columns'] as $subfield => $item) {
$checkEmpty[$subfield] = $value[$subfield] ?? NULL;
if (!empty($value[$subfield]) && is_array($value[$subfield]) && !empty($value[$subfield]['value'])) {
$value[$subfield] = $value[$subfield]['value'];
}
if (isset($values[$delta][$subfield]) && !is_numeric($values[$delta][$subfield]) && empty($value[$subfield])) {
$values[$delta][$subfield] = NULL;
}
if (!empty($value[$delta][$subfield])) {
if ($item["type"] != 'datetime_iso8601') {
$value[$subfield] = $value[$delta][$subfield];
}
unset($value[$delta]);
}
$widget_type = $settings[$subfield]['type'] ?? $typeDefinitions[$item["type"]]["default_widget"];
if ($widget_type) {
$plugin = $this->pluginWidget->createInstance($widget_type);
if (method_exists($plugin, 'massageFormValues') && isset($value[$subfield])) {
$widget_settings = [];
if (method_exists($plugin, 'defaultSettings')) {
$widget_settings = $plugin->defaultSettings();
}
$form['#machine_name'] = $subfield;
$form['#storage_settings'] = $item;
$form['#field_settings'] = $storage_settings["field_settings"][$subfield];
$form['#widget_settings'] = $settings[$subfield] ?? $widget_settings;
$form['#value_origin'] = $userInputs;
$form['#field_name'] = $fieldName;
$form['#delta'] = $delta;
$value[$subfield] = $plugin->massageFormValues($value[$subfield], $form, $form_state);
}
}
}
if (count(array_filter($checkEmpty, function ($value) {
return $value !== NULL && $value !== FALSE && $value !== "";
})) === 0) {
unset($values[$delta]);
}
}
NestedArray::setValue($form_state->getValues(), $path, $values);
return array_values($values);
}
/**
* {@inheritDoc}
*/
public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_name = $element['#field_name'];
$parents = $element['#field_parents'];
// Increment the items count.
$field_state = static::getWidgetState($parents, $field_name, $form_state);
$userInput = $form_state->getUserInput()[$field_name] ?? [];
if (is_array($userInput) && count($userInput) > 2 && $field_state['items_count'] < count($userInput) - 1) {
$field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
$field_state['items_count'] = count($userInput) - 2;
static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
}
parent::addMoreSubmit($form, $form_state);
}
/**
* Determines whether widget can render subfield label.
*/
public static function isLabelSupported(?string $widget_type = NULL): bool {
return $widget_type != 'checkbox';
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
[$delta, $subfield] = explode('.', $violation->getPropertyPath());
$element['#subfield'] = $subfield;
$element['#delta'] = $delta;
if (isset($element[$subfield]["#field_storage"]["datetime_type"]) && $element[$subfield]["#field_storage"]["datetime_type"] == 'year') {
return FALSE;
}
if ($element[$subfield]["#field_storage"]["type"] == 'datetime_iso8601' && $element[$subfield]["#field_storage"]["datetime_type"] == 'timestamp') {
return FALSE;
}
if (!empty($element[$delta][$subfield])) {
$settings = $element[$delta][$subfield]['#widget_settings'];
$widget_type = $settings["type"];
$plugin = $this->pluginWidget->createInstance($widget_type);
if (method_exists($plugin, 'errorElement')) {
return $plugin->errorElement($element, $violation, $form, $form_state);
}
}
if (!empty($element[$subfield])) {
$settings = $element[$subfield]['#widget_settings'];
$widget_type = $settings["type"];
$plugin = $this->pluginWidget->createInstance($widget_type);
if (method_exists($plugin, 'errorElement')) {
return $plugin->errorElement($element, $violation, $form, $form_state);
}
}
return isset($violation->arrayPropertyPath[$delta]) ? $element[$violation->arrayPropertyPath[$delta]] : $element;
}
/**
* {@inheritdoc}
*/
protected function getFieldSettings(): array {
$field_settings = parent::getFieldSettings();
foreach ($field_settings["columns"] as $subfield => $item) {
if (!empty($field_settings["field_settings"][$subfield]['list']) && !FieldItem::isListAllowed($item['type'])) {
$field_settings["field_settings"][$subfield]['list'] = FALSE;
}
}
return $field_settings;
}
/**
* {@inheritdoc}
*/
public function getSettings(): array {
// Unique cache ID for these settings.
$cid = 'datafieldsettingsdefault';
$default_widget = [];
// Attempt to get cached settings.
$cache = $this->cache->get($cid);
if ($cache) {
$default_widget = $cache->data;
}
else {
$widget_types = $this->pluginType->getDefinitions();
foreach ($widget_types as $widget_type) {
$default_widget[$widget_type['id']] = $widget_type['default_widget'];
}
// Save the newly computed settings to cache.
$this->cache->set($cid, $default_widget);
}
$settings = parent::getSettings();
$field_settings = $this->getFieldSettings();
foreach ($field_settings['columns'] as $subfield => $item) {
if (empty($settings['widget_settings'][$subfield]['type']) && !empty($default_widget[$item["type"]])) {
$settings['widget_settings'][$subfield]['type'] = $default_widget[$item["type"]];
}
if ($default_widget[$item["type"]] == 'datetime' && !empty($field_settings['columns'][$subfield]['datetime_type'])) {
$settings['widget_settings'][$subfield]['type'] = $field_settings['columns'][$subfield]['datetime_type'];
}
}
return $settings;
}
/**
* {@inheritdoc}
*/
public function getFormElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
$field_settings = $this->getFieldSetting('field_settings');
$settings = $this->getSettings();
$subfields = array_keys($storage = $this->getFieldSetting("columns"));
$fieldDef = $items->getFieldDefinition();
$field = $items[0]->getFieldDefinition();
$field_widget_default = $field->getDefaultValueLiteral();
if (!empty($setting = $settings['widget_settings'])) {
uasort($setting, [
'Drupal\Component\Utility\SortArray',
'sortByWeightElement',
]);
$subfields = array_keys($setting);
}
$widget = [];
$entityDefault = [
'entity_reference_type' => $fieldDef->getTargetEntityTypeId() ?? 'node',
'target_bundles' => $fieldDef->getTargetBundle() ?? 'page',
'label' => $fieldDef->label(),
];
$fieldName = $this->fieldDefinition->getName();
$userInput = $form_state->getUserInput();
foreach ($subfields as $subfield) {
if (empty($storage[$subfield])) {
continue;
}
$widget_settings = $settings["widget_settings"][$subfield];
$title = $field_settings[$subfield]['label'] ?? ucfirst($subfield);
$field_default = !empty($field_widget_default[$delta]) ? $field_widget_default[$delta][$subfield] : '';
$default_value = $items[$delta]->{$subfield} ?? $field_default;
if (is_array($default_value) && array_key_exists('url', $default_value)) {
$default_value = $default_value['url'];
}
if (empty($default_value) && !empty($userInput[$fieldName][$delta][$subfield])) {
$default_value = $userInput[$fieldName][$delta][$subfield][$subfield] ?? $userInput[$fieldName][$delta][$subfield];
}
$storage_type = $storage[$subfield]['type'];
$widget_type = $widget_settings["type"] ?? $storage_type;
$checkFormDefaultValue = !empty($form["#parents"][0]) && $form["#parents"][0] == 'default_value_input';
if ($storage_type == 'file' && $checkFormDefaultValue) {
$widget_type = 'number';
}
$widget[$delta][$subfield] = [
'#id' => Html::getUniqueId($items->getName() . '-' . $delta),
'#title' => $title,
'#title_display' => 'invisible',
'#type' => $widget_type,
'#machine_name' => $subfield,
'#default_value' => $default_value,
'#field_name' => $fieldName,
'#field_storage' => $storage[$subfield],
'#field_settings' => $field_settings[$subfield] ?? [],
'#field_parents' => $element['#field_parents'] ?? [],
'#widget_settings' => $widget_settings,
'#entity_type' => $entityDefault['entity_reference_type'],
'#bundle' => $entityDefault['target_bundles'],
'#delta' => $delta,
'#wrapper_attributes' => ['class' => [Html::getId('data_field-subfield-form-item')]],
];
if (!$checkFormDefaultValue) {
$widget[$delta][$subfield]['#required'] = $field_settings[$subfield]['required'] ?? 0;
$widget[$delta][$subfield]['#default_value_input'] = TRUE;
}
if ($storage_type == 'entity_reference') {
$plugin = $this->pluginType->createInstance($storage_type);
if (!isset($field_settings[$subfield])) {
$field_settings[$subfield] = $entityDefault;
}
$plugin->setWidgetForm($widget[$delta][$subfield], $field_settings[$subfield], $items->getEntity());
}
$plugin = $this->pluginWidget->createInstance($widget_type);
if ($plugin) {
$settingsWidget = $storage[$subfield] + $widget_settings;
$plugin->getFormElement($widget[$delta][$subfield], $items[$delta], $settingsWidget);
}
}
return $widget;
}
/**
* {@inheritDoc}
*/
public static function itemAjax(array $form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
// Go one level up in the form, to the widgets' container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
$element['#prefix'] = '<div class="ajax-new-content">' . $element['#prefix'] ?? '';
if (isset($element['#suffix'])) {
$element['#suffix'] .= '</div>';
}
return $element;
}
}
