|
<?php declare(strict_types=1); |
|
|
|
namespace Drupal\bigpipe_field; |
|
|
|
use Drupal\Core\Access\AccessResult; |
|
use Drupal\Core\Access\AccessResultInterface; |
|
use Drupal\Core\Cache\Cache; |
|
use Drupal\Core\Cache\CacheableMetadata; |
|
use Drupal\Core\Entity\EntityInterface; |
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
|
use Drupal\Core\Field\FieldItemListInterface; |
|
use Drupal\Core\Security\TrustedCallbackInterface; |
|
use Drupal\Core\TypedData\TranslatableInterface; |
|
use Drupal\Core\Url; |
|
use Drupal\file\Entity\File; |
|
|
|
/** |
|
* Lazy builders. |
|
*/ |
|
class BigPipeFieldLazyBuilders implements TrustedCallbackInterface { |
|
|
|
/** |
|
* The image style entity storage. |
|
* |
|
* @var \Drupal\image\ImageStyleStorageInterface |
|
*/ |
|
protected $imageStyleStorage; |
|
|
|
/** |
|
* Entity type manager. |
|
* |
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
|
*/ |
|
protected $entityTypeManager; |
|
|
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
public static function trustedCallbacks() { |
|
return ['renderReallyLazyImageChunk']; |
|
} |
|
|
|
/** |
|
* Constructor. |
|
* |
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager |
|
* Entity type manager. |
|
*/ |
|
public function __construct(EntityTypeManagerInterface $entityTypeManager) { |
|
$this->entityTypeManager = $entityTypeManager; |
|
$this->imageStyleStorage = $entityTypeManager->getStorage('image_style'); |
|
} |
|
|
|
/** |
|
* Lazy builder for a chunk of images. |
|
* |
|
* @param string $entityType |
|
* Entity type. |
|
* @param mixed $entityId |
|
* Entity ID. |
|
* @param string $fieldName |
|
* Field name. |
|
* @param string $deltas |
|
* Comma-delimited list of deltas to render for the field. |
|
* @param string $langcode |
|
* Language code. |
|
* @param string $jsonSettings |
|
* JSON-encoded Widget settings. |
|
* |
|
* @return array |
|
* Render array. |
|
*/ |
|
public function renderReallyLazyImageChunk(string $entityType, $entityId, string $fieldName, string $deltas, string $langcode, string $jsonSettings): array { |
|
$elements = []; |
|
$deltas = explode(',', $deltas); |
|
$settings = json_decode($jsonSettings, TRUE); |
|
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ |
|
$entity = $this->entityTypeManager |
|
->getStorage($entityType)->load($entityId); |
|
$files = $this |
|
->getEntitiesToView($entity->get($fieldName), $deltas, $langcode); |
|
|
|
$url = NULL; |
|
$image_link_setting = $settings['image_link']; |
|
// Check if the formatter involves a link. |
|
if ($image_link_setting == 'content') { |
|
if (!$entity->isNew()) { |
|
$url = $entity->toUrl(); |
|
} |
|
} |
|
elseif ($image_link_setting == 'file') { |
|
$link_file = TRUE; |
|
} |
|
|
|
$image_style_setting = $settings['image_style']; |
|
|
|
// Collect cache tags to be added for each item in the field. |
|
$base_cache_tags = []; |
|
if (!empty($image_style_setting)) { |
|
$image_style = $this->imageStyleStorage->load($image_style_setting); |
|
$base_cache_tags = $image_style->getCacheTags(); |
|
} |
|
|
|
foreach ($files as $delta => $file) { |
|
$cache_contexts = []; |
|
if (isset($link_file)) { |
|
$image_uri = $file->getFileUri(); |
|
// @todo Wrap in file_url_transform_relative(). This is currently |
|
// impossible. As a work-around, we currently add the 'url.site' cache |
|
// context to ensure different file URLs are generated for different |
|
// sites in a multisite setup, including HTTP and HTTPS versions of the |
|
// same site. Fix in https://www.drupal.org/node/2646744. |
|
$url = Url::fromUri(file_create_url($image_uri)); |
|
$cache_contexts[] = 'url.site'; |
|
} |
|
$cache_tags = Cache::mergeTags($base_cache_tags, $file->getCacheTags()); |
|
|
|
// Extract field item attributes for the theme function, and unset them |
|
// from the $item so that the field template does not re-render them. |
|
$item = $file->_referringItem; |
|
|
|
$elements[$delta] = [ |
|
'#type' => 'container', |
|
'#attributes' => ['class' => ['field__item', 'lazy-built-glider-item']], |
|
'image' => [ |
|
'#theme' => 'image_formatter', |
|
'#item' => $item, |
|
// Can't use the lazy UI widget because it doesn't work with lazy builders! |
|
'#item_attributes' => ['data-lazy' => TRUE], |
|
'#image_style' => $image_style_setting, |
|
'#url' => $url, |
|
'#cache' => [ |
|
'tags' => $cache_tags, |
|
'contexts' => $cache_contexts, |
|
], |
|
], |
|
]; |
|
} |
|
return $elements; |
|
} |
|
|
|
/** |
|
* Returns the referenced entities for display. |
|
* |
|
* The method takes care of: |
|
* - checking entity access, |
|
* - placing the entities in the language expected for display. |
|
* |
|
* @param \Drupal\Core\Field\FieldItemListInterface |
|
* Field item list. |
|
* @param integer[] |
|
* Deltas to load. |
|
* @param string $langcode |
|
* The language code of the referenced entities to display. |
|
* |
|
* @return \Drupal\Core\Entity\EntityInterface[] |
|
* The array of referenced entities to display, keyed by delta. |
|
* |
|
* @see \Drupal\image\Plugin\Field\FieldFormatter\ImageFormatterBase::getEntitiesToView() |
|
*/ |
|
protected function getEntitiesToView(FieldItemListInterface $fieldItemList, array $deltas, string $langcode) { |
|
$entities = []; |
|
|
|
foreach ($deltas as $delta) { |
|
$item = $fieldItemList->get($delta); |
|
$entity = File::load($item->target_id); |
|
if (!empty($entity)) { |
|
// Set the entity in the correct language for display. |
|
if ($entity instanceof TranslatableInterface) { |
|
$entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode); |
|
} |
|
|
|
$access = $this->checkAccess($entity); |
|
// Add the access result's cacheability, ::view() needs it. |
|
$item->_accessCacheability = CacheableMetadata::createFromObject($access); |
|
if ($access->isAllowed()) { |
|
// Add the referring item, in case the formatter needs it. |
|
$entity->_referringItem = $item; |
|
$entities[$delta] = $entity; |
|
} |
|
} |
|
} |
|
|
|
return $entities; |
|
} |
|
|
|
/** |
|
* Checks access to the given entity. |
|
* |
|
* @see \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase |
|
* |
|
* @param \Drupal\Core\Entity\EntityInterface $entity |
|
* The entity to check. |
|
* |
|
* @return \Drupal\Core\Access\AccessResultInterface |
|
* A cacheable access result. |
|
*/ |
|
protected function checkAccess(EntityInterface $entity): AccessResultInterface { |
|
// Only check access if the current file access control handler explicitly |
|
// opts in by implementing FileAccessFormatterControlHandlerInterface. |
|
$access_handler_class = $entity->getEntityType()->getHandlerClass('access'); |
|
if (is_subclass_of($access_handler_class, '\Drupal\file\FileAccessFormatterControlHandlerInterface')) { |
|
return $entity->access('view', NULL, TRUE); |
|
} |
|
else { |
|
return AccessResult::allowed(); |
|
} |
|
} |
|
|
|
} |
Did I just spend a bunch of time re-creating https://www.drupal.org/project/big_pipe_sessionless ?
Edit: No, different problem spaces, but similar motivations.