MTShop/code/web/modules/contrib/blazy/src/BlazyManager.php

395 lines
14 KiB
PHP

<?php
namespace Drupal\blazy;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Template\Attribute;
use Drupal\blazy\Theme\BlazyAttribute;
use Drupal\blazy\Cache\BlazyCache;
use Drupal\blazy\Theme\Lightbox;
use Drupal\blazy\Utility\CheckItem;
/**
* Implements a public facing blazy manager.
*
* A few modules re-use this: GridStack, Mason, Slick...
*/
class BlazyManager extends BlazyManagerBase implements TrustedCallbackInterface {
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRenderBlazy', 'preRenderBuild'];
}
/**
* Returns the enforced rich media content, or media using theme_blazy().
*
* @param array $build
* The array containing: item, content, settings, or optional captions.
* @param int $delta
* The optional delta.
*
* @return array
* The alterable and renderable array of enforced content, or theme_blazy().
*
* @todo remove some $settings after sub-modules.
*/
public function getBlazy(array $build = [], $delta = -1) {
foreach (BlazyDefault::themeProperties() as $key) {
$build[$key] = $build[$key] ?? [];
}
$settings = &$build['settings'];
$settings += BlazyDefault::itemSettings();
$item = $build['item'];
CheckItem::essentials($settings, $item, $delta);
// Prevents double checks.
// @todo re-check for dup thumbnails without a reset here, see #3278525.
$blazies = $settings['blazies']->reset($settings);
$blazies->set('is.api', TRUE);
// Respects content not handled by theme_blazy(), but passed through.
// Yet allows rich contents which might still be processed by theme_blazy().
$content = !$blazies->get('image.uri') ? $build['content'] : [
'#theme' => 'blazy',
'#delta' => $blazies->get('delta'),
'#item' => $item,
'#image_style' => $settings['image_style'],
'#build' => $build,
'#pre_render' => [[$this, 'preRenderBlazy']],
];
$this->moduleHandler->alter('blazy', $content, $settings);
return $content;
}
/**
* Builds the Blazy image as a structured array ready for ::renderer().
*
* @param array $element
* The pre-rendered element.
*
* @return array
* The renderable array of pre-rendered element.
*/
public function preRenderBlazy(array $element) {
$build = $element['#build'];
unset($element['#build']);
// Prepare the main image.
$this->prepareBlazy($element, $build);
// Fetch the newly modified settings.
$settings = $element['#settings'];
$blazies = $settings['blazies'];
$url = $blazies->get('entity.url');
if ($blazies->get('switch') == 'content' && $url) {
$element['#url'] = $url;
}
elseif ($blazies->get('lightbox.name')) {
Lightbox::build($element);
}
return $element;
}
/**
* Returns the contents using theme_field(), or theme_item_list().
*
* Blazy outputs can be formatted using either flat list via theme_field(), or
* a grid of Field items or Views rows via theme_item_list().
*
* @param array $build
* The array containing: settings, children elements, or optional items.
*
* @return array
* The alterable and renderable array of contents.
*/
public function build(array $build = []) {
$settings = &$build['settings'];
Blazy::verify($settings);
$blazies = $settings['blazies'];
// This #pre_render doesn't work if called from Views results, hence the
// output is split either as theme_field() or theme_item_list().
if ($blazies->is('grid')) {
// Take over theme_field() with a theme_item_list(), if so configured.
// The reason: this is not only fed by field items, but also Views rows.
$content = [
'#build' => $build,
'#pre_render' => [[$this, 'preRenderBuild']],
];
// Yet allows theme_field(), if so required, such as for linked_field.
$build = $blazies->get('use.theme_field') ? [$content] : $content;
}
else {
// If not a grid, pass items as regular index children to theme_field().
$settings = $this->getSettings($build);
Blazy::verify($settings);
// Runs after ::getSettings.
$this->toElementChildren($build);
$build['#blazy'] = $settings;
$this->setAttachments($build, $settings);
}
$this->moduleHandler->alter('blazy_build', $build, $settings);
return $build;
}
/**
* Builds the Blazy outputs as a structured array ready for ::renderer().
*/
public function preRenderBuild(array $element): array {
$build = $element['#build'];
unset($element['#build']);
// Checks if we got some signaled attributes.
$attributes = $element['#theme_wrappers']['container']['#attributes'] ?? $element['#attributes'] ?? [];
$settings = $this->getSettings($build);
// Runs after ::getSettings.
$this->toElementChildren($build);
// Take over elements for a grid display as this is all we need, learned
// from the issues such as: #2945524, or product variations.
// We'll selectively pass or work out $attributes not so far below.
$element = $this->toGrid($build, $settings);
$this->setAttachments($element, $settings);
if ($attributes) {
// Signals other modules if they want to use it.
// Cannot merge it into Grid (wrapper_)attributes, done as grid.
// Use case: Product variations, best served by ElevateZoom Plus.
if (isset($element['#ajax_replace_class'])) {
$element['#container_attributes'] = $attributes;
}
else {
// Use case: VIS, can be blended with UL element safely down here.
// The $attributes is merged with self::toGrid() ones here.
$element['#attributes'] = NestedArray::mergeDeep($element['#attributes'], $attributes);
}
}
return $element;
}
/**
* Build captions for both old image, or media entity.
*/
protected function buildCaption(array $captions, array $settings, $id = 'blazy') {
$blazies = $settings['blazies'];
$content = [];
foreach ($captions as $key => $caption_content) {
if ($caption_content) {
$content[$key]['content'] = $caption_content;
$content[$key]['tag'] = strpos($key, 'title') !== FALSE ? 'h2' : 'div';
$class = $key == 'alt' ? 'description' : str_replace('field_', '', $key);
$attrs = new Attribute();
$attrs->addClass($id . '__caption--' . str_replace('_', '-', $class));
$content[$key]['attributes'] = $attrs;
}
}
// Figcaption is more relevant for core filter captions under Figure.
$tag = $blazies->is('figcaption') ? 'figcaption' : 'div';
return $content ? ['inline' => $content, 'tag' => $tag] : [];
}
/**
* Build out (rich media) content.
*/
private function buildContent(array &$element, array &$build) {
$settings = &$build['settings'];
$blazies = $settings['blazies'];
if (empty($build['content'])) {
return;
}
// Prevents complication for now, such as lightbox for Facebook, etc.
// Either makes no sense, or not currently supported without extra legs.
// Original formatter settings can still be accessed via content variable.
$blazies->set('placeholder', [])
->set('is.bg', FALSE)
->set('use.loader', FALSE);
// $settings = array_merge($settings, BlazyDefault::richSettings());
// Supports HTML content for lightboxes as long as having image trigger.
// Type rich to not conflict with Image rendered by its formatter option.
$supported = $blazies->is('richbox') ?: $settings['_richbox'] ?? FALSE;
$rich = $blazies->get('media.type') == 'rich' && $supported;
$litebox = $blazies->is('lightbox');
$blazy = ($build['content'][0]['#settings'] ?? NULL);
if ($rich && $litebox && is_object($blazy)) {
if ($blazies->is('hires', !empty($settings['image']))) {
// Overrides the overriden settings with original formatter settings.
$settings = array_merge($settings, $blazy->storage());
$element['#lightbox_html'] = $build['content'];
$build['content'] = [];
}
}
}
/**
* Build out (Responsive) image.
*
* Since 2.9, many were moved into BlazyTheme to support custom work better.
*/
private function buildMedia(array &$element, array &$build): void {
$item = $build['item'];
$settings = &$build['settings'];
$blazies = $settings['blazies'];
// (Responsive) image with item attributes, might be RDF.
$item_attributes = empty($build['item_attributes'])
? []
: BlazyAttribute::sanitize($build['item_attributes']);
// Extract field item attributes for the theme function, and unset them
// from the $item so that the field template does not re-render them.
if ($item && isset($item->_attributes)) {
$item_attributes += $item->_attributes;
unset($item->_attributes);
}
// Responsive image integration, with/o CSS background so to work with.
$resimage = $blazies->get('resimage');
if ($resimage && $caches = $resimage['caches'] ?? []) {
$element['#cache']['tags'] = $caches;
}
// Provides caches for regular image, with/o CSS background.
if (!$blazies->get('resimage.id')) {
if ($caches = BlazyCache::file($settings)) {
$element['#cache']['max-age'] = -1;
foreach ($caches as $key => $cache) {
$element['#cache'][$key] = $cache;
}
}
}
// Pass non-rich-media elements to theme_blazy().
$element['#item_attributes'] = $item_attributes;
}
/**
* Prepares Blazy settings.
*
* Supports galeries if provided, updates $settings.
* Cases: Blazy within Views gallery, or references without direct image.
* Views may flatten out the array, bail out.
* What we do here is extract the formatter settings from the first found
* image and pass its settings to this container so that Blazy Grid which
* lacks of settings may know if it should load/ display a lightbox, etc.
* Lightbox should work without `Use field template` checked.
*/
private function getSettings(array &$build) {
$settings = $build['settings'] ?? [];
$blazies = $settings['blazies'] ?? NULL;
if ($blazies && $data = $blazies->get('first.data')) {
if (is_array($data)) {
$this->isBlazy($settings, $data);
}
}
return $settings;
}
/**
* Prepares the Blazy output as a structured array ready for ::renderer().
*
* @param array $element
* The renderable array being modified.
* @param array $build
* The array of information containing the required Image or File item
* object, settings, optional container attributes.
*/
private function prepareBlazy(array &$element, array $build) {
$item = $build['item'] ?? NULL;
$settings = &$build['settings'];
$blazies = $settings['blazies'];
foreach (BlazyDefault::themeAttributes() as $key) {
$key = $key . '_attributes';
$build[$key] = $build[$key] ?? [];
}
// Blazy has these 3 attributes, yet provides optional ones far below.
// Sanitize potential user-defined attributes such as from BlazyFilter.
// Skip attributes via $item, or by module, as they are not user-defined.
$attributes = &$build['attributes'];
// Initial feature checks, URI, delta, media features, etc.
Blazy::prepare($settings, $item);
// Build thumbnail and optional placeholder based on thumbnail.
// Prepare image URL and its dimensions, including for rich-media content,
// such as for local video poster image if a poster URI is provided.
Blazy::prepared($attributes, $settings, $item);
// Only process (Responsive) image/ video if no rich-media are provided.
$this->buildContent($element, $build);
if (empty($build['content'])) {
$this->buildMedia($element, $build);
}
// Provides extra attributes as needed, excluding url, item, done above.
// Was planned to replace sub-module item markups if similarity is found for
// theme_gridstack_box(), theme_slick_slide(), etc. Likely for Blazy 3.x+.
foreach (['caption', 'media', 'wrapper'] as $key) {
$element["#$key" . '_attributes'] = empty($build[$key . '_attributes'])
? [] : BlazyAttribute::sanitize($build[$key . '_attributes']);
}
// Provides captions, if so configured.
$id = $blazies->get('item.id', 'blazy');
$content = $build['captions'] ?? '';
if ($content && ($captions = $this->buildCaption($content, $settings, $id))) {
$element['#captions'] = $captions;
$element['#caption_attributes']['class'][] = $id . '__caption';
}
// Pass common elements to theme_blazy().
$element['#attributes'] = $attributes;
$element['#settings'] = $settings;
$element['#url_attributes'] = $build['url_attributes'];
// Preparing Blazy to replace other blazy-related content/ item markups.
// Composing or layering is crucial for mixed media (icon over CTA or text
// or lightbox links or iframe over image or CSS background over noscript
// which cannot be simply dumped as array without elaborate arrangements).
foreach (['content', 'icon', 'overlay', 'preface', 'postscript'] as $key) {
$element["#$key"] = empty($element["#$key"]) ? $build[$key] : NestedArray::mergeDeep($element["#$key"], $build[$key]);
}
}
/**
* Prepares Blazy outputs, extract items as indices.
*
* If children are grouped within items property, reset to indexed keys.
* Blazy comes late to the party after sub-modules decided what they want
* where items may be stored as direct indices, or put into items property.
* Actually the same issue happens at core where contents may be indexed or
* grouped. Meaning not a problem at all, only a problem for consistency.
*/
private function toElementChildren(array &$build): void {
$build = $build['items'] ?? $build;
unset($build['items'], $build['settings']);
}
}