reviewer-1.2.x-dev/modules/reviewer_test_kit/src/Plugin/reviewer/Task/Entity/Display/DisplayFieldsTaskBase.php
modules/reviewer_test_kit/src/Plugin/reviewer/Task/Entity/Display/DisplayFieldsTaskBase.php
<?php
declare(strict_types=1);
namespace Drupal\reviewer_test_kit\Plugin\reviewer\Task\Entity\Display;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
/**
* Base task for checking entity display field configuration.
*
* While it may be occasionally useful to extend this class, there is likely a
* different reviewer test kit task that could be used to implement checks with
* much less effort instead.
*
* Tasks extending this class must define either the $fieldTypes or $fieldNames
* properties. If both properties are empty, a \LogicException will be thrown.
*/
abstract class DisplayFieldsTaskBase extends DisplayTaskBase {
/**
* The types of fields to check.
*
* Field types are the field widget or formatter plugin names. 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 = [];
/**
* The names of fields to check.
*
* To check all fields, set the value to ['*'].
*
* 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 $fieldTypes are both
* empty.
*
* @var string[]
*/
protected array $fieldNames = [];
/**
* {@inheritdoc}
*/
protected function checkValidProperties(): void {
parent::checkValidProperties();
if (\count($this->fieldTypes) === 0 && \count($this->fieldNames) === 0) {
throw new \LogicException(sprintf('Both %1$s::fieldTypes and %1$s::fieldNames properties cannot be empty.', static::class));
}
}
/**
* Get a list of all field names to check for a specific display.
*
* @return string[]
*/
protected function fieldsToCheck(EntityDisplayInterface $display): array {
// If all field names are specified, then skip the more expensive type
// checks and just return all the field names from the components.
return $this->fieldNames === ['*']
? array_keys($this->fieldsCallback($display))
: array_unique(array_merge($this->fieldNames, $this->fieldNamesFromTypes($display)));
}
/**
* 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));
}
// Only check fields which match the type.
$field_names = array_filter(
$this->fieldsCallback($display),
fn(array $configuration): bool => \in_array($configuration['type'] ?? '', $this->fieldTypes, TRUE),
);
// No string keys means no handlers to parse.
if (array_is_list($this->fieldTypes)) {
return array_keys($field_names);
}
// 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 ($field_names 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) {
break;
}
if (\is_int($handler)) {
// Types without handlers were sorted to the end, so if the type
// matches move on to the next field, as types with handlers have
// already been checked.
if (($configuration['type'] ?? '') === $field_type) {
continue 2;
}
// The type does not match, so keep looking through the types.
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($field_names[$field_name]);
}
return array_keys($field_names);
}
}
