diff --git a/core/modules/image/src/Annotation/ImageProcessPipeline.php b/core/modules/image/src/Annotation/ImageProcessPipeline.php index 26ad974e44..0a691b39a3 100644 --- a/core/modules/image/src/Annotation/ImageProcessPipeline.php +++ b/core/modules/image/src/Annotation/ImageProcessPipeline.php @@ -29,11 +29,4 @@ class ImageProcessPipeline extends Plugin { */ public $description; - /** - * The name of the derived image handler class. - * - * @var string - */ - public $derivedImageClass; - } diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index c22f581572..dda0ced5a4 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -165,11 +165,10 @@ protected static function replaceImageStyle(ImageStyleInterface $style) { */ public function buildUri($uri) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - return $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->setSourceUri($uri) - ->buildDerivativeUri(); + ->setSourceImageUri($uri) + ->getDerivedImageUri(); } /** @@ -177,11 +176,11 @@ public function buildUri($uri) { */ public function buildUrl($path, $clean_urls = NULL) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - return $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->setSourceUri($path) - ->buildDerivativeUrl($clean_urls); + ->setSourceImageUri($path) + ->setDerivedImageCleanUrl($clean_urls) + ->getDerivedImageUrl(); } /** @@ -189,16 +188,13 @@ public function buildUrl($path, $clean_urls = NULL) { */ public function flush($path = NULL) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - $derivative_handler->setImageStyle($this); + $pipeline = \Drupal::service('image.processor')->createInstance('core'); + $pipeline->setImageStyle($this); if (isset($path)) { // A specific image path has been provided. Flush only that derivative. - $derivative_handler->setSourceUri($path)->removeSourceUriDerivative(); - } - else { - // Flush all the derivatives for this image style. - $derivative_handler->removeAllImageStyleDerivatives(); + $pipeline->setSourceImageUri($path); } + $pipeline->removeDerivedImagesFromStorage(); return $this; } @@ -207,14 +203,11 @@ public function flush($path = NULL) { */ public function createDerivative($original_uri, $derivative_uri) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->setSourceUri($original_uri); - if (!$derivative_handler->transformImage()) { - return FALSE; - } - return $derivative_handler->saveImage($derivative_uri); + ->setSourceImageUri($original_uri) + ->setDerivedImageUri($derivative_uri) + ->produceDerivedImage(); } /** @@ -222,12 +215,13 @@ public function createDerivative($original_uri, $derivative_uri) { */ public function transformDimensions(array &$dimensions, $uri) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - $dimensions = $derivative_handler + $pipeline = \Drupal::service('image.processor')->createInstance('core'); + $pipeline ->setImageStyle($this) - ->setSourceUri($uri) - ->setImageDimensions($dimensions) - ->getDerivativeImageDimensions(); + ->setSourceImageUri($uri) + ->setSourceImageDimensions($dimensions['width'] ?? NULL, $dimensions['height'] ?? NULL); + $dimensions['width'] = $pipeline->getDerivedImageWidth(); + $dimensions['height'] = $pipeline->getDerivedImageHeight(); return; } @@ -236,10 +230,10 @@ public function transformDimensions(array &$dimensions, $uri) { */ public function getDerivativeExtension($extension) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - return $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->getDerivativeImageFileExtension($extension); + ->setSourceImageFileExtension($extension) + ->getDerivedImageFileExtension(); } /** @@ -247,10 +241,10 @@ public function getDerivativeExtension($extension) { */ public function getPathToken($uri) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - return $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->getPathToken($uri); + ->setSourceImageUri($uri) + ->getDerivedImageUrlSecurityToken(); } /** @@ -267,11 +261,10 @@ public function deleteImageEffect(ImageEffectInterface $effect) { */ public function supportsUri($uri) { @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); - return $derivative_handler + return \Drupal::service('image.processor')->createInstance('core') ->setImageStyle($this) - ->setSourceUri($uri) - ->isSourceImageFileDerivable(); + ->setSourceImageUri($uri) + ->isSourceImageProcessable(); } /** diff --git a/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php b/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php index f67324da00..582d2acfb1 100644 --- a/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php +++ b/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php @@ -31,7 +31,6 @@ * @ImageProcessPipeline( * id = "core", * description = @Translation("Processes source images through image style configuration to return image derivatives."), - * derivedImageClass = "", * ) */ class Core extends PluginBase implements ImageProcessPipelinePluginInterface { @@ -97,7 +96,12 @@ class Core extends PluginBase implements ImageProcessPipelinePluginInterface { * * @var \Drupal\Core\KeyValueStore\MemoryStorage */ - protected $derivativeVariables; + protected $variables; + + /** + * @todo + */ + protected $pipelineState; /** * Constructs a Core plugin. @@ -127,7 +131,7 @@ class Core extends PluginBase implements ImageProcessPipelinePluginInterface { */ public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageFactory $image_factory, StreamWrapperManagerInterface $stream_wrapper_manager, PrivateKey $private_key, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, RequestStack $request_stack, LoggerInterface $logger, FileSystemInterface $file_system) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->derivativeVariables = new MemoryStorage('image_derivative_variables'); + $this->variables = new MemoryStorage('image_pipeline_variables'); $this->imageFactory = $image_factory; $this->streamWrapperManager = $stream_wrapper_manager; $this->privateKey = $private_key->get(); @@ -158,71 +162,297 @@ public static function create(ContainerInterface $container, array $configuratio } /** - * {@inheritdoc} + * Sets a variable to a specified value. + * + * @param string $variable + * The variable to set. + * @param mixed $value + * The value to set. + * + * @return $this */ - public function setVariable($variable, $value) { - $this->derivativeVariables->set($variable, $value); + public function setVariable(string $variable, $value): self { + $this->variables->set($variable, $value); return $this; } /** - * {@inheritdoc} + * Returns the value of a variable. + * + * @return mixed + * The value of the variable. + * + * @throws \RuntimeException + * If the variable is not set. */ - public function getVariable($variable) { - if (!$this->hasVariable($variable)) { + public function getVariable(string $variable) { + if (!$this->variables->has($variable)) { throw new \RuntimeException("Variable {$variable} not set"); } - return $this->derivativeVariables->get($variable); + return $this->variables->get($variable); } /** - * {@inheritdoc} + * Returns whether a variable is set. + * + * @return bool + * TRUE if the variable is set, FALSE otherwise. */ - public function hasVariable($variable) { - return $this->derivativeVariables->has($variable); + public function hasVariable(string $variable): bool { + return $this->variables->has($variable); } /** - * {@inheritdoc} + * */ - public function setSourceUri($uri) { - $this->setVariable('sourceUri', $uri); + public function runTask(string $task): void { + if (isset($this->pipelineState[$task])) { + switch ($task) { + case 'done': + return; + + case 'processing': + throw new \RuntimeException("$task is already in process"); + + } + } + $this->pipelineState[$task] = 'processing'; + + try { + $this->$task(); + } + catch (\RuntimeException $e) { + throw new \RuntimeException("Failed '$task': " . $e->getMessage(), $e->getCode(), $e); + } + + $this->pipelineState[$task] = 'done'; + } + + /** + * Sets the 'sourceImageUri' variable. + * + * @param string $uri + * The URI of the source image file. + * + * @return $this + */ + public function setSourceImageUri(string $uri): self { + $this->setVariable('sourceImageUri', $uri); return $this; } /** - * {@inheritdoc} + * + */ + public function setSourceImageFileExtension(string $extension): self { + $this->setVariable('sourceImageFileExtension', $extension); + return $this; + } + + /** + * Sets the 'imageStyle' variable. + * + * @param \Drupal\image\ImageStyleInterface $image_style + * The ImageStyle config entity to use for derivative creation. + * + * @return $this */ - public function setImageStyle(ImageStyleInterface $image_style) { + public function setImageStyle(ImageStyleInterface $image_style): self { $this->setVariable('imageStyle', $image_style); return $this; } /** - * {@inheritdoc} + * Sets the 'image' variable. + * + * @param \Drupal\Core\Image\ImageInterface $image + * The ImageInterface object to be derived. + * + * @return $this */ - public function setImage(ImageInterface $image) { + public function setImage(ImageInterface $image): self { + if (!$this->hasVariable('sourceImageUri')) { + $this->setVariable('sourceImageUri', NULL); + } $this->setVariable('image', $image); return $this; } /** - * {@inheritdoc} + * Sets the 'imageDimensions' variable. + * + * @param array $dimensions + * The starting image dimensions as an associative array with the following + * keys: + * - 'width': Integer with the starting image width. + * - 'height': Integer with the starting image height. + * + * @return $this */ - public function setImageDimensions(array $dimensions) { - $this->setVariable('imageDimensions', $dimensions); + public function setSourceImageDimensions(?int $width, ?int $height): self { + $this->setVariable('sourceImageWidth', $width); + $this->setVariable('sourceImageHeight', $height); return $this; } /** - * {@inheritdoc} + * + */ + public function setDerivedImageUri(string $uri): self { + $this->setVariable('derivedImageUri', $uri); + return $this; + } + + /** + * + */ + public function setDerivedImageCleanUrl(?bool $clean_url): self { + $this->setVariable('derivedImageCleanUrl', $clean_url); + return $this; + } + + /** + * + */ + public function isSourceImageProcessable(): bool { + $this->runTask('determineSourceImageProcessability'); + return $this->getVariable('isSourceImageProcessable'); + } + + /** + * + */ + public function getDerivedImageFileExtension(): string { + $this->runTask('determineDerivedImageFormat'); + return $this->getVariable('derivedImageFileExtension'); + } + + /** + * + */ + public function getDerivedImageWidth(): ?int { + $this->runTask('determineDerivedImageDimensions'); + return $this->getVariable('derivedImageWidth'); + } + + /** + * + */ + public function getDerivedImageHeight(): ?int { + $this->runTask('determineDerivedImageDimensions'); + return $this->getVariable('derivedImageHeight'); + } + + /** + * + */ + public function getDerivedImageUri(): string { + $this->runTask('determineDerivedImageUri'); + return $this->getVariable('derivedImageUri'); + } + + /** + * + */ + public function getDerivedImageUrl(): string { + $this->runTask('determineDerivedImageUrl'); + return $this->getVariable('derivedImageUrl'); + } + + /** + * + */ + public function getDerivedImageUrlSecurityToken(): ?string { + $this->runTask('determineDerivedImageUrlProtection'); + + if (!$this->suppressImageSecurityTokenOutput() && $this->hasVariable('derivedImageUrlProtection')) { + $token_query = $this->getVariable('derivedImageUrlProtection'); + return $token_query[IMAGE_DERIVATIVE_TOKEN]; + } + + return NULL; + } + + /** + * + */ + protected function determineSourceImageFormat(): void { + // Only support the URI if its extension is supported by the current image + // toolkit. + if (!$this->hasVariable('sourceImageFileExtension')) { + $this->setSourceImageFileExtension(mb_strtolower(pathinfo($this->getVariable('sourceImageUri'), PATHINFO_EXTENSION))); + } + } + + /** + * + */ + protected function determineSourceImageProcessability(): void { + $this->runTask('determineSourceImageFormat'); + + $this->setVariable('isSourceImageProcessable', in_array($this->getVariable('sourceImageFileExtension'), $this->imageFactory->getSupportedExtensions())); + } + + /** + * + */ + protected function determineDerivedImageFormat(): void { + $this->runTask('determineSourceImageProcessability'); + + if (!$this->getVariable('isSourceImageProcessable')) { + return; + } + + // Determine derived image file extension. + $extension = $this->getVariable('sourceImageFileExtension'); + foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { + $extension = $effect->getDerivativeExtension($extension); + } + $this->setVariable('derivedImageFileExtension', $extension); + } + + /** + * + */ + protected function determineDerivedImageDimensions(): void { + $this->runTask('determineSourceImageProcessability'); + + // It's still possible to calculate dimensions even if the image at source + // is not processable but we have input dimensions. + if (!$this->getVariable('isSourceImageProcessable') && (!$this->hasVariable('sourceImageWidth') || !$this->hasVariable('sourceImageHeight'))) { + return; + } + + $dimensions = [ + 'width' => $this->getVariable('sourceImageWidth'), + 'height' => $this->getVariable('sourceImageHeight'), + ]; + foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { + $effect->transformDimensions($dimensions, $this->getVariable('sourceImageUri')); + } + $this->setVariable('derivedImageWidth', $dimensions['width']); + $this->setVariable('derivedImageHeight', $dimensions['height']); + } + + /** + * */ - public function buildDerivativeUri() { - $source_scheme = $scheme = $this->fileUriScheme($this->getVariable('sourceUri')); + protected function determineDerivedImageUri(): void { + $this->runTask('determineDerivedImageFormat'); + + if ($this->hasVariable('derivedImageUri')) { + return; + } + if (!$this->getVariable('isSourceImageProcessable')) { + return; + } + + // Determine derived image URI. + $source_scheme = $scheme = $this->fileUriScheme($this->getVariable('sourceImageUri')); $default_scheme = $this->fileDefaultScheme(); if ($source_scheme) { - $path = $this->fileUriTarget($this->getVariable('sourceUri')); + $path = $this->fileUriTarget($this->getVariable('sourceImageUri')); // The scheme of derivative image files only needs to be computed for // source files not stored in the default scheme. if ($source_scheme != $default_scheme) { @@ -239,17 +469,19 @@ public function buildDerivativeUri() { } } else { - $path = $this->getVariable('sourceUri'); + $path = $this->getVariable('sourceImageUri'); $source_scheme = $scheme = $default_scheme; } - return "$scheme://styles/{$this->getVariable('imageStyle')->id()}/$source_scheme/{$this->addExtension($path)}"; + $path = $this->getVariable('derivedImageFileExtension') === $this->getVariable('sourceImageFileExtension') ? $path : $path . '.' . $this->getVariable('derivedImageFileExtension'); + $this->setVariable('derivedImageUri', "$scheme://styles/{$this->getVariable('imageStyle')->id()}/$source_scheme/$path"); } /** - * {@inheritdoc} + * */ - public function buildDerivativeUrl($clean_urls = NULL) { - $derivative_uri = $this->buildDerivativeUri(); + protected function determineDerivedImageUrlProtection(): void { + $this->runTask('determineDerivedImageUri'); + // 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 @@ -260,12 +492,28 @@ public function buildDerivativeUrl($clean_urls = NULL) { // image derivative URL nor checked for in // \Drupal\image\ImageStyleInterface::deliver()). $token_query = []; - if (!$this->configFactory->get('image.settings')->get('suppress_itok_output')) { + if (!$this->suppressImageSecurityTokenOutput()) { // The sourceUri property can be either a relative path or a full URI. - $original_uri = $this->streamWrapperManager->getScheme($this->getVariable('sourceUri')) ? $this->streamWrapperManager->normalizeUri($this->getVariable('sourceUri')) : file_build_uri($this->getVariable('sourceUri')); - $token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)]; + $original_uri_normalised = $this->fileUriScheme($this->getVariable('sourceImageUri')) ? $this->streamWrapperManager->normalizeUri($this->getVariable('sourceImageUri')) : file_build_uri($this->getVariable('sourceImageUri')); + $cryptable_uri = $this->getVariable('derivedImageFileExtension') === $this->getVariable('sourceImageFileExtension') ? $original_uri_normalised : $original_uri_normalised . '.' . $this->getVariable('derivedImageFileExtension'); + // Return the first 8 characters. + $token_query = [IMAGE_DERIVATIVE_TOKEN => substr(Crypt::hmacBase64($this->getVariable('imageStyle')->id() . ':' . $cryptable_uri, $this->privateKey . $this->getHashSalt()), 0, 8)]; + $this->setVariable('derivedImageUrlProtection', $token_query); } + } + + /** + * + */ + protected function determineDerivedImageUrl(): void { + $this->runTask('determineDerivedImageUri'); + $this->runTask('determineDerivedImageUrlProtection'); + $clean_urls = $this->getVariable('derivedImageCleanUrl'); + $derivative_uri = $this->getVariable('derivedImageUri'); + $token_query = $this->hasVariable('derivedImageUrlProtection') ? $this->getVariable('derivedImageUrlProtection') : []; + + // Determine whether clean URLs can be used. if ($clean_urls === NULL) { // Assume clean URLs unless the request tells us otherwise. $clean_urls = TRUE; @@ -283,79 +531,42 @@ public function buildDerivativeUrl($clean_urls = NULL) { // built. if ($clean_urls === FALSE && $this->streamWrapperManager->getScheme($derivative_uri) == 'public' && !file_exists($derivative_uri)) { $directory_path = $this->streamWrapperManager->getViaUri($derivative_uri)->getDirectoryPath(); - return Url::fromUri('base:' . $directory_path . '/' . $this->streamWrapperManager->getTarget($derivative_uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); + $url = Url::fromUri('base:' . $directory_path . '/' . $this->streamWrapperManager->getTarget($derivative_uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); + $this->setVariable('derivedImageUrl', $url); + return; } - $file_url = file_create_url($derivative_uri); // Append the query string with the token, if necessary. + $file_url = file_create_url($derivative_uri); if ($token_query) { $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query); } - return $file_url; + $this->setVariable('derivedImageUrl', $file_url); } /** - * {@inheritdoc} - */ - public function removeSourceUriDerivative() { - $derivative_uri = $this->buildDerivativeUri(); - if (file_exists($derivative_uri)) { - $this->fileSystem->delete($derivative_uri); - } - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function removeAllImageStyleDerivatives() { - // Delete the style directory in each registered wrapper. - $wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); - foreach ($wrappers as $wrapper => $wrapper_data) { - if (file_exists($directory = $wrapper . '://styles/' . $this->getVariable('imageStyle')->id())) { - $this->fileSystem->deleteRecursive($directory); - } - } - - // Let other modules update as necessary on flush. - $this->moduleHandler->invokeAll('image_style_flush', [$this->getVariable('imageStyle')]); - - // Clear caches so that formatters may be added for this style. - drupal_theme_rebuild(); - - Cache::invalidateTags($this->getVariable('imageStyle')->getCacheTagsToInvalidate()); - - return TRUE; - } - - /** - * {@inheritdoc} + * */ - public function transformImage() { - if ($this->hasVariable('sourceUri') && !$this->hasVariable('image')) { + protected function loadSourceImage(): void { + if ($this->hasVariable('sourceImageUri') && !$this->hasVariable('image')) { // If the source file doesn't exist, return FALSE without creating folders. - $image = $this->imageFactory->get($this->getVariable('sourceUri')); + $image = $this->imageFactory->get($this->getVariable('sourceImageUri')); if (!$image->isValid()) { - return FALSE; + return; } $this->setImage($image); } - - // Apply the image effects to the image object. - foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { - $effect->applyEffect($this->getVariable('image')); - } - - return TRUE; } /** - * {@inheritdoc} + * */ - public function saveImage($uri) { + protected function saveDerivedImage(): bool { + $this->runTask('determineDerivedImageUri'); + // Get the folder for the final location of this style. - $directory = $this->fileSystem->dirname($uri); + $directory = $this->fileSystem->dirname($this->getVariable('derivedImageUri')); // Build the destination folder tree if it doesn't already exist. if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { @@ -363,9 +574,9 @@ public function saveImage($uri) { return FALSE; } - if (!$this->getVariable('image')->save($uri)) { - if (file_exists($uri)) { - $this->logger->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $uri]); + if (!$this->getVariable('image')->save($this->getVariable('derivedImageUri'))) { + if (file_exists($this->getVariable('derivedImageUri'))) { + $this->logger->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $this->getVariable('derivedImageUri')]); } return FALSE; } @@ -376,43 +587,49 @@ public function saveImage($uri) { /** * {@inheritdoc} */ - public function getDerivativeImageDimensions() { - $dimensions = $this->getVariable('imageDimensions'); - foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { - $effect->transformDimensions($dimensions, $this->getVariable('sourceUri')); + public function removeDerivedImagesFromStorage(): void { + if ($this->hasVariable('sourceImageUri')) { + // Remove a single image derivative. + $this->runTask('determineDerivedImageUri'); + + if ($this->hasVariable('derivedImageUri')) { + $derivative_uri = $this->getVariable('derivedImageUri'); + if (file_exists($derivative_uri)) { + $this->fileSystem->delete($derivative_uri); + } + } } - $this->setImageDimensions($dimensions); - return $dimensions; - } + else { + // Delete the style directory in each registered wrapper. + $wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); + foreach ($wrappers as $wrapper => $wrapper_data) { + if (file_exists($directory = $wrapper . '://styles/' . $this->getVariable('imageStyle')->id())) { + $this->fileSystem->deleteRecursive($directory); + } + } - /** - * {@inheritdoc} - */ - public function getDerivativeImageFileExtension($extension) { - foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { - $extension = $effect->getDerivativeExtension($extension); + // Let other modules update as necessary on flush. + $this->moduleHandler->invokeAllDeprecated("is deprecated since version 9.x.x and will be removed in y.y.y.", 'image_style_flush', [$this->getVariable('imageStyle')]); + + // Clear caches so that formatters may be added for this style. + drupal_theme_rebuild(); + + Cache::invalidateTags($this->getVariable('imageStyle')->getCacheTagsToInvalidate()); } - return $extension; } /** * {@inheritdoc} */ - public function getPathToken($uri) { - // Return the first 8 characters. - return substr(Crypt::hmacBase64($this->getVariable('imageStyle')->id() . ':' . $this->addExtension($uri), $this->privateKey . $this->getHashSalt()), 0, 8); - } + public function produceDerivedImage(): bool { + $this->loadSourceImage(); - /** - * {@inheritdoc} - */ - public function isSourceImageFileDerivable() { - // Only support the URI if its extension is supported by the current image - // toolkit. - return in_array( - mb_strtolower(pathinfo($this->getVariable('sourceUri'), PATHINFO_EXTENSION)), - $this->imageFactory->getSupportedExtensions() - ); + // Apply the image effects to the image object. + foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { + $effect->applyEffect($this->getVariable('image')); + } + + return $this->saveDerivedImage(); } /** @@ -427,29 +644,6 @@ protected function getHashSalt() { return Settings::getHashSalt(); } - /** - * Adds an extension to a path. - * - * If this image style changes the extension of the derivative, this method - * adds the new extension to the given path. This way we avoid filename - * clashes while still allowing us to find the source image. - * - * @param string $path - * The path to add the extension to. - * - * @return string - * The given path if this image style doesn't change its extension, or the - * path with the added extension if it does. - */ - protected function addExtension($path) { - $original_extension = pathinfo($path, PATHINFO_EXTENSION); - $extension = $this->getDerivativeImageFileExtension($original_extension); - if ($original_extension !== $extension) { - $path .= '.' . $extension; - } - return $path; - } - /** * Provides a wrapper for file_uri_scheme() to allow unit testing. * @@ -495,4 +689,11 @@ protected function fileDefaultScheme() { return $this->configFactory->get('system.file')->get('default_scheme'); } + /** + * + */ + protected function suppressImageSecurityTokenOutput() { + return $this->configFactory->get('image.settings')->get('suppress_itok_output'); + } + } diff --git a/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php b/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php index 3a71b89c17..ce5c324cfe 100644 --- a/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php +++ b/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php @@ -12,80 +12,6 @@ */ interface ImageProcessPipelinePluginInterface extends ContainerFactoryPluginInterface, PluginInspectionInterface { - /** - * Sets a variable to a specified value. - * - * @param string $variable - * The variable to set. - * @param mixed $value - * The value to set. - * - * @return $this - */ - public function setVariable($variable, $value); - - /** - * Returns the value of a variable. - * - * @return mixed - * The value of the variable. - * - * @throws \RuntimeException - * If the variable is not set. - */ - public function getVariable($variable); - - /** - * Returns whether a variable is set. - * - * @return bool - * TRUE if the variable is set, FALSE otherwise. - */ - public function hasVariable($variable); - - /** - * Sets the 'sourceUri' variable. - * - * @param string $uri - * The URI of the source image file. - * - * @return $this - */ - public function setSourceUri($uri); - - /** - * Sets the 'imageStyle' variable. - * - * @param \Drupal\image\ImageStyleInterface $image_style - * The ImageStyle config entity to use for derivative creation. - * - * @return $this - */ - public function setImageStyle(ImageStyleInterface $image_style); - - /** - * Sets the 'image' variable. - * - * @param \Drupal\Core\Image\ImageInterface $image - * The ImageInterface object to be derived. - * - * @return $this - */ - public function setImage(ImageInterface $image); - - /** - * Sets the 'imageDimensions' variable. - * - * @param array $dimensions - * The starting image dimensions as an associative array with the following - * keys: - * - 'width': Integer with the starting image width. - * - 'height': Integer with the starting image height. - * - * @return $this - */ - public function setImageDimensions(array $dimensions); - /** * Returns the URI of the derivative image file. * @@ -97,16 +23,13 @@ public function setImageDimensions(array $dimensions); * @return string * The URI to the image derivative for this style. */ - public function buildDerivativeUri(); + //public function buildDerivativeUri(); /** * Returns the URL of the derivative image file. * * Takes the source URI and the image style to determine the derivative URL. * - * @param mixed $clean_urls - * (optional) Whether clean URLs are in use. - * * @return string * The absolute URL where a style image can be downloaded, suitable for use * in an tag. Requesting the URL will cause the image to be created. @@ -114,7 +37,7 @@ public function buildDerivativeUri(); * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver() * @see file_url_transform_relative() */ - public function buildDerivativeUrl($clean_urls = NULL); + //public function buildDerivativeUrl(); /** * Flushes an image derivative for the specified image style. @@ -126,7 +49,7 @@ public function buildDerivativeUrl($clean_urls = NULL); * TRUE if all the image derivatives were removed from the file system, and * the image style cache tags were invalidated. */ - public function removeSourceUriDerivative(); +// public function removeSourceUriDerivative(); /** * Flushes all image derivatives for the specified image style. @@ -135,7 +58,7 @@ public function removeSourceUriDerivative(); * TRUE if all the image derivatives were removed from the file system, and * the image style cache tags were invalidated. */ - public function removeAllImageStyleDerivatives(); +// public function removeAllImageStyleDerivatives(); /** * Transform an image based on the image style settings. @@ -149,7 +72,7 @@ public function removeAllImageStyleDerivatives(); * * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::saveImage() */ - public function transformImage(); + //public function transformImage(); /** * Saves a transformed image to the derivative URI. @@ -162,7 +85,7 @@ public function transformImage(); * * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::transformImage() */ - public function saveImage($uri); + //public function saveImage($uri); /** * Determines the dimensions of the derivative image. @@ -183,7 +106,7 @@ public function saveImage($uri); * @see ImageEffectInterface::transformDimensions * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::setImageDimensions() */ - public function getDerivativeImageDimensions(); + //public function getDerivativeImageDimensions(); /** * Determines the extension of the derivative image. @@ -195,7 +118,7 @@ public function getDerivativeImageDimensions(); * The extension the derivative image will have, given the extension of the * original. */ - public function getDerivativeImageFileExtension($extension); + //public function getDerivedImageFileExtension($extension); /** * Generates a token to protect an image style derivative. @@ -210,7 +133,7 @@ public function getDerivativeImageFileExtension($extension); * An eight-character token which can be used to protect image style * derivatives against denial-of-service attacks. */ - public function getPathToken($uri); + //public function getPathToken($uri); /** * Determines if the source image at URI can be derived. @@ -221,6 +144,6 @@ public function getPathToken($uri); * @return bool * TRUE if the image is supported, FALSE otherwise. */ - public function isSourceImageFileDerivable(); +// public function isSourceImageProcessable(); } diff --git a/core/modules/image/tests/src/Functional/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php index 2f623f3460..d070bfc4b6 100644 --- a/core/modules/image/tests/src/Functional/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -40,13 +40,11 @@ public function testNormal() { $file = File::create((array) current($this->drupalGetTestFiles('image'))); // Create derivative image. - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); $styles = ImageStyle::loadMultiple(); $style = reset($styles); $original_uri = $file->getFileUri(); - $derivative_uri = $derivative_handler->setImageStyle($style)->setSourceUri($original_uri)->buildDerivativeUri(); - $this->assertTrue($derivative_handler->transformImage()); - $this->assertTrue($derivative_handler->saveImage($derivative_uri)); + $derivative_uri = $style->buildUri($original_uri); + $style->createDerivative($original_uri, $derivative_uri); // Check if derivative image exists. $this->assertTrue(file_exists($derivative_uri), 'Make sure derivative image is generated successfully.'); @@ -78,13 +76,16 @@ public function testBuildDerivativeFromImageObject() { $this->assertNull($image->getFileSize()); // Create derivative. - $derivative_handler = \Drupal::service('image.processor')->createInstance('core'); + $pipeline = \Drupal::service('image.processor')->createInstance('core'); $style = ImageStyle::load('medium'); $desired_filepath = 'public://' . $this->randomMachineName(); $derivative_uri = $desired_filepath . '/test_0.png'; - $derivative_handler->setImageStyle($style)->setImage($image); - $this->assertTrue($derivative_handler->transformImage()); - $this->assertTrue($derivative_handler->saveImage($derivative_uri)); + $pipeline + ->setImageStyle($style) + ->setImage($image) + ->setSourceImageFileExtension('png') + ->setDerivedImageUri($derivative_uri); + $this->assertTrue($pipeline->produceDerivedImage()); // Check if derivative image exists. $this->assertTrue(file_exists($derivative_uri)); diff --git a/core/modules/image/tests/src/Kernel/ImageStyleTest.php b/core/modules/image/tests/src/Kernel/ImageStyleTest.php index 3051e0ebad..fef43a4998 100644 --- a/core/modules/image/tests/src/Kernel/ImageStyleTest.php +++ b/core/modules/image/tests/src/Kernel/ImageStyleTest.php @@ -41,6 +41,8 @@ protected function setUp() { ]); $this->imageStyle->addImageEffect(['id' => 'image_module_test_null']); $this->imageStyle->save(); + + \Drupal::service('file_system')->copy($this->root . '/core/misc/druplicon.png', 'public://test.png'); } /** @@ -48,7 +50,7 @@ protected function setUp() { * @expectedDeprecation The Drupal\image\Entity\ImageStyle::buildUri method is deprecated since version 9.x.x and will be removed in y.y.y. */ public function testBuildUri() { - $this->assertNotEmpty($this->imageStyle->buildUri('public://test.png')); + $this->assertSame('public://styles/test/public/test.png', $this->imageStyle->buildUri('public://test.png')); } /** @@ -56,7 +58,7 @@ public function testBuildUri() { * @expectedDeprecation The Drupal\image\Entity\ImageStyle::buildUrl method is deprecated since version 9.x.x and will be removed in y.y.y. */ public function testBuildUrl() { - $this->assertNotEmpty($this->imageStyle->buildUrl('public://test.png')); + $this->assertContains('files/styles/test/public/test.png?itok=', $this->imageStyle->buildUrl('public://test.png')); } /** @@ -82,6 +84,10 @@ public function testCreateDerivative() { public function testTransformDimensions() { $dimensions = ['width' => 100, 'height' => 200]; $this->assertNull($this->imageStyle->transformDimensions($dimensions, 'public://test.png')); + $this->assertEquals([ + 'width' => NULL, + 'height' => NULL, + ], $dimensions); } /** @@ -89,7 +95,7 @@ public function testTransformDimensions() { * @expectedDeprecation The Drupal\image\Entity\ImageStyle::getDerivativeExtension method is deprecated since version 9.x.x and will be removed in y.y.y. */ public function testGetDerivativeExtension() { - $this->assertNotEmpty($this->imageStyle->getDerivativeExtension('png')); + $this->assertSame('png', $this->imageStyle->getDerivativeExtension('png')); } /** @@ -105,7 +111,7 @@ public function testGetPathToken() { * @expectedDeprecation The Drupal\image\Entity\ImageStyle::supportsUri method is deprecated since version 9.x.x and will be removed in y.y.y. */ public function testSupportsUri() { - $this->assertInternalType('bool', $this->imageStyle->supportsUri('public://test.png')); + $this->assertTrue($this->imageStyle->supportsUri('public://test.png')); } } diff --git a/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php b/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php index b956283749..1a80427559 100644 --- a/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php +++ b/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php @@ -6,10 +6,14 @@ use Drupal\image\Entity\ImageStyle; use Drupal\image\ImageEffectManager; +if (!defined('IMAGE_DERIVATIVE_TOKEN')) { + define('IMAGE_DERIVATIVE_TOKEN', 'itok'); +} + /** * @coversDefaultClass \Drupal\image\Plugin\ImageProcessPipeline\Core * - * @group Image + * @group image */ class CoreImageProcessPipelinePluginTest extends UnitTestCase { @@ -65,7 +69,7 @@ protected function getCoreImageProcessPipelinePluginMock($stubs = []) { ->getMock(); $image_factory->expects($this->any()) ->method('getSupportedExtensions') - ->will($this->returnValue(['gif', 'png'])); + ->will($this->returnValue(['gif', 'png', 'jpeg'])); $private_key = $this->getMockBuilder('\Drupal\Core\PrivateKey') ->disableOriginalConstructor() @@ -79,8 +83,9 @@ protected function getCoreImageProcessPipelinePluginMock($stubs = []) { 'fileUriScheme', 'fileUriTarget', 'fileDefaultScheme', + 'suppressImageSecurityTokenOutput', ]; - $derivative_handler = $this->getMockBuilder('\Drupal\image\Plugin\ImageProcessPipeline\Core') + $pipeline = $this->getMockBuilder('\Drupal\image\Plugin\ImageProcessPipeline\Core') ->setConstructorArgs([ [], 'core', @@ -96,26 +101,29 @@ protected function getCoreImageProcessPipelinePluginMock($stubs = []) { ]) ->setMethods(array_merge($default_stubs, $stubs)) ->getMock(); - $derivative_handler->expects($this->any()) + $pipeline->expects($this->any()) ->method('getHashSalt') ->will($this->returnValue('testHashSalt')); - $derivative_handler->expects($this->any()) + $pipeline->expects($this->any()) ->method('fileDefaultScheme') ->will($this->returnValue('public')); - $derivative_handler->expects($this->any()) + $pipeline->expects($this->any()) ->method('fileUriScheme') ->will($this->returnCallback([$this, 'fileUriScheme'])); - $derivative_handler->expects($this->any()) + $pipeline->expects($this->any()) ->method('fileUriTarget') ->will($this->returnCallback([$this, 'fileUriTarget'])); + $pipeline->expects($this->any()) + ->method('suppressImageSecurityTokenOutput') + ->will($this->returnValue(FALSE)); - return $derivative_handler; + return $pipeline; } /** - * @covers ::buildDerivativeUri + * @covers ::getDerivedImageUri */ - public function testBuildDerivativeUri() { + public function testGetDerivedImageUri() { // Image style that changes the extension. $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') @@ -126,9 +134,9 @@ public function testBuildDerivativeUri() { ->will($this->returnValue('png')); $image_style = $this->getImageStyleMock($image_effect); - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setImageStyle($image_style)->setSourceUri('public://test.jpeg'); - $this->assertEquals('public://styles/testImageStyleEntityId/public/test.jpeg.png', $derivative_handler->buildDerivativeUri()); + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline->setImageStyle($image_style)->setSourceImageUri('public://test.jpeg'); + $this->assertEquals('public://styles/testImageStyleEntityId/public/test.jpeg.png', $pipeline->getDerivedImageUri()); // Image style that doesn't change the extension. $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); @@ -140,15 +148,15 @@ public function testBuildDerivativeUri() { ->will($this->returnArgument(0)); $image_style = $this->getImageStyleMock($image_effect); - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setImageStyle($image_style)->setSourceUri('public://test.jpeg'); - $this->assertEquals('public://styles/testImageStyleEntityId/public/test.jpeg', $derivative_handler->buildDerivativeUri()); + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline->setImageStyle($image_style)->setSourceImageUri('public://test.jpeg'); + $this->assertEquals('public://styles/testImageStyleEntityId/public/test.jpeg', $pipeline->getDerivedImageUri()); } /** - * @covers ::getDerivativeImageFileExtension + * @covers ::getDerivedImageFileExtension */ - public function testGetDerivativeImageFileExtension() { + public function testGetDerivedImageFileExtension() { // Image style that changes the extension. $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') @@ -161,18 +169,21 @@ public function testGetDerivativeImageFileExtension() { // The mock ImageEffect::getDerivativeExtension method is always returning // 'png'. - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setImageStyle($image_style); $extensions = ['jpeg', 'gif', 'png']; foreach ($extensions as $extension) { - $this->assertSame('png', $derivative_handler->getDerivativeImageFileExtension($extension)); + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline + ->setSourceImageFileExtension($extension) + ->setImageStyle($image_style); + $this->assertSame('png', $pipeline->getDerivedImageFileExtension()); } } /** - * @covers ::getDerivativeImageDimensions + * @covers ::getDerivedImageWidth + * @covers ::getDerivedImageHeight */ - public function testGetDerivativeImageDimensions() { + public function testGetDerivedImageDimensions() { $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') ->setConstructorArgs([[], 'testEffectId', [], $logger]) @@ -184,31 +195,34 @@ public function testGetDerivativeImageDimensions() { // The mock ImageEffect::transformDimensions method is halving each // dimension. - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline ->setImageStyle($image_style) - ->setSourceUri('noUri') - ->setImageDimensions(['width' => 100, 'height' => 200]); - $this->assertSame(['width' => 50, 'height' => 100], $derivative_handler->getDerivativeImageDimensions()); + ->setSourceImageUri('noUri') + ->setSourceImageDimensions(100, 200); + $this->assertSame(50, $pipeline->getDerivedImageWidth()); + $this->assertSame(100, $pipeline->getDerivedImageHeight()); } /** - * @covers ::isSourceImageFileDerivable + * @covers ::isSourceImageProcessable */ - public function testIsSourceImageFileDerivable() { + public function testIsSourceImageProcessable() { // The mock ImageFactory::getSupportedExtensions() method is returning // ['gif', 'png']. - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setSourceUri('bingo.png'); - $this->assertTrue($derivative_handler->isSourceImageFileDerivable()); - $derivative_handler->setSourceUri('bingo.svg'); - $this->assertFalse($derivative_handler->isSourceImageFileDerivable()); + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline->setSourceImageUri('bingo.png'); + $this->assertTrue($pipeline->isSourceImageProcessable()); + + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline->setSourceImageUri('bingo.svg'); + $this->assertFalse($pipeline->isSourceImageProcessable()); } /** - * @covers ::getPathToken + * @covers ::getDerivedImageUrlSecurityToken */ - public function testGetPathToken() { + public function testGetDerivedImageUrlSecurityToken() { // Image style that changes the extension. $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') @@ -219,11 +233,13 @@ public function testGetPathToken() { ->will($this->returnValue('png')); $image_style = $this->getImageStyleMock($image_effect); - // Assert the extension has been added to the URI before creating the token. - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setImageStyle($image_style); - $this->assertSame($derivative_handler->getPathToken('public://test.jpeg.png'), $derivative_handler->getPathToken('public://test.jpeg')); - $this->assertSame('rD82Rz5j', $derivative_handler->getPathToken('public://test.jpeg')); + // Assert the extension has been added. + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline + ->setSourceImageUri('public://test.jpeg') + ->setImageStyle($image_style); + $this->assertSame('R2xWQ5uR', $pipeline->getDerivedImageUrlSecurityToken()); + $this->assertSame('public://styles/testImageStyleEntityId/public/test.jpeg.png', $pipeline->getDerivedImageUri()); // Image style that doesn't change the extension. $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); @@ -235,11 +251,13 @@ public function testGetPathToken() { ->will($this->returnArgument(0)); $image_style = $this->getImageStyleMock($image_effect); - // Assert no extension has been added to the URI before creating the token. - $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); - $derivative_handler->setImageStyle($image_style); - $this->assertNotEquals($derivative_handler->getPathToken('public://test.jpeg.png'), $derivative_handler->getPathToken('public://test.jpeg')); - $this->assertSame('sbDHSih4', $derivative_handler->getPathToken('public://test.jpeg')); + // Assert no extension has been added. + $pipeline = $this->getCoreImageProcessPipelinePluginMock(); + $pipeline + ->setSourceImageUri('public://test.jpeg') + ->setImageStyle($image_style); + $this->assertSame('1U1MYok-', $pipeline->getDerivedImageUrlSecurityToken()); + $this->assertSame('public://styles/testImageStyleEntityId/public/test.jpeg', $pipeline->getDerivedImageUri()); } /**