Skip to content
Snippets Groups Projects
Commit 900cd8a8 authored by Shibin Das's avatar Shibin Das
Browse files

Issue #3495133 by d34dman: Handle Dependency on "combined_image_styles"

parent 86d56f1b
No related branches found
No related tags found
No related merge requests found
......@@ -8,7 +8,6 @@ core_version_requirement: ^9.4 || ^10 || ^11
php: 8.1
dependencies:
- image_widget_crop:image_widget_crop
- combined_image_style:combined_image_style
- image_style_quality:image_style_quality
- focal_point:focal_point
- crop:crop
<?php
/**
* @file
* RIFT Module file.
*/
use Drupal\rift\Entity\ImageStyle;
/**
* Implements hook_entity_type_build().
*/
function rift_entity_type_build(&$entity_types): void {
if (isset($entity_types['image_style'])) {
$entity_types['image_style']->setClass(ImageStyle::class);
}
}
......@@ -43,3 +43,16 @@ services:
- '@config.factory'
- '@plugin.manager.rift_source'
- '@plugin.manager.rift_media_source'
rift.route_subscriber:
class: Drupal\rift\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
cache.rift_image_dimensions:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin }
factory: cache_factory:get
arguments: [ rift_image_dimensions ]
<?php
namespace Drupal\rift\Controller;
use Drupal\rift\Entity\CombinedImageStyle;
use Drupal\image\Controller\ImageStyleDownloadController as ImageStyleDownloadControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ImageStyleDownloadController extends ImageStyleDownloadControllerBase {
/**
* @param \Symfony\Component\HttpFoundation\Request $request
* @param $scheme
* @param $image_styles
* @param string $required_derivative_scheme
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function deliverCombined(Request $request, $scheme, $image_styles, string $required_derivative_scheme): Response {
return $this->deliver($request, $scheme, CombinedImageStyle::fromName($image_styles), $required_derivative_scheme);
}
}
<?php
namespace Drupal\rift\Entity;
use Drupal;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageEffectInterface;
use Drupal\image\ImageEffectPluginCollection;
use Drupal\image\ImageStyleInterface;
use stdClass;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
class CombinedImageStyle extends ImageStyle {
public const SEPARATOR = '-';
/**
* @var string
*/
private string $sourceUri;
/**
* @var array
*/
private array $imageStyles = [];
/**
* @param array $values
* @param string $entity_type
*/
public function __construct(array $values = [], string $entity_type = '') {
parent::__construct($values, $entity_type);
}
/**
* @param string $name
*
* @return \Drupal\rift\Entity\CombinedImageStyle
*/
public static function fromName(string $name): CombinedImageStyle {
$imageStyleIds = explode(self::SEPARATOR, $name);
$imageStyles = ImageStyle::loadMultiple($imageStyleIds);
return (new static())
->setImageStyles($imageStyles);
}
/**
* @param \Drupal\image\ImageStyleInterface $imageStyle
*
* @return array
*/
public static function loadCombinedBySingle(ImageStyleInterface $imageStyle): array {
$matches = [];
$wrappers = Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
foreach ($wrappers as $wrapper => $wrapper_data) {
if (!file_exists($wrapper . '://styles')) {
continue;
}
// Find image style folders matching the mask.
$mask = '/.*' . $imageStyle->id() . '.*/';
$matches += Drupal::service('file_system')->scanDirectory($wrapper . '://styles', $mask, ['recurse' => FALSE]);
}
// Load combined image styles.
return array_values(array_map(static function (stdClass $match) {
return self::fromName($match->name);
}, $matches));
}
/**
* @return string
*/
public function id(): string {
return implode(self::SEPARATOR, array_map(static function (ImageStyleInterface $imageStyle) {
return $imageStyle->id();
}, $this->getImageStyles()));
}
/**
* @return string
*/
public function getSourceUri(): string {
return $this->sourceUri;
}
/**
* @param string $sourceUri
*
* @return $this
*/
public function setSourceUri(string $sourceUri): CombinedImageStyle {
$this->sourceUri = $sourceUri;
return $this;
}
/**
* @return array
*/
public function getImageStyles(): array {
return $this->imageStyles;
}
/**
* @param array $imageStyles
*
* @return $this
*/
public function setImageStyles(array $imageStyles): CombinedImageStyle {
$this->imageStyles = array_filter(array_map(static function ($imageStyle) {
return ($imageStyle instanceof ImageStyleInterface ? $imageStyle : ImageStyle::load($imageStyle));
}, $imageStyles));
return $this;
}
/**
* @param $imageStyle
*
* @return $this
*/
public function addImageStyle($imageStyle): CombinedImageStyle {
$this->imageStyles[] = ($imageStyle instanceof ImageStyleInterface ? $imageStyle : ImageStyle::load($imageStyle));
return $this;
}
/**
* @return bool
*/
public function hasImageStyles(): bool {
return !empty($this->imageStyles);
}
/**
* @return \Drupal\image\ImageEffectPluginCollection
* @throws \Exception
*/
public function getEffects(): ImageEffectPluginCollection {
if (!$this->effectsCollection) {
$effects = array_map(static function (ImageStyle $imageStyle) {
return array_map(static function (ImageEffectInterface $effect) {
return $effect->getConfiguration();
}, iterator_to_array($imageStyle->getEffects()->getIterator()));
}, $this->getImageStyles());
$effects = array_merge(...array_values($effects));
$this->effectsCollection = new ImageEffectPluginCollection($this->getImageEffectPluginManager(), $effects);
}
return $this->effectsCollection;
}
/**
* {@inheritdoc}
*/
public function getPathToken($uri): string {
$uriWithExtension = $this->addExtension($uri);
return implode(self::SEPARATOR, array_map(static function (ImageStyleInterface $imageStyle) use ($uriWithExtension) {
return substr(Crypt::hmacBase64($imageStyle->id() . ':' . $uriWithExtension, $imageStyle->getPrivateKey() . $imageStyle->getHashSalt()), 0, 8);
}, $this->getImageStyles()));
}
/**
* {@inheritdoc}
*/
public function getCacheTagsToInvalidate(): array {
return array_merge(...array_values(array_map(static function (ImageStyleInterface $imageStyle) {
return ['config:' . $imageStyle->getConfigDependencyName()];
}, $this->getImageStyles())));
}
/**
* @param string $uri
*
* @return array
*/
public function getDimensions(string $uri): array {
$cache = \Drupal::cache('image_dimensions');
$derivativeUri = $this->buildUri($this->sourceUri);
// Load from cache.
if ($cacheEntry = $cache->get($derivativeUri)) {
return $cacheEntry->data;
}
// Calculate dimensions after effects.
$resource = Drupal::service('image.factory')->get($uri);
$dimensions = [
'width' => $resource->getWidth(),
'height' => $resource->getHeight(),
];
$this->transformDimensions($dimensions, $uri);
// Set cache and return.
$cache->set($derivativeUri, $dimensions);
return $dimensions;
}
/**
* @return int
*/
public function getWidth(): int {
return $this->getDimensions($this->sourceUri)['width'] ?? 0;
}
/**
* @return int
*/
public function getHeight(): int {
return $this->getDimensions($this->sourceUri)['height'] ?? 0;
}
/**
* @return string
*/
public function buildCombinedUri(): string {
return $this->buildUri($this->sourceUri);
}
public function buildCombinedUrl($clean_urls = NULL): string {
$uri = $this->buildCombinedUri();
/** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
// The token query is added even if the
// 'image.settings:allow_insecure_derivatives' configuration is TRUE, so
// that the emitted links remain valid if it is changed back to the default
// FALSE. However, sites which need to prevent the token query from being
// emitted at all can additionally set the
// 'image.settings:suppress_itok_output' configuration to TRUE to achieve
// that (if both are set, the security token will neither be emitted in the
// image derivative URL nor checked for in
// \Drupal\image\ImageStyleInterface::deliver()).
$token_query = [];
if (!\Drupal::config('image.settings')->get('suppress_itok_output')) {
$path = $this->getSourceUri();
// The passed $path variable can be either a relative path or a full URI.
if (!$stream_wrapper_manager::getScheme($path)) {
$path = \Drupal::config('system.file')->get('default_scheme') . '://' . $path;
}
$original_uri = $stream_wrapper_manager->normalizeUri($path);
$token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)];
}
if ($clean_urls === NULL) {
// Assume clean URLs unless the request tells us otherwise.
$clean_urls = TRUE;
try {
$request = \Drupal::request();
$clean_urls = RequestHelper::isCleanUrl($request);
}
catch (ServiceNotFoundException $e) {
}
}
// If not using clean URLs, the image derivative callback is only available
// with the script path. If the file does not exist, use Url::fromUri() to
// ensure that it is included. Once the file exists it's fine to fall back
// to the actual file path, this avoids bootstrapping PHP once the files are
// built.
if ($clean_urls === FALSE && $stream_wrapper_manager::getScheme($uri) == 'public' && !file_exists($uri)) {
$directory_path = $stream_wrapper_manager->getViaUri($uri)->getDirectoryPath();
return Url::fromUri('base:' . $directory_path . '/' . $stream_wrapper_manager::getTarget($uri), ['absolute' => TRUE, 'query' => $token_query])->toString();
}
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$file_url = $file_url_generator->generateAbsoluteString($uri);
// Append the query string with the token, if necessary.
if ($token_query) {
$file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query);
}
return $file_url;
}
/**
* {@inheritdoc}
*/
public function flush($path = NULL): void {
parent::flush($path);
if (!isset($this->sourceUri)) {
return;
}
// Invalidate image dimensions cache.
$derivativeUri = $this->buildUri($this->sourceUri);
\Drupal::cache('image_dimensions')->delete($derivativeUri);
}
/**
* {@inheritdoc}
*/
public function createDerivative($original_uri, $derivative_uri, $overwrite = FALSE): bool {
$created = ($overwrite || !file_exists($derivative_uri)) && parent::createDerivative($original_uri, $derivative_uri);
if ($created) {
// Invalidate image dimensions cache when new file gets written.
\Drupal::cache('rift_image_dimensions')->delete($derivative_uri);
}
return file_exists($derivative_uri);
}
/**
* @return array
*/
public function toImage(): array {
return [
'#theme' => 'image',
'#width' => $this->getWidth(),
'#height' => $this->getHeight(),
'#uri' => $this->buildCombinedUrl(),
];
}
}
<?php
namespace Drupal\rift\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\image\Entity\ImageStyle as ImageStyleBase;
class ImageStyle extends ImageStyleBase {
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
// To prevent the parent::postSave().
$this->invalidateTagsOnSave($update);
if ($update) {
if (!empty($this->original) && $this->id() !== $this->original->id()) {
// The old image style name needs flushing after a rename.
$this->original->flush();
// Update field settings if necessary.
if (!$this->isSyncing()) {
static::replaceImageStyle($this);
}
}
elseif ($this->getEffects()->getConfiguration() !== $this->original->getEffects()->getConfiguration()) {
// Flush image style only when effects configuration changed.
$this->flush();
}
}
}
public function flush($path = NULL) {
foreach (CombinedImageStyle::loadCombinedBySingle($this) as $combinedImageStyle) {
$combinedImageStyle->flush($path);
}
parent::flush($path);
}
}
......@@ -2,7 +2,7 @@
namespace Drupal\rift\Plugin\RiftSource;
use Drupal\combined_image_style\Entity\CombinedImageStyle as CombinedImageStyleService;
use Drupal\rift\Entity\CombinedImageStyle as CombinedImageStyleService;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
......
<?php
namespace Drupal\rift\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\RouteCollection;
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection): void {
/** @var \Symfony\Component\Routing\Route $route */
if ($route = $collection->get('image.style_public')) {
$path = $route->getPath();
$route->setPath(str_replace('image_style', 'image_styles', $path));
$route->setDefault('_controller', '\Drupal\rift\Controller\ImageStyleDownloadController::deliverCombined');
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events = [];
if (!\Drupal::moduleHandler()->moduleExists('combined_image_styles')) {
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -1025];
}
return $events;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment