ztools-1.0.x-dev/src/Plugin/Field/FieldWidget/TextfieldListWidget.php
src/Plugin/Field/FieldWidget/TextfieldListWidget.php
<?php declare(strict_types = 1);
namespace Drupal\ztools\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\EmailValidatorInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\InternalViolation;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Defines the 'ztools_textfield_list' field widget.
*
* @FieldWidget(
* id = "ztools_textfield_list",
* label = @Translation("Textfield List"),
* field_types = {"string", "email"},
* )
*/
final class TextfieldListWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
$setting = [
'delimiter' => ',',
'trim' => TRUE,
];
return $setting + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$element['delimiter'] = [
'#type' => 'textfield',
'#length' => 5,
'#title' => $this->t('Delimiter'),
'#default_value' => $this->getSetting('delimiter'),
];
$element['trim'] = [
'#type' => 'checkbox',
'#title' => $this->t('Trim values'),
'#default_value' => $this->getSetting('trim'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary(): array {
return [
$this->t('Delimiter: @delimiter', ['@delimiter' => $this->getSetting('delimiter')]),
$this->t('Trim: @trim', ['@trim' => $this->getSetting('trim') ? 'Yes' : 'No']),
];
}
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
$parents = $form['#parents'];
switch ($cardinality) {
case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
$field_state = static::getWidgetState($parents, $field_name, $form_state);
$max = $field_state['items_count'];
break;
default:
$max = $cardinality - 1;
break;
}
$title = $this->fieldDefinition->getLabel();
$description = $this->getFilteredDescription();
$element = [
'#title' => $title,
'#type' => 'textfield',
'#required' => $this->fieldDefinition->isRequired(),
'#title_display' => 'before',
'#description' => $description,
];
$values = [];
for ($delta = 0; $delta <= $max; $delta++) {
$values[$delta] = $items->offsetExists($delta) ? $items[$delta]->value : NULL;
}
$element['#default_value'] = implode($this->getSetting('delimiter'), array_filter($values));
return $element;
}
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
return $element;
}
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
// Extract the values from $form_state->getValues().
$path = array_merge($form['#parents'], [$field_name]);
$key_exists = NULL;
$value = NestedArray::getValue($form_state->getValues(), $path, $key_exists);
if ($key_exists) {
$values = $this->explodeValues($value, $form, $form_state);
// Assign the values and remove the empty ones.
$items->setValue($values);
$items->filterEmptyItems();
// Put delta mapping in $form_state, so that flagErrors() can use it.
$field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
foreach ($items as $delta => $item) {
$field_state['original_deltas'][$delta] = $item->_original_delta ?? $delta;
unset($item->_original_delta, $item->_weight, $item->_actions);
}
static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
}
}
public function explodeValues(string $value, array $form, FormStateInterface $form_state) {
$values = explode($this->getSetting('delimiter'), $value);
if ($this->getSetting('trim')) {
$values = array_map('trim', $values);
}
return $values;
}
/**
* {@inheritdoc}
*/
public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
$field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
if ($violations->count()) {
// Locate the correct element in the form.
$element = NestedArray::getValue($form_state->getCompleteForm(), $field_state['array_parents']);
// Do not report entity-level validation errors if Form API errors have
// already been reported for the field.
// @todo Field validation should not be run on fields with FAPI errors to
// begin with. See https://www.drupal.org/node/2070429.
$element_path = implode('][', $element['#parents']);
if ($reported_errors = $form_state->getErrors()) {
foreach (array_keys($reported_errors) as $error_path) {
if (str_starts_with($error_path, $element_path)) {
return;
}
}
}
// Only set errors if the element is visible.
if (Element::isVisibleElement($element)) {
$handles_multiple = $this->handlesMultipleValues();
$violations_by_delta = $item_list_violations = [];
foreach ($violations as $violation) {
$violation = new InternalViolation($violation);
// Separate violations by delta.
$property_path = explode('.', $violation->getPropertyPath());
$delta = array_shift($property_path);
if (is_numeric($delta)) {
$violations_by_delta[$delta][] = $violation;
}
// Violations at the ItemList level are not associated to any delta.
else {
$item_list_violations[] = $violation;
}
// @todo Remove BC layer https://www.drupal.org/i/3307859 on PHP 8.2.
$violation->arrayPropertyPath = $property_path;
}
/** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $delta_violations */
foreach ($violations_by_delta as $delta => $delta_violations) {
foreach ($delta_violations as $violation) {
$error_element = $element;
if ($error_element !== FALSE) {
$form_state->setError($error_element, $violation->getMessage());
}
}
}
/** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $item_list_violations */
// Pass violations to the main element without going through
// errorElement() if the violations are at the ItemList level.
foreach ($item_list_violations as $violation) {
$form_state->setError($element, $violation->getMessage());
}
}
}
}
}
