diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml index 2f17bb5c8e..90ff836794 100644 --- a/core/modules/image/image.services.yml +++ b/core/modules/image/image.services.yml @@ -4,6 +4,9 @@ services: arguments: ['@stream_wrapper_manager'] tags: - { name: path_processor_inbound, priority: 300 } + plugin.manager.image_derivative: + class: Drupal\image\Plugin\ImageDerivativePluginManager + parent: default_plugin_manager plugin.manager.image.effect: class: Drupal\image\ImageEffectManager parent: default_plugin_manager diff --git a/core/modules/image/src/Annotation/ImageDerivative.php b/core/modules/image/src/Annotation/ImageDerivative.php new file mode 100644 index 0000000000..633ef6268b --- /dev/null +++ b/core/modules/image/src/Annotation/ImageDerivative.php @@ -0,0 +1,32 @@ +fileUriScheme($uri); - $default_scheme = $this->fileDefaultScheme(); - - if ($source_scheme) { - $path = $this->fileUriTarget($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 = $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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->createInstance('core'); + return $derivative_handler + ->setImageStyle($this) + ->setSourceUri($uri) + ->buildDerivativeUri(); } /** * {@inheritdoc} */ public function buildUrl($path, $clean_urls = NULL) { - $uri = $this->buildUri($path); - // 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 = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($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 && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { - $directory_path = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath(); - return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->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. + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->createInstance('core'); + $derivative_handler->setImageStyle($this); if (isset($path)) { - $derivative_uri = $this->buildUri($path); - if (file_exists($derivative_uri)) { - file_unmanaged_delete($derivative_uri); - } - 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())) { - file_unmanaged_delete_recursive($directory); - } + 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; } @@ -282,60 +205,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()) { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->createInstance('core'); + $derivative_handler + ->setImageStyle($this) + ->setSourceUri($original_uri); + if (!$derivative_handler->transformImage()) { return FALSE; } - - // Get the folder for the final location of this style. - $directory = drupal_dirname($derivative_uri); - - // Build the destination folder tree if it doesn't already exist. - if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { - \Drupal::logger('image')->error('Failed to create style directory: %directory', ['%directory' => $directory]); - 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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->createInstance('core'); + return $derivative_handler + ->setImageStyle($this) + ->getPathToken($uri); } /** @@ -351,12 +265,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 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->createInstance('core'); + return $derivative_handler + ->setImageStyle($this) + ->setSourceUri($uri) + ->isSourceImageFileDerivable(); } /** @@ -432,8 +346,11 @@ protected function getImageEffectPluginManager() { * * @return \Drupal\Core\Image\ImageFactory * The image factory. + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function getImageFactory() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return \Drupal::service('image.factory'); } @@ -442,8 +359,11 @@ protected function getImageFactory() { * * @return string * The Drupal private key. + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function getPrivateKey() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return \Drupal::service('private_key')->get(); } @@ -454,8 +374,11 @@ protected function getPrivateKey() { * A salt based on information in settings.php, not in the database. * * @throws \RuntimeException + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function getHashSalt() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return Settings::getHashSalt(); } @@ -472,8 +395,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 8.x.x and will be removed in 9.0.0. */ protected function addExtension($path) { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); $original_extension = pathinfo($path, PATHINFO_EXTENSION); $extension = $this->getDerivativeExtension($original_extension); if ($original_extension !== $extension) { @@ -492,13 +418,14 @@ protected function addExtension($path) { * * @see file_uri_target() * - * @todo: Remove when https://www.drupal.org/node/2050759 is in. - * * @return string * A string containing the name of the scheme, or FALSE if none. For * example, the URI "public://example.txt" would return "public". + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function fileUriScheme($uri) { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return file_uri_scheme($uri); } @@ -512,14 +439,15 @@ protected function fileUriScheme($uri) { * * @see file_uri_scheme() * - * @todo: Convert file_uri_target() into a proper injectable service. - * * @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". + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function fileUriTarget($uri) { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return file_uri_target($uri); } @@ -528,12 +456,13 @@ protected function fileUriTarget($uri) { * * Gets the default file stream implementation. * - * @todo: Convert file_default_scheme() into a proper injectable service. - * * @return string * 'public', 'private' or any other file scheme defined as the default. + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ protected function fileDefaultScheme() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return file_default_scheme(); } @@ -543,9 +472,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 8.x.x and will be removed in 9.0.0. */ protected function getStreamWrapperManager() { + @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.x.x and will be removed in 9.0.0.', E_USER_DEPRECATED); return \Drupal::service('stream_wrapper_manager'); } diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index 8430fdc2f7..8ab8c12e49 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -54,6 +54,8 @@ public function setName($name); * * @return string * The URI to the image derivative for this style. + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ public function buildUri($uri); @@ -71,6 +73,8 @@ public function buildUri($uri); * * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver() * @see file_url_transform_relative() + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ public function buildUrl($path, $clean_urls = NULL); @@ -86,6 +90,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 8.x.x and will be removed in 9.0.0. */ public function getPathToken($uri); @@ -97,6 +103,8 @@ public function getPathToken($uri); * image derivative will be flushed. * * @return $this + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ public function flush($path = NULL); @@ -114,6 +122,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 8.x.x and will be removed in 9.0.0. */ public function createDerivative($original_uri, $derivative_uri); @@ -138,6 +148,8 @@ public function createDerivative($original_uri, $derivative_uri); * performance. * * @see ImageEffectInterface::transformDimensions + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ public function transformDimensions(array &$dimensions, $uri); @@ -150,6 +162,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 8.x.x and will be removed in 9.0.0. */ public function getDerivativeExtension($extension); @@ -201,6 +215,8 @@ public function deleteImageEffect(ImageEffectInterface $effect); * * @return bool * TRUE if the image is supported, FALSE otherwise. + * + * @deprecated since version 8.x.x and will be removed in 9.0.0. */ public function supportsUri($uri); diff --git a/core/modules/image/src/Plugin/ImageDerivative/Core.php b/core/modules/image/src/Plugin/ImageDerivative/Core.php new file mode 100644 index 0000000000..8b154a784b --- /dev/null +++ b/core/modules/image/src/Plugin/ImageDerivative/Core.php @@ -0,0 +1,504 @@ +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 = $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 = file_uri_scheme($this->getVariable('sourceUri')) ? file_stream_wrapper_uri_normalize($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 && file_uri_scheme($derivative_uri) == 'public' && !file_exists($derivative_uri)) { + $directory_path = $this->streamWrapperManager->getViaUri($derivative_uri)->getDirectoryPath(); + return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($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)) { + file_unmanaged_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())) { + file_unmanaged_delete_recursive($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 (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_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". + * + * @see file_uri_target() + * + * @todo: Remove when https://www.drupal.org/node/2050759 is in. + * + * @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 file_uri_scheme($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". + * + * @see file_uri_scheme() + * + * @todo: Convert file_uri_target() into a proper injectable service. + * + * @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 file_uri_target($uri); + } + + /** + * Provides a wrapper for file_default_scheme() to allow unit testing. + * + * Gets the default file stream implementation. + * + * @todo: Convert file_default_scheme() into a proper injectable service. + * + * @return string + * 'public', 'private' or any other file scheme defined as the default. + */ + protected function fileDefaultScheme() { + return file_default_scheme(); + } + +} diff --git a/core/modules/image/src/Plugin/ImageDerivativePluginInterface.php b/core/modules/image/src/Plugin/ImageDerivativePluginInterface.php new file mode 100644 index 0000000000..d6a4d8e294 --- /dev/null +++ b/core/modules/image/src/Plugin/ImageDerivativePluginInterface.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\ImageDerivativePluginInterface::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\ImageDerivativePluginInterface::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\ImageDerivativePluginInterface::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/src/Plugin/ImageDerivativePluginManager.php b/core/modules/image/src/Plugin/ImageDerivativePluginManager.php new file mode 100644 index 0000000000..f33e4fdf8b --- /dev/null +++ b/core/modules/image/src/Plugin/ImageDerivativePluginManager.php @@ -0,0 +1,30 @@ +alterInfo('image_derivative_plugin_info'); + $this->setCacheBackend($cache_backend, 'image_derivative_plugins'); + } + +} diff --git a/core/modules/image/tests/src/Functional/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php index 3cf3be71b8..31906ac549 100644 --- a/core/modules/image/tests/src/Functional/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -34,11 +34,13 @@ public function testNormal() { $file = File::create((array) current($this->drupalGetTestFiles('image'))); // Create derivative image. + $derivative_handler = \Drupal::service('plugin.manager.image_derivative')->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.'); @@ -55,4 +57,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('plugin.manager.image_derivative')->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..a4948dc06e --- /dev/null +++ b/core/modules/image/tests/src/Kernel/ImageStyleTest.php @@ -0,0 +1,110 @@ +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 8.x.x and will be removed in 9.0.0. + */ + public function testBuildUri() { + $this->imageStyle->buildUri('public://test.png'); + } + + /** + * @covers ::buildUrl + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::buildUrl method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testBuildUrl() { + $this->imageStyle->buildUrl('public://test.png'); + } + + /** + * @covers ::flush + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::flush method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testFlush() { + $this->imageStyle->flush(); + } + + /** + * @covers ::createDerivative + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::createDerivative method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testCreateDerivative() { + $this->imageStyle->createDerivative('public://test.png', 'public://test_derivative.png'); + } + + /** + * @covers ::transformDimensions + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::transformDimensions method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testTransformDimensions() { + $dimensions = ['width' => 100, 'height' => 200]; + $this->imageStyle->transformDimensions($dimensions, 'public://test.png'); + } + + /** + * @covers ::getDerivativeExtension + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::getDerivativeExtension method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testGetDerivativeExtension() { + $this->imageStyle->getDerivativeExtension('png'); + } + + /** + * @covers ::getPathToken + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::getPathToken method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testGetPathToken() { + $this->imageStyle->getPathToken('public://test.png'); + } + + /** + * @covers ::supportsUri + * @expectedDeprecation The Drupal\image\Entity\ImageStyle::supportsUri method is deprecated since version 8.x.x and will be removed in 9.0.0. + */ + public function testSupportsUri() { + $this->imageStyle->supportsUri('public://test.png'); + } + +} diff --git a/core/modules/image/tests/src/Unit/CoreImageDerivativePluginTest.php b/core/modules/image/tests/src/Unit/CoreImageDerivativePluginTest.php new file mode 100644 index 0000000000..031ebf5d76 --- /dev/null +++ b/core/modules/image/tests/src/Unit/CoreImageDerivativePluginTest.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 ImageDerivativePlugin for testing. + * + * @return \Drupal\image\ImageStyleInterface + * The mocked image style. + */ + protected function getCoreImageDerivativePluginMock($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\ImageDerivative\Core') + ->setConstructorArgs([ + [], + 'core', + [], + $image_factory, + $this->getMock('\Drupal\Core\StreamWrapper\StreamWrapperManagerInterface'), + $private_key, + $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'), + $this->getMock('\Drupal\Core\Config\ConfigFactoryInterface'), + $this->getMock('\Symfony\Component\HttpFoundation\RequestStack'), + $this->getMock('\Psr\Log\LoggerInterface'), + $this->getMock('\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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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->getCoreImageDerivativePluginMock(); + $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 bc954b3cc4..0000000000 --- a/core/modules/image/tests/src/Unit/ImageStyleTest.php +++ /dev/null @@ -1,237 +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('fileUriScheme') - ->will($this->returnCallback([$this, 'fileUriScheme'])); - $image_style->expects($this->any()) - ->method('fileUriTarget') - ->will($this->returnCallback([$this, 'fileUriTarget'])); - $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->getMock('\Drupal\Core\Entity\EntityTypeInterface'); - $this->entityType->expects($this->any()) - ->method('getProvider') - ->will($this->returnValue($this->provider)); - $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); - $this->entityManager->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::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; - } - - /** - * 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 873dae5123..79766cf8a4 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php @@ -124,6 +124,14 @@ public static function getSkippedDeprecations() { 'Drupal\taxonomy\Tests\TaxonomyTranslationTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait', 'Drupal\basic_auth\Tests\BasicAuthTestTrait is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait instead. See https://www.drupal.org/node/2862800.', 'Drupal\taxonomy\Tests\TaxonomyTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait', + 'The Drupal\image\Entity\ImageStyle::buildUri method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::buildUrl method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::getPathToken method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::flush method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::createDerivative method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::transformDimensions method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::getDerivativeExtension method is deprecated since version 8.x.x and will be removed in 9.0.0.', + 'The Drupal\image\Entity\ImageStyle::supportsUri method is deprecated since version 8.x.x and will be removed in 9.0.0.', 'Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "/system-test/Ȅchȏ/meφΩ/{text}".', 'Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "/somewhere/{item}/over/the/קainbow".', 'Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "/place/meφω".',