ispim-1.0.x-dev/ispim.module
ispim.module
<?php
/**
* @file
* ISPIM related functions.
*/
declare(strict_types=1);
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\file\FileInterface;
use Drupal\image\ImageStyleInterface;
use Drupal\ispim\Entity\IspimPreviewImageInterface;
/**
* Implements hook_theme().
*
* @phpstan-param array<string, array<string, mixed>> $existing
*
* @phpstan-return array<string, array<string, mixed>>
*/
function ispim_theme(array $existing, string $type, string $theme, string $path): array {
return [
'ispim_preview_image' => [
'render element' => 'elements',
'template' => 'ispim.ispim-preview-image',
],
'ispim_preview_image_selector' => [
'template' => 'ispim-preview-image-selector',
'variables' => [
'previewImages' => [],
'thumbnailImageStyleId' => NULL,
'imageStyleIds' => [],
// CSS selectors.
'imageSelectors' => NULL,
],
],
];
}
/**
* Implements hook_form_FORM_ID_alter().
*
* @phpstan-param array<string, mixed> $form
*/
function ispim_form_image_style_edit_form_alter(array &$form, FormStateInterface $formState): void {
$container = \Drupal::getContainer();
/** @var null|\Drupal\Core\Entity\EntityFormInterface $entityForm*/
$entityForm = $formState->getBuildInfo()['callback_object'];
$style = $entityForm?->getEntity();
if (!($style instanceof ImageStyleInterface)) {
return;
}
/* @noinspection PhpUnhandledExceptionInspection */
$previewImageStorage = $container
->get('entity_type.manager')
->getStorage('ispim_preview_image');
$previewImageIds = $previewImageStorage
->getQuery()
->accessCheck()
->condition('status', TRUE)
->sort('weight')
->execute();
if (!$previewImageIds) {
return;
}
/** @var \Drupal\ispim\Entity\IspimPreviewImageInterface[] $previewImages */
$previewImages = $previewImageStorage->loadMultiple($previewImageIds);
$imageSettings = $container->get('config.factory')->get('image.settings');
$htmlId = Html::getUniqueId('ispim-preview-image');
$form['ispim_preview_image'] = [
'#type' => 'select',
'#title' => t('Preview image'),
'#default_value' => $imageSettings->get('preview_image'),
'#options' => [],
'#weight' => -999,
'#id' => $htmlId,
'#wrapper_attributes' => [
'class' => [
'container-inline',
],
],
'#attributes' => [
'class' => ['ispim-preview-image-selector'],
],
'#attached' => [
'library' => [
'ispim/preview_image',
],
'drupalSettings' => [
'ispim' => [
'previewImageSelectors' => [
$htmlId => [
'imageSelector' => '.image-style-preview .preview-image img',
],
],
],
],
],
];
$fileUrlGenerator = $container->get('file_url_generator');
$now = $container->get('datetime.time')->getRequestTime();
$drupalSettings =& $form['ispim_preview_image']['#attached']['drupalSettings']['ispim'];
/** @var \Drupal\image\ImageStyleInterface[] $styles */
$styles = [$style];
foreach ($previewImages as $previewImage) {
$file = $previewImage->getImageFile();
$form['ispim_preview_image']['#options'][$file->getFileUri()] = $previewImage->label();
$drupalSettings['previewImages'][$file->getFileUri()]['original'] = $fileUrlGenerator->generateString($file->getFileUri());
foreach ($styles as $style) {
$url = $style->buildUrl($file->getFileUri());
$url .= (str_contains($url, '?') ? '&' : '?') . "cache_bypass=$now";
$drupalSettings['previewImages'][$file->getFileUri()]['imageStyles'][$style->id()] = $url;
}
}
}
/**
* Implements hook_theme_suggestions_HOOK().
*
* @phpstan-param array<string, mixed> $variables
*
* @phpstan-return array<string>
*/
function ispim_theme_suggestions_ispim_preview_image(array $variables): array {
$entityTypeId = $variables['elements']['#entity_type'];
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $variables['elements']["#{$entityTypeId}"];
$view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$bundle = $entity->bundle();
$id = $entity->id();
return [
"{$entityTypeId}__{$view_mode}",
"{$entityTypeId}__{$bundle}",
"{$entityTypeId}__{$bundle}__{$view_mode}",
"{$entityTypeId}__{$id}",
"{$entityTypeId}__{$id}__{$view_mode}",
];
}
/**
* Prepares variables for Ispim Preview Image templates.
*
* Default template: ispim_preview_image.html.twig.
*
* Most themes use their own copy of ispim_preview_image.html.twig. The default is located
* inside "/.../ispim/templates/ispim.ispim_preview_image.html.twig".
* Look in there for the full list of variables.
*
* @param array $variables
* An associative array containing:
* - elements: An array of elements to display in view mode.
* - ispim_preview_image: Ispim Preview Image object.
* - url: The Url object.
*
* @phpstan-param array<string, mixed> $variables
*
* @throws \Drupal\Core\Entity\EntityMalformedException
*/
function template_preprocess_ispim_preview_image(array &$variables): void {
$variables['view_mode'] = $variables['elements']['#view_mode'];
$variables['ispim_preview_image'] = $variables['elements']['#ispim_preview_image'];
/** @var \Drupal\ispim\Entity\IspimPreviewImageInterface $ispim_preview_image */
$ispim_preview_image = $variables['ispim_preview_image'];
$variables['url'] = $ispim_preview_image->toUrl(
'canonical',
[
'language' => $ispim_preview_image->language(),
],
);
$variables['linkRelationshipType'] = ispim_is_entity_page($ispim_preview_image)
? 'canonical'
: NULL;
$variables += ['content' => []];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
$variables['attributes']['role'] = 'article';
}
/**
* Implements hook_ENTITY_TYPE_update().
*/
function ispim_ispim_preview_image_update(IspimPreviewImageInterface $entity): void {
ispim_ensure_image_settings($entity->isPublished() ? NULL : $entity->id());
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function ispim_ispim_preview_image_delete(IspimPreviewImageInterface $entity): void {
ispim_ensure_image_settings($entity->id());
}
function ispim_ensure_image_settings(null|int|string $ignore): void {
$config = \Drupal::configFactory()->getEditable('image.settings');
$actual = $config->get('preview_image');
if ($actual === IspimPreviewImageInterface::DEFAULT_PREVIEW_IMAGE) {
return;
}
/* @noinspection PhpUnhandledExceptionInspection */
/** @var \Drupal\file\FileStorageInterface $fileStorage */
$fileStorage = \Drupal::entityTypeManager()->getStorage('file');
$files = $fileStorage->loadByProperties(['uri' => $actual]);
/** @var null|\Drupal\file\FileInterface $file */
$file = reset($files);
if (!($file instanceof FileInterface)) {
$config
->set('preview_image', IspimPreviewImageInterface::DEFAULT_PREVIEW_IMAGE)
->save();
return;
}
// The file referenced in the config is still exists.
// This runs in an entity operation (update, delete) transaction,
// maybe the file will be deleted, because before the update|delete this
// entity was the last referer, but after the transaction won't be any referer.
// Orphan files don't get deleted immediately.
// I don't want to check the file_usage service, because I don't know if
// this usage was already decremented or not.
/* @noinspection PhpUnhandledExceptionInspection */
$query = \Drupal::entityTypeManager()
->getStorage('ispim_preview_image')
->getQuery()
->accessCheck(FALSE)
->condition('image', $file->id())
->count();
if ($ignore) {
$query->condition('id', $ignore, '<>');
}
$numOfReferer = $query->execute();
if (!$numOfReferer) {
$config
->set('preview_image', IspimPreviewImageInterface::DEFAULT_PREVIEW_IMAGE)
->save();
}
}
/**
* Checks whether the current page is the full page view of the passed-in entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* An entity.
*
* @return bool
* Returns TRUE if the current page is the canonical page of the $entity.
*/
function ispim_is_entity_page(EntityInterface $entity): bool {
$entityTypeId = $entity->getEntityTypeId();
$routeMatch = \Drupal::routeMatch();
$pageEntity = ($routeMatch->getRouteName() === "entity.$entityTypeId.canonical")
? $routeMatch->getParameter($entityTypeId)
: NULL;
return !empty($pageEntity) && $pageEntity->id() === $entity->id();
}
