diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml index 2f17bb5c8e..bbddf3ee8b 100644 --- a/core/modules/image/image.services.yml +++ b/core/modules/image/image.services.yml @@ -4,6 +4,11 @@ services: arguments: ['@stream_wrapper_manager'] tags: - { name: path_processor_inbound, priority: 300 } + image.processor: + class: Drupal\image\ImageProcessor + parent: default_plugin_manager + tags: + - { name: plugin_manager_cache_clear } plugin.manager.image.effect: class: Drupal\image\ImageEffectManager parent: default_plugin_manager diff --git a/core/modules/image/src/Annotation/ImageProcessPipeline.php b/core/modules/image/src/Annotation/ImageProcessPipeline.php new file mode 100644 index 0000000000..26ad974e44 --- /dev/null +++ b/core/modules/image/src/Annotation/ImageProcessPipeline.php @@ -0,0 +1,39 @@ +fileDefaultScheme(); - - if ($source_scheme) { - $path = StreamWrapperManager::getTarget($uri); - // 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) { - $class = $this->getStreamWrapperManager()->getClass($source_scheme); - $is_writable = NULL; - if ($class) { - $is_writable = $class::getType() & StreamWrapperInterface::WRITE; - } - - // Compute the derivative URI scheme. Derivatives created from writable - // source stream wrappers will inherit the scheme. Derivatives created - // from read-only stream wrappers will fall-back to the default scheme. - $scheme = $is_writable ? $source_scheme : $default_scheme; - } - } - else { - $path = $uri; - $source_scheme = $scheme = $default_scheme; - } - return "$scheme://styles/{$this->id()}/$source_scheme/{$this->addExtension($path)}"; + @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 + ->setImageStyle($this) + ->setSourceUri($uri) + ->buildDerivativeUri(); } /** * {@inheritdoc} */ public function buildUrl($path, $clean_urls = NULL) { - $uri = $this->buildUri($path); - - /** @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')) { - // The passed $path variable can be either a relative path or a full URI. - $original_uri = $stream_wrapper_manager::getScheme($path) ? $stream_wrapper_manager->normalizeUri($path) : file_build_uri($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(); - } - - $file_url = file_create_url($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; + @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 + ->setImageStyle($this) + ->setSourceUri($path) + ->buildDerivativeUrl($clean_urls); } /** * {@inheritdoc} */ public function flush($path = NULL) { - // A specific image path has been provided. Flush only that derivative. - /** @var \Drupal\Core\File\FileSystemInterface $file_system */ - $file_system = \Drupal::service('file_system'); + @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); if (isset($path)) { - $derivative_uri = $this->buildUri($path); - if (file_exists($derivative_uri)) { - try { - $file_system->delete($derivative_uri); - } - catch (FileException $e) { - // Ignore failed deletes. - } - } - return $this; + // A specific image path has been provided. Flush only that derivative. + $derivative_handler->setSourceUri($path)->removeSourceUriDerivative(); } - - // Delete the style directory in each registered wrapper. - $wrappers = $this->getStreamWrapperManager()->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); - foreach ($wrappers as $wrapper => $wrapper_data) { - if (file_exists($directory = $wrapper . '://styles/' . $this->id())) { - try { - $file_system->deleteRecursive($directory); - } - catch (FileException $e) { - // Ignore failed deletes. - } - } + else { + // Flush all the derivatives for this image style. + $derivative_handler->removeAllImageStyleDerivatives(); } - - // Let other modules update as necessary on flush. - $module_handler = \Drupal::moduleHandler(); - $module_handler->invokeAll('image_style_flush', [$this]); - - // Clear caches so that formatters may be added for this style. - drupal_theme_rebuild(); - - Cache::invalidateTags($this->getCacheTagsToInvalidate()); - return $this; } @@ -305,60 +206,51 @@ public function flush($path = NULL) { * {@inheritdoc} */ public function createDerivative($original_uri, $derivative_uri) { - // If the source file doesn't exist, return FALSE without creating folders. - $image = $this->getImageFactory()->get($original_uri); - if (!$image->isValid()) { - return FALSE; - } - - // Get the folder for the final location of this style. - $directory = \Drupal::service('file_system')->dirname($derivative_uri); - - // Build the destination folder tree if it doesn't already exist. - if (!\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { - \Drupal::logger('image')->error('Failed to create style directory: %directory', ['%directory' => $directory]); + @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) + ->setSourceUri($original_uri); + if (!$derivative_handler->transformImage()) { return FALSE; } - - foreach ($this->getEffects() as $effect) { - $effect->applyEffect($image); - } - - if (!$image->save($derivative_uri)) { - if (file_exists($derivative_uri)) { - \Drupal::logger('image')->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $derivative_uri]); - } - return FALSE; - } - - return TRUE; + return $derivative_handler->saveImage($derivative_uri); } /** * {@inheritdoc} */ public function transformDimensions(array &$dimensions, $uri) { - foreach ($this->getEffects() as $effect) { - $effect->transformDimensions($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 + ->setImageStyle($this) + ->setSourceUri($uri) + ->setImageDimensions($dimensions) + ->getDerivativeImageDimensions(); + return; } /** * {@inheritdoc} */ public function getDerivativeExtension($extension) { - foreach ($this->getEffects() as $effect) { - $extension = $effect->getDerivativeExtension($extension); - } - return $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 + ->setImageStyle($this) + ->getDerivativeImageFileExtension($extension); } /** * {@inheritdoc} */ public function getPathToken($uri) { - // Return the first 8 characters. - return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8); + @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 + ->setImageStyle($this) + ->getPathToken($uri); } /** @@ -374,12 +266,12 @@ public function deleteImageEffect(ImageEffectInterface $effect) { * {@inheritdoc} */ public function supportsUri($uri) { - // Only support the URI if its extension is supported by the current image - // toolkit. - return in_array( - mb_strtolower(pathinfo($uri, PATHINFO_EXTENSION)), - $this->getImageFactory()->getSupportedExtensions() - ); + @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 + ->setImageStyle($this) + ->setSourceUri($uri) + ->isSourceImageFileDerivable(); } /** @@ -455,8 +347,11 @@ protected function getImageEffectPluginManager() { * * @return \Drupal\Core\Image\ImageFactory * The image factory. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function getImageFactory() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); return \Drupal::service('image.factory'); } @@ -465,8 +360,11 @@ protected function getImageFactory() { * * @return string * The Drupal private key. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function getPrivateKey() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); return \Drupal::service('private_key')->get(); } @@ -477,8 +375,11 @@ protected function getPrivateKey() { * A salt based on information in settings.php, not in the database. * * @throws \RuntimeException + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function getHashSalt() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); return Settings::getHashSalt(); } @@ -495,8 +396,11 @@ protected function getHashSalt() { * @return string * The given path if this image style doesn't change its extension, or the * path with the added extension if it does. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function addExtension($path) { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); $original_extension = pathinfo($path, PATHINFO_EXTENSION); $extension = $this->getDerivativeExtension($original_extension); if ($original_extension !== $extension) { @@ -512,8 +416,11 @@ protected function addExtension($path) { * * @return string * 'public', 'private' or any other file scheme defined as the default. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function fileDefaultScheme() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); return \Drupal::config('system.file')->get('default_scheme'); } @@ -523,9 +430,10 @@ protected function fileDefaultScheme() { * @return \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface * The stream wrapper manager service * - * @todo Properly inject this service in Drupal 9.0.x. + * @deprecated since version 9.x.x and will be removed in y.y.y. */ protected function getStreamWrapperManager() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 9.x.x and will be removed in y.y.y.', E_USER_DEPRECATED); return \Drupal::service('stream_wrapper_manager'); } diff --git a/core/modules/image/src/ImageProcessor.php b/core/modules/image/src/ImageProcessor.php new file mode 100644 index 0000000000..5b39895b65 --- /dev/null +++ b/core/modules/image/src/ImageProcessor.php @@ -0,0 +1,31 @@ +alterInfo('image_process_pipeline_plugin_info'); + $this->setCacheBackend($cache_backend, 'image_process_pipeline_plugins'); + } + +} diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index d3306e5d15..1006de7988 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -40,6 +40,8 @@ public function setName($name); * * @return string * The URI to the image derivative for this style. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function buildUri($uri); @@ -57,6 +59,8 @@ public function buildUri($uri); * * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver() * @see file_url_transform_relative() + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function buildUrl($path, $clean_urls = NULL); @@ -72,6 +76,8 @@ public function buildUrl($path, $clean_urls = NULL); * @return string * An eight-character token which can be used to protect image style * derivatives against denial-of-service attacks. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function getPathToken($uri); @@ -83,6 +89,8 @@ public function getPathToken($uri); * image derivative will be flushed. * * @return $this + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function flush($path = NULL); @@ -100,6 +108,8 @@ public function flush($path = NULL); * @return bool * TRUE if an image derivative was generated, or FALSE if the image * derivative could not be generated. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function createDerivative($original_uri, $derivative_uri); @@ -124,6 +134,8 @@ public function createDerivative($original_uri, $derivative_uri); * performance. * * @see ImageEffectInterface::transformDimensions + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function transformDimensions(array &$dimensions, $uri); @@ -136,6 +148,8 @@ public function transformDimensions(array &$dimensions, $uri); * @return string * The extension the derivative image will have, given the extension of the * original. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function getDerivativeExtension($extension); @@ -187,6 +201,8 @@ public function deleteImageEffect(ImageEffectInterface $effect); * * @return bool * TRUE if the image is supported, FALSE otherwise. + * + * @deprecated since version 9.x.x and will be removed in y.y.y. */ public function supportsUri($uri); diff --git a/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php b/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php new file mode 100644 index 0000000000..f67324da00 --- /dev/null +++ b/core/modules/image/src/Plugin/ImageProcessPipeline/Core.php @@ -0,0 +1,498 @@ +derivativeVariables = new MemoryStorage('image_derivative_variables'); + $this->imageFactory = $image_factory; + $this->streamWrapperManager = $stream_wrapper_manager; + $this->privateKey = $private_key->get(); + $this->moduleHandler = $module_handler; + $this->configFactory = $config_factory; + $this->currentRequest = $request_stack->getCurrentRequest(); + $this->logger = $logger; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('image.factory'), + $container->get('stream_wrapper_manager'), + $container->get('private_key'), + $container->get('module_handler'), + $container->get('config.factory'), + $container->get('request_stack'), + $container->get('logger.channel.image'), + $container->get('file_system') + ); + } + + /** + * {@inheritdoc} + */ + public function setVariable($variable, $value) { + $this->derivativeVariables->set($variable, $value); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVariable($variable) { + if (!$this->hasVariable($variable)) { + throw new \RuntimeException("Variable {$variable} not set"); + } + return $this->derivativeVariables->get($variable); + } + + /** + * {@inheritdoc} + */ + public function hasVariable($variable) { + return $this->derivativeVariables->has($variable); + } + + /** + * {@inheritdoc} + */ + public function setSourceUri($uri) { + $this->setVariable('sourceUri', $uri); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setImageStyle(ImageStyleInterface $image_style) { + $this->setVariable('imageStyle', $image_style); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setImage(ImageInterface $image) { + $this->setVariable('image', $image); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setImageDimensions(array $dimensions) { + $this->setVariable('imageDimensions', $dimensions); + return $this; + } + + /** + * {@inheritdoc} + */ + public function buildDerivativeUri() { + $source_scheme = $scheme = $this->fileUriScheme($this->getVariable('sourceUri')); + $default_scheme = $this->fileDefaultScheme(); + + if ($source_scheme) { + $path = $this->fileUriTarget($this->getVariable('sourceUri')); + // 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) { + $class = $this->streamWrapperManager->getClass($source_scheme); + $is_writable = NULL; + if ($class) { + $is_writable = $class::getType() & StreamWrapperInterface::WRITE; + } + + // Compute the derivative URI scheme. Derivatives created from writable + // source stream wrappers will inherit the scheme. Derivatives created + // from read-only stream wrappers will fall-back to the default scheme. + $scheme = $is_writable ? $source_scheme : $default_scheme; + } + } + else { + $path = $this->getVariable('sourceUri'); + $source_scheme = $scheme = $default_scheme; + } + return "$scheme://styles/{$this->getVariable('imageStyle')->id()}/$source_scheme/{$this->addExtension($path)}"; + } + + /** + * {@inheritdoc} + */ + public function buildDerivativeUrl($clean_urls = NULL) { + $derivative_uri = $this->buildDerivativeUri(); + // 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 (!$this->configFactory->get('image.settings')->get('suppress_itok_output')) { + // 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)]; + } + + if ($clean_urls === NULL) { + // Assume clean URLs unless the request tells us otherwise. + $clean_urls = TRUE; + try { + $clean_urls = RequestHelper::isCleanUrl($this->currentRequest); + } + 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 && $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(); + } + + $file_url = file_create_url($derivative_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 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')) { + // If the source file doesn't exist, return FALSE without creating folders. + $image = $this->imageFactory->get($this->getVariable('sourceUri')); + if (!$image->isValid()) { + return FALSE; + } + $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) { + // Get the folder for the final location of this style. + $directory = $this->fileSystem->dirname($uri); + + // Build the destination folder tree if it doesn't already exist. + if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { + $this->logger->error('Failed to create style directory: %directory', ['%directory' => $directory]); + 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]); + } + return FALSE; + } + + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeImageDimensions() { + $dimensions = $this->getVariable('imageDimensions'); + foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { + $effect->transformDimensions($dimensions, $this->getVariable('sourceUri')); + } + $this->setImageDimensions($dimensions); + return $dimensions; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeImageFileExtension($extension) { + foreach ($this->getVariable('imageStyle')->getEffects() as $effect) { + $extension = $effect->getDerivativeExtension($extension); + } + 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); + } + + /** + * {@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() + ); + } + + /** + * Gets a salt useful for hardening against SQL injection. + * + * @return string + * A salt based on information in settings.php, not in the database. + * + * @throws \RuntimeException + */ + 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. + * + * Returns the scheme of a URI (e.g. a stream). + * + * @param string $uri + * A stream, referenced as "scheme://target" or "data:target". + * + * @return string + * A string containing the name of the scheme, or FALSE if none. For + * example, the URI "public://example.txt" would return "public". + */ + protected function fileUriScheme($uri) { + return $this->streamWrapperManager->getScheme($uri); + } + + /** + * Provides a wrapper for file_uri_target() to allow unit testing. + * + * Returns the part of a URI after the schema. + * + * @param string $uri + * A stream, referenced as "scheme://target" or "data:target". + * + * @return string|bool + * A string containing the target (path), or FALSE if none. + * For example, the URI "public://sample/test.txt" would return + * "sample/test.txt". + */ + protected function fileUriTarget($uri) { + return $this->streamWrapperManager->getTarget($uri); + } + + /** + * Provides a wrapper for file_default_scheme() to allow unit testing. + * + * Gets the default file stream implementation. + * + * @return string + * 'public', 'private' or any other file scheme defined as the default. + */ + protected function fileDefaultScheme() { + return $this->configFactory->get('system.file')->get('default_scheme'); + } + +} diff --git a/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php b/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php new file mode 100644 index 0000000000..3a71b89c17 --- /dev/null +++ b/core/modules/image/src/Plugin/ImageProcessPipelinePluginInterface.php @@ -0,0 +1,226 @@ + tag. Requesting the URL will cause the image to be created. + * + * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver() + * @see file_url_transform_relative() + */ + public function buildDerivativeUrl($clean_urls = NULL); + + /** + * Flushes an image derivative for the specified image style. + * + * Takes the source URI and the image style to determine the derivative to be + * removed. + * + * @return bool + * TRUE if all the image derivatives were removed from the file system, and + * the image style cache tags were invalidated. + */ + public function removeSourceUriDerivative(); + + /** + * Flushes all image derivatives for the specified image style. + * + * @return bool + * TRUE if all the image derivatives were removed from the file system, and + * the image style cache tags were invalidated. + */ + public function removeAllImageStyleDerivatives(); + + /** + * Transform an image based on the image style settings. + * + * Generates an image derivative applying all image effects, without saving + * the result to the file system. Takes the source URI or an ImageInterface + * object and the image style to process the image. + * + * @return bool + * TRUE if the image was transformed, or FALSE in case of failure. + * + * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::saveImage() + */ + public function transformImage(); + + /** + * Saves a transformed image to the derivative URI. + * + * @param string $uri + * Derivative image file URI. + * + * @return bool + * TRUE if the image derivative was saved, or FALSE in case of failure. + * + * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::transformImage() + */ + public function saveImage($uri); + + /** + * Determines the dimensions of the derivative image. + * + * Takes the source URI, the image style, and the starting dimensions to + * determine the expected dimensions of the derivative image. The source URI + * is used to allow effects to optionally use this information to retrieve + * additional image metadata to determine output dimensions. The key + * objective is to calculate derivative image dimensions without performing + * actual image operations, so be aware that performing I/O on the URI may + * lead to decrease in performance. + * The starting dimensions are defined through ::setImageDimensions as an + * associative array. Implementations have to provide values for the + * following keys: + * - 'width': Integer with the starting image width. + * - 'height': Integer with the starting image height. + * + * @see ImageEffectInterface::transformDimensions + * @see \Drupal\image\Plugin\ImageProcessPipelinePluginInterface::setImageDimensions() + */ + public function getDerivativeImageDimensions(); + + /** + * Determines the extension of the derivative image. + * + * @param string $extension + * The file extension of the original image. + * + * @return string + * The extension the derivative image will have, given the extension of the + * original. + */ + public function getDerivativeImageFileExtension($extension); + + /** + * Generates a token to protect an image style derivative. + * + * This prevents unauthorized generation of an image style derivative, + * which can be costly both in CPU time and disk space. + * + * @param string $uri + * The URI of the original image of this style. + * + * @return string + * An eight-character token which can be used to protect image style + * derivatives against denial-of-service attacks. + */ + public function getPathToken($uri); + + /** + * Determines if the source image at URI can be derived. + * + * Takes the source URI and the image style to determine if the image file + * can be loaded, transformed and saved as a derivative image. + * + * @return bool + * TRUE if the image is supported, FALSE otherwise. + */ + public function isSourceImageFileDerivable(); + +} diff --git a/core/modules/image/tests/src/Functional/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php index c2a3a1bbe4..2f623f3460 100644 --- a/core/modules/image/tests/src/Functional/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -40,11 +40,13 @@ 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 = $style->buildUri($original_uri); - $style->createDerivative($original_uri, $derivative_uri); + $derivative_uri = $derivative_handler->setImageStyle($style)->setSourceUri($original_uri)->buildDerivativeUri(); + $this->assertTrue($derivative_handler->transformImage()); + $this->assertTrue($derivative_handler->saveImage($derivative_uri)); // Check if derivative image exists. $this->assertTrue(file_exists($derivative_uri), 'Make sure derivative image is generated successfully.'); @@ -61,4 +63,47 @@ public function testNormal() { $this->assertFalse(file_exists($derivative_uri), 'Make sure derivative image has been flushed.'); } + /** + * Tests creating a derivative from scratch. + */ + public function testBuildDerivativeFromImageObject() { + // Create scratch image. + $image = \Drupal::service('image.factory')->get(); + $this->assertSame('', $image->getSource()); + $this->assertSame('', $image->getMimeType()); + $this->assertNull($image->getFileSize()); + $image->createNew(600, 300, 'png'); + $this->assertSame('', $image->getSource()); + $this->assertSame('image/png', $image->getMimeType()); + $this->assertNull($image->getFileSize()); + + // Create derivative. + $derivative_handler = \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)); + + // Check if derivative image exists. + $this->assertTrue(file_exists($derivative_uri)); + + // Check derivative image after saving, with old object. + $this->assertSame(220, $image->getWidth()); + $this->assertSame(110, $image->getHeight()); + $this->assertSame($derivative_uri, $image->getSource()); + $this->assertSame('image/png', $image->getMimeType()); + $file_size = $image->getFileSize(); + $this->assertGreaterThan(0, $file_size); + + // Check derivative image after reloading from saved image file. + $image_r = \Drupal::service('image.factory')->get($derivative_uri); + $this->assertSame(220, $image_r->getWidth()); + $this->assertSame(110, $image_r->getHeight()); + $this->assertSame($derivative_uri, $image_r->getSource()); + $this->assertSame('image/png', $image_r->getMimeType()); + $this->assertSame($file_size, $image_r->getFileSize()); + } + } diff --git a/core/modules/image/tests/src/Kernel/ImageStyleTest.php b/core/modules/image/tests/src/Kernel/ImageStyleTest.php new file mode 100644 index 0000000000..3051e0ebad --- /dev/null +++ b/core/modules/image/tests/src/Kernel/ImageStyleTest.php @@ -0,0 +1,111 @@ +imageStyle = ImageStyle::create([ + 'name' => 'test', + ]); + $this->imageStyle->addImageEffect(['id' => 'image_module_test_null']); + $this->imageStyle->save(); + } + + /** + * @covers ::buildUri + * @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')); + } + + /** + * @covers ::buildUrl + * @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')); + } + + /** + * @covers ::flush + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::flush method is deprecated since version 9.x.x and will be removed in y.y.y. + */ + public function testFlush() { + $this->assertInstanceOf(ImageStyleInterface::class, $this->imageStyle->flush()); + } + + /** + * @covers ::createDerivative + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::createDerivative method is deprecated since version 9.x.x and will be removed in y.y.y. + */ + public function testCreateDerivative() { + $this->assertInternalType('bool', $this->imageStyle->createDerivative('public://test.png', 'public://test_derivative.png')); + } + + /** + * @covers ::transformDimensions + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::transformDimensions method is deprecated since version 9.x.x and will be removed in y.y.y. + */ + public function testTransformDimensions() { + $dimensions = ['width' => 100, 'height' => 200]; + $this->assertNull($this->imageStyle->transformDimensions($dimensions, 'public://test.png')); + } + + /** + * @covers ::getDerivativeExtension + * @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')); + } + + /** + * @covers ::getPathToken + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::getPathToken method is deprecated since version 9.x.x and will be removed in y.y.y. + */ + public function testGetPathToken() { + $this->assertNotEmpty($this->imageStyle->getPathToken('public://test.png')); + } + + /** + * @covers ::supportsUri + * @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')); + } + +} diff --git a/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php b/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php new file mode 100644 index 0000000000..b956283749 --- /dev/null +++ b/core/modules/image/tests/src/Unit/CoreImageProcessPipelinePluginTest.php @@ -0,0 +1,277 @@ +getMockBuilder(ImageEffectManager::class) + ->disableOriginalConstructor() + ->getMock(); + $effectManager->expects($this->any()) + ->method('createInstance') + ->with('testEffectId') + ->will($this->returnValue($image_effect)); + + $default_stubs = [ + 'getImageEffectPluginManager', + 'id', + ]; + $image_style = $this->getMockBuilder(ImageStyle::class) + ->setConstructorArgs([ + ['effects' => ['testEffectId' => ['id' => 'testEffectId']]], + 'testImageStyleEntityId', + ]) + ->setMethods(array_merge($default_stubs, $stubs)) + ->getMock(); + + $image_style->expects($this->any()) + ->method('getImageEffectPluginManager') + ->will($this->returnValue($effectManager)); + $image_style->expects($this->any()) + ->method('id') + ->will($this->returnValue('testImageStyleEntityId')); + + return $image_style; + } + + /** + * Gets a mocked Core ImageProcessPipelinePlugin for testing. + * + * @return \Drupal\image\ImageStyleInterface + * The mocked image style. + */ + protected function getCoreImageProcessPipelinePluginMock($stubs = []) { + $image_factory = $this->getMockBuilder('\Drupal\Core\Image\ImageFactory') + ->disableOriginalConstructor() + ->getMock(); + $image_factory->expects($this->any()) + ->method('getSupportedExtensions') + ->will($this->returnValue(['gif', 'png'])); + + $private_key = $this->getMockBuilder('\Drupal\Core\PrivateKey') + ->disableOriginalConstructor() + ->getMock(); + $private_key->expects($this->once()) + ->method('get') + ->will($this->returnValue('testPrivateKey')); + + $default_stubs = [ + 'getHashSalt', + 'fileUriScheme', + 'fileUriTarget', + 'fileDefaultScheme', + ]; + $derivative_handler = $this->getMockBuilder('\Drupal\image\Plugin\ImageProcessPipeline\Core') + ->setConstructorArgs([ + [], + 'core', + [], + $image_factory, + $this->createMock('\Drupal\Core\StreamWrapper\StreamWrapperManagerInterface'), + $private_key, + $this->createMock('\Drupal\Core\Extension\ModuleHandlerInterface'), + $this->createMock('\Drupal\Core\Config\ConfigFactoryInterface'), + $this->createMock('\Symfony\Component\HttpFoundation\RequestStack'), + $this->createMock('\Psr\Log\LoggerInterface'), + $this->createMock('\Drupal\Core\File\FileSystemInterface'), + ]) + ->setMethods(array_merge($default_stubs, $stubs)) + ->getMock(); + $derivative_handler->expects($this->any()) + ->method('getHashSalt') + ->will($this->returnValue('testHashSalt')); + $derivative_handler->expects($this->any()) + ->method('fileDefaultScheme') + ->will($this->returnValue('public')); + $derivative_handler->expects($this->any()) + ->method('fileUriScheme') + ->will($this->returnCallback([$this, 'fileUriScheme'])); + $derivative_handler->expects($this->any()) + ->method('fileUriTarget') + ->will($this->returnCallback([$this, 'fileUriTarget'])); + + return $derivative_handler; + } + + /** + * @covers ::buildDerivativeUri + */ + public function testBuildDerivativeUri() { + // Image style that changes the extension. + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('getDerivativeExtension') + ->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()); + + // Image style that doesn't change the extension. + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('getDerivativeExtension') + ->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()); + } + + /** + * @covers ::getDerivativeImageFileExtension + */ + public function testGetDerivativeImageFileExtension() { + // Image style that changes the extension. + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('getDerivativeExtension') + ->will($this->returnValue('png')); + $image_style = $this->getImageStyleMock($image_effect); + + // 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)); + } + } + + /** + * @covers ::getDerivativeImageDimensions + */ + public function testGetDerivativeImageDimensions() { + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('transformDimensions') + ->will($this->returnCallback([$this, 'transformDimensions'])); + $image_style = $this->getImageStyleMock($image_effect); + + // The mock ImageEffect::transformDimensions method is halving each + // dimension. + $derivative_handler = $this->getCoreImageProcessPipelinePluginMock(); + $derivative_handler + ->setImageStyle($image_style) + ->setSourceUri('noUri') + ->setImageDimensions(['width' => 100, 'height' => 200]); + $this->assertSame(['width' => 50, 'height' => 100], $derivative_handler->getDerivativeImageDimensions()); + } + + /** + * @covers ::isSourceImageFileDerivable + */ + public function testIsSourceImageFileDerivable() { + // 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()); + } + + /** + * @covers ::getPathToken + */ + public function testGetPathToken() { + // Image style that changes the extension. + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('getDerivativeExtension') + ->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')); + + // Image style that doesn't change the extension. + $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); + $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') + ->setConstructorArgs([[], 'testEffectId', [], $logger]) + ->getMock(); + $image_effect->expects($this->any()) + ->method('getDerivativeExtension') + ->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')); + } + + /** + * Mock function for ImageEffect::transformDimensions(). + */ + public function transformDimensions(array &$dimensions, $uri) { + $dimensions['width'] /= 2; + $dimensions['height'] /= 2; + } + + /** + * Mock function for ImageStyle::fileUriScheme(). + */ + public function fileUriScheme($uri) { + if (preg_match('/^([\w\-]+):\/\/|^(data):/', $uri, $matches)) { + // The scheme will always be the last element in the matches array. + return array_pop($matches); + } + + return FALSE; + } + + /** + * Mock function for ImageStyle::fileUriTarget(). + */ + public function fileUriTarget($uri) { + // Remove the scheme from the URI and remove erroneous leading or trailing, + // forward-slashes and backslashes. + $target = trim(preg_replace('/^[\w\-]+:\/\/|^data:/', '', $uri), '\/'); + + // If nothing was replaced, the URI doesn't have a valid scheme. + return $target !== $uri ? $target : FALSE; + } + +} diff --git a/core/modules/image/tests/src/Unit/ImageStyleTest.php b/core/modules/image/tests/src/Unit/ImageStyleTest.php deleted file mode 100644 index f02e3eeff4..0000000000 --- a/core/modules/image/tests/src/Unit/ImageStyleTest.php +++ /dev/null @@ -1,207 +0,0 @@ -getMockBuilder('\Drupal\image\ImageEffectManager') - ->disableOriginalConstructor() - ->getMock(); - $effectManager->expects($this->any()) - ->method('createInstance') - ->with($image_effect_id) - ->will($this->returnValue($image_effect)); - $default_stubs = [ - 'getImageEffectPluginManager', - 'fileUriScheme', - 'fileUriTarget', - 'fileDefaultScheme', - ]; - $image_style = $this->getMockBuilder('\Drupal\image\Entity\ImageStyle') - ->setConstructorArgs([ - ['effects' => [$image_effect_id => ['id' => $image_effect_id]]], - $this->entityTypeId, - ]) - ->setMethods(array_merge($default_stubs, $stubs)) - ->getMock(); - - $image_style->expects($this->any()) - ->method('getImageEffectPluginManager') - ->will($this->returnValue($effectManager)); - $image_style->expects($this->any()) - ->method('fileDefaultScheme') - ->will($this->returnCallback([$this, 'fileDefaultScheme'])); - - return $image_style; - } - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->entityTypeId = $this->randomMachineName(); - $this->provider = $this->randomMachineName(); - $this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface'); - $this->entityType->expects($this->any()) - ->method('getProvider') - ->will($this->returnValue($this->provider)); - $this->entityTypeManager = $this->createMock('\Drupal\Core\Entity\EntityTypeManagerInterface'); - $this->entityTypeManager->expects($this->any()) - ->method('getDefinition') - ->with($this->entityTypeId) - ->will($this->returnValue($this->entityType)); - } - - /** - * @covers ::getDerivativeExtension - */ - public function testGetDerivativeExtension() { - $image_effect_id = $this->randomMachineName(); - $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); - $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') - ->setConstructorArgs([[], $image_effect_id, [], $logger]) - ->getMock(); - $image_effect->expects($this->any()) - ->method('getDerivativeExtension') - ->will($this->returnValue('png')); - - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect); - - $extensions = ['jpeg', 'gif', 'png']; - foreach ($extensions as $extension) { - $extensionReturned = $image_style->getDerivativeExtension($extension); - $this->assertEquals($extensionReturned, 'png'); - } - } - - /** - * @covers ::buildUri - */ - public function testBuildUri() { - // Image style that changes the extension. - $image_effect_id = $this->randomMachineName(); - $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); - $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') - ->setConstructorArgs([[], $image_effect_id, [], $logger]) - ->getMock(); - $image_effect->expects($this->any()) - ->method('getDerivativeExtension') - ->will($this->returnValue('png')); - - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect); - $this->assertEquals($image_style->buildUri('public://test.jpeg'), 'public://styles/' . $image_style->id() . '/public/test.jpeg.png'); - - // Image style that doesn't change the extension. - $image_effect_id = $this->randomMachineName(); - $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') - ->setConstructorArgs([[], $image_effect_id, [], $logger]) - ->getMock(); - $image_effect->expects($this->any()) - ->method('getDerivativeExtension') - ->will($this->returnArgument(0)); - - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect); - $this->assertEquals($image_style->buildUri('public://test.jpeg'), 'public://styles/' . $image_style->id() . '/public/test.jpeg'); - } - - /** - * @covers ::getPathToken - */ - public function testGetPathToken() { - $logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock(); - $private_key = $this->randomMachineName(); - $hash_salt = $this->randomMachineName(); - - // Image style that changes the extension. - $image_effect_id = $this->randomMachineName(); - $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') - ->setConstructorArgs([[], $image_effect_id, [], $logger]) - ->getMock(); - $image_effect->expects($this->any()) - ->method('getDerivativeExtension') - ->will($this->returnValue('png')); - - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, ['getPrivateKey', 'getHashSalt']); - $image_style->expects($this->any()) - ->method('getPrivateKey') - ->will($this->returnValue($private_key)); - $image_style->expects($this->any()) - ->method('getHashSalt') - ->will($this->returnValue($hash_salt)); - - // Assert the extension has been added to the URI before creating the token. - $this->assertEquals($image_style->getPathToken('public://test.jpeg.png'), $image_style->getPathToken('public://test.jpeg')); - $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg.png', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); - $this->assertNotEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); - - // Image style that doesn't change the extension. - $image_effect_id = $this->randomMachineName(); - $image_effect = $this->getMockBuilder('\Drupal\image\ImageEffectBase') - ->setConstructorArgs([[], $image_effect_id, [], $logger]) - ->getMock(); - $image_effect->expects($this->any()) - ->method('getDerivativeExtension') - ->will($this->returnArgument(0)); - - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, ['getPrivateKey', 'getHashSalt']); - $image_style->expects($this->any()) - ->method('getPrivateKey') - ->will($this->returnValue($private_key)); - $image_style->expects($this->any()) - ->method('getHashSalt') - ->will($this->returnValue($hash_salt)); - // Assert no extension has been added to the uri before creating the token. - $this->assertNotEquals($image_style->getPathToken('public://test.jpeg.png'), $image_style->getPathToken('public://test.jpeg')); - $this->assertNotEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg.png', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); - $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); - } - - /** - * Mock function for ImageStyle::fileDefaultScheme(). - */ - public function fileDefaultScheme() { - return 'public'; - } - -} diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php index 1870d05ebe..9233453a99 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php @@ -140,6 +140,14 @@ public static function isDeprecationSkipped($message) { */ public static function getSkippedDeprecations() { return [ + 'The Drupal\image\Entity\ImageStyle::buildUri method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::buildUrl method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::getPathToken method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::flush method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::createDerivative method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::transformDimensions method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::getDerivativeExtension method is deprecated since version 9.x.x and will be removed in y.y.y.', + 'The Drupal\image\Entity\ImageStyle::supportsUri method is deprecated since version 9.x.x and will be removed in y.y.y.', 'Passing in arguments the legacy way is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Provide the right parameter names in the method, similar to controllers. See https://www.drupal.org/node/2894819', 'The Symfony\Component\ClassLoader\ApcClassLoader class is deprecated since Symfony 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.', // The following deprecation is not triggered by DrupalCI testing since it