ispim-1.0.x-dev/src/PreviewImage/ListBuilder.php
src/PreviewImage/ListBuilder.php
<?php
declare(strict_types=1);
namespace Drupal\ispim\PreviewImage;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Entity\EntityViewModeInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\ispim\Entity\IspimPreviewImageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @method \Drupal\ispim\Entity\IspimPreviewImageInterface[] load()
*/
class ListBuilder extends EntityListBuilder implements FormInterface {
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The key to use for the form element containing the entities.
*/
protected string $entitiesKey = 'entities';
/**
* @var array<\Drupal\ispim\Entity\IspimPreviewImageInterface>
*/
protected array $entities = [];
/**
* Name of the entity's weight field or NULL if no field is provided.
*/
protected ?string $weightKey = NULL;
protected ?EntityViewModeInterface $viewMode = NULL;
protected ?EntityViewDisplayInterface $viewDisplay = NULL;
protected ?EntityViewBuilderInterface $viewBuilder = NULL;
/**
* @var null|array<string, array<string, mixed>>
*/
protected ?array $components = NULL;
/**
* @var null|array<string, string|\Stringable>
*/
protected ?array $header = NULL;
/**
* {@inheritdoc}
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function createInstance(
ContainerInterface $container,
EntityTypeInterface $entity_type,
): static {
// @phpstan-ignore-next-line
return new static(
$entity_type,
$container->get('entity_type.manager'),
$container->get('config.factory'),
$container->get('form_builder'),
$container->get('entity_type.bundle.info'),
$container->get('entity_field.manager'),
);
}
/**
* {@inheritdoc}
*/
public function __construct(
EntityTypeInterface $entity_type,
EntityTypeManagerInterface $entityTypeManager,
protected ConfigFactoryInterface $configFactory,
protected FormBuilderInterface $formBuilder,
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo,
protected EntityFieldManagerInterface $entityFieldManager,
) {
$this->entityTypeManager = $entityTypeManager;
/* @noinspection PhpUnhandledExceptionInspection */
parent::__construct(
$entity_type,
$this->entityTypeManager->getStorage($entity_type->id()),
);
if ($this->entityType->hasKey('weight')) {
$this->weightKey = (string) $this->entityType->getKey('weight');
}
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'ispim_preview_images_form';
}
/**
* {@inheritdoc}
*
* @phpstan-param array<string, mixed> $form
*
* @return array<string, mixed>
*
* @throws \Drupal\Core\Entity\EntityMalformedException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form[$this->entitiesKey] = [
'#type' => 'table',
'#header' => $this->buildHeader(),
'#empty' => $this->t(
'There are no @entityType.pluralLabel yet.',
[
'@entityType.pluralLabel' => $this->entityType->getPluralLabel(),
],
),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
'#sorted' => FALSE,
];
$this->entities = $this->load();
$delta = 10;
// Change the delta of the weight field if have more than 20 entities.
if ($this->weightKey) {
$count = count($this->entities);
if ($count > 20) {
$delta = ceil($count / 2);
}
}
foreach ($this->entities as $entity) {
$row = $this->buildRow($entity);
if (isset($row[$this->weightKey])) {
$row[$this->weightKey]['#delta'] = $delta;
}
$form[$this->entitiesKey][$entity->id()] = $row;
}
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*
* @phpstan-param array<string, mixed> $form
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
// No validation.
}
/**
* {@inheritdoc}
*
* @phpstan-param array<string, mixed> $form
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$this
->submitFormUpdateEntities($form, $form_state)
->submitFormUpdateImageSettings($form, $form_state);
}
/**
* @phpstan-param array<string, mixed> $form
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function submitFormUpdateEntities(array &$form, FormStateInterface $formState): static {
$numOfUpdated = 0;
foreach ($formState->getValue($this->entitiesKey, []) ?: [] as $id => $value) {
if (!isset($this->entities[$id])) {
continue;
}
$newWeight = (int) $value[$this->weightKey];
if ($this->entities[$id]->getWeight() !== $newWeight) {
// Save entity only when its weight was changed.
$this->entities[$id]->set($this->weightKey, $newWeight);
$this->entities[$id]->save();
$numOfUpdated++;
}
}
if ($numOfUpdated) {
$this->messenger()->addStatus($this->t(
'Order of the preview images have been updated.',
));
}
return $this;
}
/**
* @phpstan-param array<string, mixed> $form
*/
protected function submitFormUpdateImageSettings(array &$form, FormStateInterface $formState): static {
$previewImage = $formState->getValue(['image_settings', 'preview_image']);
if (!$previewImage
|| $previewImage === $this->configFactory->get('image.settings')->get('preview_image')
) {
return $this;
}
$this
->configFactory
->getEditable('image.settings')
->set('preview_image', $previewImage)
->save();
$this->messenger()->addStatus($this->t(
'Default preview image has been updated.',
));
return $this;
}
/**
* {@inheritdoc}
*
* @noinspection PhpMissingParentCallCommonInspection
*/
protected function getEntityListQuery(): QueryInterface {
// No parent::getEntityListQuery() call, because of "sort by ID".
$keys = $this->entityType->getKeys();
$query = $this
->getStorage()
->getQuery()
->accessCheck();
if (!empty($keys['weight'])) {
$query->sort($keys['weight']);
}
if (!empty($keys['label'])) {
$query->sort($keys['label']);
}
// Only add the pager if a limit is specified.
if ($this->limit) {
$query->pager($this->limit);
}
return $query;
}
/**
* {@inheritdoc}
*
* @return array<string, mixed>
*/
public function render(): array {
$this->initViewDisplay();
return $this->weightKey
? $this->formBuilder->getForm($this)
: parent::render();
}
protected function initViewDisplay(): static {
$this->viewMode = NULL;
$this->viewDisplay = NULL;
$this->viewBuilder = NULL;
$this->header = NULL;
$this->components = NULL;
$settings = $this->configFactory->get("ispim.{$this->entityTypeId}.settings");
$renderer = $settings->get('listBuilder.renderer');
if ($renderer !== 'viewMode') {
return $this;
}
$viewModeSortId = $settings->get('listBuilder.config.viewMode.id');
if (!$viewModeSortId) {
return $this;
}
/* @noinspection PhpUnhandledExceptionInspection */
$this->viewMode = $this
->entityTypeManager
->getStorage('entity_view_mode')
->load("{$this->entityTypeId}.$viewModeSortId");
if (!$this->viewMode) {
return $this;
}
/* @noinspection PhpUnhandledExceptionInspection */
$this->viewDisplay = $this
->entityTypeManager
->getStorage('entity_view_display')
->load("{$this->entityTypeId}.{$this->entityTypeId}.$viewModeSortId");
if (!$this->viewDisplay) {
return $this;
}
$this->viewBuilder = $this->entityTypeManager->getViewBuilder($this->entityTypeId);
// If for some reason ::buildRow() were called before the ::buildHeader()
// then make it sure ::$header is already initialized.
$this->initViewDisplayHeader();
return $this;
}
protected function initViewDisplayHeader(): static {
$this->header = [];
$this->components = $this->viewDisplay->getComponents();
uasort(
$this->components,
function (array $a, array $b): int {
return $a['weight'] <=> $b['weight'];
},
);
$fields = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $this->entityTypeId);
foreach ($this->components as $name => $component) {
$this->header[$name] = $fields[$name]->getLabel();
if ($name === 'image') {
$this->header['default'] = $this->t('Default');
}
}
return $this;
}
/**
* {@inheritdoc}
*
* @return array<string, string|\Stringable>
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public function buildHeader(): array {
return $this->viewDisplay
? $this->buildHeaderViewMode()
: $this->buildHeaderDefault();
}
/**
* @return array<string, string|\Stringable>
*/
protected function buildHeaderViewMode(): array {
return $this->header + parent::buildHeader();
}
/**
* @return array<string, string|\Stringable>
*/
protected function buildHeaderDefault(): array {
$header = [];
$header['id'] = $this->t('Id');
if ($this->weightKey) {
$header['weight'] = $this->t('Weight');
}
if ($this->entityType->getBundleEntityType()) {
$header['bundle'] = $this->t('Type');
}
$header['image'] = $this->t('Image');
$header['default'] = $this->t('Default');
$header['label'] = $this->t('Label');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*
* @return array<string, mixed>
*
* @throws \Drupal\Core\Entity\EntityMalformedException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public function buildRow(EntityInterface $entity): array {
/** @var \Drupal\ispim\Entity\IspimPreviewImageInterface $entity */
return $this->viewDisplay
? $this->buildRowViewMode($entity)
: $this->buildRowDefault($entity);
}
/**
* @return array<string, mixed>
*/
protected function buildRowViewMode(IspimPreviewImageInterface $entity): array {
$row = [
'#attributes' => [
'class' => ['draggable'],
],
'#weight' => $entity->getWeight(),
];
$imageSettings = $this->configFactory->get('image.settings');
foreach ($this->components as $componentName => $component) {
if ($componentName !== $this->weightKey) {
// @todo I have no idea what's the problem here.
// @phpstan-ignore-next-line
$row[$componentName]['data'] = $this->viewBuilder->viewField($entity->get($componentName), $component);
if ($componentName === 'image') {
$row['default'] = [
'data' => [
'#type' => 'radio',
'#title' => $this->t('Default'),
'#title_display' => 'invisible',
'#required' => TRUE,
'#return_value' => $entity->getImageFile()->getFileUri(),
'#default_value' => $imageSettings->get('preview_image') === $entity->getImageFile()->getFileUri()
? $entity->getImageFile()->getFileUri()
: NULL,
'#name' => 'image_settings[preview_image]',
'#parents' => ['image_settings', 'preview_image'],
'#access' => $entity->isPublished(),
],
];
}
continue;
}
$row[$componentName] = [
'#type' => 'weight',
'#title' => $this->t(
'Weight for @entity.label',
[
'@title' => $entity->label(),
],
),
'#title_display' => 'invisible',
'#default_value' => $entity->getWeight(),
'#attributes' => [
'class' => [
'weight',
],
],
];
}
return $row + parent::buildRow($entity);
}
/**
* @return array<string, mixed>
*
* @throws \Drupal\Core\Entity\EntityMalformedException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function buildRowDefault(IspimPreviewImageInterface $entity): array {
$bundleEntityTypeId = $entity
->getEntityType()
->getBundleEntityType();
$bundle = NULL;
if ($bundleEntityTypeId) {
/* @noinspection PhpUnhandledExceptionInspection */
$bundle = $this
->entityTypeManager
->getStorage($entity->getEntityType()->getBundleEntityType())
->load($entity->bundle());
}
$row = [];
$row['id'] = [
'data' => [
'#markup' => $entity->id(),
],
];
if ($this->weightKey) {
// Override default values to markup elements.
$row['#attributes']['class'][] = 'draggable';
$row['#weight'] = $entity->getWeight();
$row[$this->weightKey] = [
'#type' => 'weight',
'#title' => $this->t(
'Weight for @entity.label',
[
'@title' => $entity->label(),
],
),
'#title_display' => 'invisible',
'#default_value' => $entity->getWeight(),
'#attributes' => [
'class' => [
'weight',
],
],
];
}
if ($bundle) {
$row['bundle'] = [
'data' => ($bundle->access('edit') ? $bundle->toLink(NULL, 'edit-form') : $bundle->label()),
];
}
$row['image'] = [
'data' => $entity->get('image')->view([
'type' => 'image',
'label' => 'hidden',
'settings' => [
'image_link' => '',
'image_style' => $this->getImageStyleId(),
],
]),
];
$row['default'] = [
'data' => [
'#type' => 'markup',
'#markup' => $this->t('@todo Implement'),
],
];
$row['label'] = [
'data' => [
'#type' => 'link',
'#title' => $entity->label(),
'#url' => $entity->toUrl('canonical'),
],
];
return $row + parent::buildRow($entity);
}
protected function getImageStyleId(): string {
$settings = $this->configFactory->get("ispim.{$this->entityTypeId}.settings");
return $settings->get('listBuilder.config.default.imageStyle');
}
}
