diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php index cb56a76449..834e2c6c31 100644 --- a/core/modules/image/image.api.php +++ b/core/modules/image/image.api.php @@ -38,6 +38,62 @@ function hook_image_style_flush($style) { \Drupal::cache('mymodule')->deleteAll(); } +/** + * Alters the derivative URI. + * + * This hook allows modules to alter the URI of the derivative. By implementing + * this hook, a module can decide, for example, that a specific image style will + * be available in a different stream wrapper. This hook is called when the + * image module builds the URI of an image derivative. + * + * Note: If you use a special URI alteration that stores the image derivatives + * outside of public://styles/{image_style}, private://styles/{image_style} or + * even outside Drupal (for example, on a public image service like Flickr, + * Instagram, etc.), you need to implement your own flush policy. Implementing + * hook_image_style_flush() is a good point where you can define your custom + * flush actions. + * + * @param string $uri + * The image style URI to be altered. + * @param array $context + * A context associative array with next keys and values: + * - scheme: The image scheme. + * - path: The image path. If this image style changes the extension of the + * derivative, the value of 'path' item reflects this. + * - image_style: The \Drupal\image\ImageStyleInterface image style object. + */ +function hook_image_style_uri_alter(&$uri, array $context) { + if (($name = $context['image_style']->id()) === 'watermarked_low_resolution') { + $path = $context['path']; + // Low resolution, watermarked images are available in the public readable + // stream wrapper. + $uri = "public://styles/$name/public/$path"; + } +} + +/** + * Alters the original file URI. + * + * This hook is the opposite of hook_image_style_uri_alter(). It helps to + * restore the original path to file in case the URI of the image derivative + * was altered. + * + * @param string $uri + * The original file URI to be altered. + * @param array $context + * A context associative array with next keys and values: + * - scheme: The image scheme. + * - path: The image path. If this image style changes the extension of the + * derivative, the value of 'path' item reflects this. + * - image_style: The \Drupal\image\ImageStyleInterface image style object. + */ +function hook_image_style_source_alter(&$uri, array $context) { + if ($context['image_style']->id() === 'watermarked_low_resolution') { + // The original file that is used in `watermarked_low_resolution` derivatives + // is actually in private file system, so use it here. + $uri = "private://" . $context['path']; + } +} /** * @} End of "addtogroup hooks". */ diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php index 6ded44fab6..06ea3a59c4 100644 --- a/core/modules/image/src/Controller/ImageStyleDownloadController.php +++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php @@ -157,6 +157,15 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // image exists. $path_info = pathinfo(StreamWrapperManager::getTarget($image_uri)); $converted_image_uri = sprintf('%s://%s%s%s', $this->streamWrapperManager->getScheme($derivative_uri), $path_info['dirname'], DIRECTORY_SEPARATOR, $path_info['filename']); + // Allow modules to alter the URI before it is returned. + // If the image style derivative has different scheme than original file, + // then it should restored in alter hook, so that derivative can be + // created from the source. It is not possible to change the scheme + // earlier, because it that case `getPathToken` will return different + // value from the one given in the url. + $derivative_scheme = $this->streamWrapperManager->getScheme($derivative_uri); + $context = ['scheme' => $derivative_scheme, 'path' => $target, 'image_style' => $image_style]; + $this->moduleHandler()->alter('image_style_source', $converted_image_uri, $context); if (!file_exists($converted_image_uri)) { $this->logger->notice('Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', ['%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri]); return new Response($this->t('Error generating image, missing source file.'), 404); diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index f7edba286b..b26749cd90 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -198,7 +198,16 @@ public function buildUri($uri) { $path = $uri; $source_scheme = $scheme = $default_scheme; } - return "$scheme://styles/{$this->id()}/$source_scheme/{$this->addExtension($path)}"; + // Append a different extension to the image file name, if the image style + // specifies so. + $path = $this->addExtension($path); + $uri = "$scheme://styles/{$this->id()}/$source_scheme/$path"; + + // Allow modules to alter the URI before it is returned. + $context = ['scheme' => $scheme, 'path' => $path, 'image_style' => $this]; + \Drupal::moduleHandler()->alter('image_style_uri', $uri, $context); + + return $uri; } /** diff --git a/core/modules/image/tests/modules/image_module_test/image_module_test.module b/core/modules/image/tests/modules/image_module_test/image_module_test.module index 3fd845cc58..b31c288149 100644 --- a/core/modules/image/tests/modules/image_module_test/image_module_test.module +++ b/core/modules/image/tests/modules/image_module_test/image_module_test.module @@ -38,3 +38,15 @@ function image_module_test_image_effect_info_alter(&$effects) { function image_module_test_image_style_presave(ImageStyleInterface $style) { $style->setThirdPartySetting('image_module_test', 'foo', 'bar'); } + +/** + * Implements hook_image_style_uri_alter(). + */ +function image_module_test_image_style_uri_alter(&$uri, array $context) { + if (($name = $context['image_style']->id()) === 'watermarked_low_resolution') { + $path = $context['path']; + // Low resolution, watermarked images are available publicly, in the + // public:// stream wrapper. + $uri = "public://styles/$name/public/$path"; + } +} diff --git a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php index 6d89a920d8..e5be336e33 100644 --- a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php @@ -326,4 +326,19 @@ public function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_s $this->assertDirectoryDoesNotExist($directory); } + /** + * Tests hook_image_style_uri_alter(). + */ + public function testImageStyleUriAlter() { + $style = ImageStyle::create([ + 'name' => 'watermarked_low_resolution', + 'label' => $this->randomString(), + ]); + $style->save(); + + $original = 'private://bar/baz.png'; + // The 'watermarked_low_resolution' derivatives are available publicly. + $this->assertSame($style->buildUri($original), 'public://styles/watermarked_low_resolution/public/bar/baz.png'); + } + } diff --git a/core/modules/image/tests/src/Unit/ImageStyleTest.php b/core/modules/image/tests/src/Unit/ImageStyleTest.php index 94dcc28979..02f40aaf10 100644 --- a/core/modules/image/tests/src/Unit/ImageStyleTest.php +++ b/core/modules/image/tests/src/Unit/ImageStyleTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\image\Unit; use Drupal\Component\Utility\Crypt; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Tests\UnitTestCase; /** @@ -88,6 +89,15 @@ protected function setUp(): void { ->method('getDefinition') ->with($this->entityTypeId) ->will($this->returnValue($this->entityType)); + $module_handler = $this->getMockBuilder('\Drupal\Core\Extension\ModuleHandler') + ->disableOriginalConstructor() + ->getMock(); + $module_handler->expects($this->any()) + ->method('alter') + ->willReturnArgument(1); + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + $container->set('module_handler', $module_handler); } /**