reviewer-1.2.x-dev/modules/reviewer_test_kit/src/Plugin/reviewer/Task/Entity/Display/FieldPluginsTaskBase.php
modules/reviewer_test_kit/src/Plugin/reviewer/Task/Entity/Display/FieldPluginsTaskBase.php
<?php
declare(strict_types=1);
namespace Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\reviewer\Reviewer\Result\ResultInterface;
use Drupal\reviewer\Reviewer\Status\Status;
use Drupal\reviewer\Reviewer\Task\FixableInterface;
/**
* Internal task for checking if fields on entity displays use allowed plugins.
*
* @internal
* This class is internal and the methods within may change at any time. Use
* \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FieldWidgetsTaskBase
* or
* \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\FieldFormatterTaskBase
* to check if fields use the correct plugins on specific entity displays
* instead.
*
* @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\Form\FieldWidgetsTaskBase
* @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\View\FieldFormatterTaskBase
*/
abstract class FieldPluginsTaskBase extends DisplayFieldsTaskBase implements FixableInterface {
/**
* The types of fields to check.
*
* The field types are the field definition types defined in the field
* storage, not the field widget or formatter plugin types. This is because
* the widget and formatter plugin types are what are checked by this task. To
* check all fields, set the value to ['*'].
*
* Entity reference fields often do not convey enough information for tasks to
* accurately check the field based on type alone, because the correct
* behavior is often dependent on the type of handler and bundle of the entity
* referenced. In this case, you may specify the handler as the array key. You
* may also specify handler bundles using a pipe followed by a comma-separated
* list.
*
* For example, to check all entity reference media fields use
* ['default:media' => 'entity_reference'], and to check only image and icon
* media fields use ['default:media|icon,image' => 'entity_reference'].
*
* Do not access this directly in tasks, but use the fieldsToCheck() method to
* return a list of fields from the $fieldNames and $fieldTypes properties
* evaluated for a specific entity display.
*
* A \LogicException will be thrown if this property and $fieldNames are both
* empty.
*
* @var array<int|string, string>
*
* @see \Drupal\Core\Field\FieldDefinitionInterface
* @see \Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display\DisplayTaskBase
*/
protected array $fieldTypes = [];
/**
* A list of field widget or formatter plugins allowed for use on the field.
*
* @var string[]
*/
protected array $allowedPlugins = [];
/**
* {@inheritdoc}
*/
protected function checkValidProperties(): void {
parent::checkValidProperties();
if (\count($this->allowedPlugins) === 0) {
throw new \LogicException(sprintf('%s::allowedPlugins cannot be empty.', static::class));
}
}
/**
* {@inheritdoc}
*/
public function check(): ResultInterface {
$this->checkValidProperties();
$results = $this->createCollection();
foreach ($this->displays() as $display) {
$components = $display->getComponents();
foreach ($this->fieldsToCheck($display) as $field) {
$results->add($this->createCheckResult(
\in_array($components[$field]['type'] ?? '', $this->allowedPlugins, TRUE),
sprintf('Field "%s" uses an appropriate plugin.', $field),
sprintf('Field "%s" plugin must be one of: "%s".', $field, implode('", "', $this->allowedPlugins)),
"{$display->getMode()}.$field",
));
}
}
return $results;
}
/**
* {@inheritdoc}
*/
public function fix(): ResultInterface {
if (\count($this->allowedPlugins) > 1) {
return $this->createFixResult(
Status::Fail,
'',
sprintf('Cannot set field plugins on "%s" because there is more than one allowed plugin ("%s").', $this->entityTypeFullId(), implode('", "', $this->allowedPlugins)),
);
}
$allowed = reset($this->allowedPlugins);
foreach ($this->displays() as $display) {
$components = $display->getComponents();
foreach ($this->fieldsToCheck($display) as $field) {
if (($components[$field]['type'] ?? '') !== $allowed) {
$components[$field]['type'] = $allowed;
}
$this->fixSaveCallback($display, $field, $components[$field]);
}
$display->save();
}
return $this->createFixResult(
$this->check()->getStatus(),
sprintf('Fixed field plugins for "%s".', $this->entityTypeFullId()),
sprintf('Unable to fix field plugins for "%s".', $this->entityTypeFullId()),
);
}
/**
* Get all field names matching the field type and optional handler.
*
* @return string[]
*/
protected function fieldNamesFromTypes(EntityDisplayInterface $display): array {
// We can short-circuit here if types are empty or all types are matched.
if (!$this->fieldTypes) {
return [];
}
if ($this->fieldTypes === ['*']) {
return array_keys($this->fieldsCallback($display));
}
$fields_to_check = $this->fieldsCallback($display);
// Order the types so those without handlers come first. This helps later
// to not load field definitions to check handlers unless it's necessary.
$field_types = $this->fieldTypes;
ksort($field_types);
foreach ($fields_to_check as $field_name => $configuration) {
$field_definition = NULL;
foreach ($field_types as $handler => $field_type) {
// Get the field definition it doesn't exist yet.
$field_definition ??= $this->fieldDefinition($field_name);
// Fields without a field definition are not valid matches.
if (!$field_definition) {
unset($fields_to_check[$handler]);
break;
}
// This type does not match, so keep looking through the types.
if ($field_definition->getType() !== $field_type) {
continue;
}
// There is no handler, so the type match is sufficient, because the
// types have been ordered so that ones with handlers are checked first.
if (\is_int($handler)) {
continue;
}
// Retrieve the handler and bundles for the field.
[$handler, $bundles] = array_pad(explode('|', $handler), 2, NULL);
// The handler does not match, so keep looking through the types.
if ($field_definition->getSetting('handler') !== $handler) {
continue;
}
// If there are bundles specified, make sure these match as well.
if ($bundles) {
$bundles = explode(',', $bundles);
/** @var array{target_bundles?: string[]} $handler_settings */
$handler_settings = $field_definition->getSetting('handler_settings');
$field_bundles = $handler_settings['target_bundles'] ?? [];
// No bundles are matched on the field, so keep looking through the
// types.
if (\count(array_intersect($bundles, $field_bundles)) === 0) {
continue;
}
}
// The handler and bundles match, so move on to the next field.
continue 2;
}
// There was no handler or bundle match that moved over this field, so it
// should be removed.
unset($fields_to_check[$field_name]);
}
return array_keys($fields_to_check);
}
}
