diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index 9a466a0..3b080af 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -32,11 +32,11 @@ function template_preprocess_image_style_preview(&$variables) { // Set up original file information. $original_path = \Drupal::config('image.settings')->get('preview_image'); - $original_image = $image_factory->get($original_path); + $image = $image_factory->get($original_path); $variables['original'] = array( 'url' => file_create_url($original_path), - 'width' => $original_image->getWidth(), - 'height' => $original_image->getHeight(), + 'width' => $image->getWidth(), + 'height' => $image->getHeight(), ); if ($variables['original']['width'] > $variables['original']['height']) { $variables['preview']['original']['width'] = min($variables['original']['width'], $sample_width); @@ -51,13 +51,16 @@ function template_preprocess_image_style_preview(&$variables) { $preview_file = $style->buildUri($original_path); // Create derivative if necessary. if (!file_exists($preview_file)) { - $style->createDerivativeFromImage($original_image, $preview_file); + $style->applyAndSave($image, $preview_file); } - $preview_image = $image_factory->get($preview_file); + else { + $image = $image_factory->get($preview_file); + } + $image = $image_factory->get($preview_file); $variables['derivative'] = array( 'url' => file_create_url($preview_file), - 'width' => $preview_image->getWidth(), - 'height' => $preview_image->getHeight(), + 'width' => $image->getWidth(), + 'height' => $image->getHeight(), ); if ($variables['derivative']['width'] > $variables['derivative']['height']) { $variables['preview']['derivative']['width'] = min($variables['derivative']['width'], $sample_width); diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php index 1407298..35e8235 100644 --- a/core/modules/image/src/Controller/ImageStyleDownloadController.php +++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php @@ -163,16 +163,23 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st } } - // Try to generate the image, unless another thread just did it while we - // were acquiring the lock. - $success = file_exists($derivative_uri) || $image_style->createDerivative($image_uri, $derivative_uri); + if (file_exists($derivative_uri)) { + // Another thread just generated the derivative image while we were + // acquiring the lock. + $image = $this->imageFactory->get($derivative_uri); + $success = $image->isValid(); + } + else { + // Generate the derivative image. + $image = $this->imageFactory->get($image_uri); + $success = $image_style->applyAndSave($image, $derivative_uri); + } if (!empty($lock_acquired)) { $this->lock->release($lock_name); } if ($success) { - $image = $this->imageFactory->get($derivative_uri); $uri = $image->getSource(); $headers += array( 'Content-Type' => $image->getMimeType(), diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index ea96ff2..e7eb019 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -7,13 +7,18 @@ namespace Drupal\image\Entity; +use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityWithPluginCollectionInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Image\ImageFactory; use Drupal\Core\Image\ImageInterface; +use Drupal\Core\PrivateKey; use Drupal\Core\Routing\RequestHelper; use Drupal\Core\Site\Settings; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\Core\Url; use Drupal\image\ImageEffectPluginCollection; use Drupal\image\ImageEffectInterface; @@ -95,6 +100,68 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity protected $effectsCollection; /** + * The file system service. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + + /** + * The image factory. + * + * @var \Drupal\Core\Image\ImageFactory + */ + protected $imageFactory; + + /** + * The image effect plugin manager. + * + * @var \Drupal\Component\Plugin\PluginManagerInterface + */ + protected $effectManager; + + /** + * The stream wrapper manager. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** + * The private key service. + * + * @var \Drupal\Core\PrivateKey + */ + protected $privateKey; + + /** + * Constructs an ImageStyle object. + * + * @param array $values + * An array of values to set, keyed by property name. + * @param string $entity_type + * The type of the entity to create. + * @param \Drupal\Core\File\FileSystemInterface $file_system + * The file system service. + * @param \Drupal\Core\Image\ImageFactory $image_factory + * The image factory. + * @param \Drupal\Component\Plugin\PluginManagerInterface $image_effect_plugin_manager + * The image effect plugin manager. + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * The stream wrapper manager. + * @param \Drupal\Core\PrivateKey $private_key + * The private key service. + */ + public function __construct(array $values, $entity_type, FileSystemInterface $file_system = NULL, ImageFactory $image_factory = NULL, PluginManagerInterface $image_effect_plugin_manager = NULL, StreamWrapperManagerInterface $stream_wrapper_manager = NULL, PrivateKey $private_key = NULL) { + parent::__construct($values, $entity_type); + $this->fileSystem = $file_system ?: \Drupal::service('file_system'); + $this->imageFactory = $image_factory ?: \Drupal::service('image.factory'); + $this->effectManager = $image_effect_plugin_manager ?: \Drupal::service('plugin.manager.image.effect'); + $this->streamWrapperManager = $stream_wrapper_manager ?: \Drupal::service('stream_wrapper_manager'); + $this->privateKey = $private_key ?: \Drupal::service('private_key'); + } + + /** * Overrides Drupal\Core\Entity\Entity::id(). */ public function id() { @@ -178,7 +245,7 @@ protected static function replaceImageStyle(ImageStyleInterface $style) { * {@inheritdoc} */ public function buildUri($uri) { - $scheme = $this->fileUriScheme($uri); + $scheme = $this->fileSystem->uriScheme($uri); if ($scheme) { $path = $this->fileUriTarget($uri); } @@ -206,7 +273,7 @@ public function buildUrl($path, $clean_urls = NULL) { $token_query = array(); 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); + $original_uri = $this->fileSystem->uriScheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path); $token_query = array(IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)); } @@ -226,8 +293,8 @@ public function buildUrl($path, $clean_urls = NULL) { // 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 = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)->getDirectoryPath(); + if ($clean_urls === FALSE && $this->fileSystem->uriScheme($uri) == 'public' && !file_exists($uri)) { + $directory_path = $this->streamWrapperManager->getViaUri($uri)->getDirectoryPath(); return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query))->toString(); } @@ -254,7 +321,7 @@ public function flush($path = NULL) { } // Delete the style directory in each registered wrapper. - $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); + $wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::WRITE_VISIBLE); foreach ($wrappers as $wrapper => $wrapper_data) { if (file_exists($directory = $wrapper . '://styles/' . $this->id())) { file_unmanaged_delete_recursive($directory); @@ -277,22 +344,27 @@ public function flush($path = NULL) { * {@inheritdoc} */ public function createDerivative($original_uri, $derivative_uri) { - $image = \Drupal::service('image.factory')->get($original_uri); - return $this->createDerivativeFromImage($image, $derivative_uri); + return $this->applyAndSave($this->imageFactory->get($original_uri), $derivative_uri); } /** * {@inheritdoc} */ - public function createDerivativeFromImage(ImageInterface $image, $derivative_uri) { + public function applyAndSave(ImageInterface $image, $derivative_uri) { // If the image doesn't exist or is invalid, return FALSE without creating // folders. if (!$image->isValid()) { return FALSE; } - // Get the folder for the final location of this style. - $directory = drupal_dirname($derivative_uri); + // Apply the style to the image object. Return FALSE without creating + // folders in case of failure. + if (!$this->apply($image)) { + return FALSE; + } + + // Get the folder for the final location of this derivative image. + $directory = $this->fileSystem->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)) { @@ -300,10 +372,7 @@ public function createDerivativeFromImage(ImageInterface $image, $derivative_uri return FALSE; } - foreach ($this->getEffects() as $effect) { - $effect->applyEffect($image); - } - + // Save image file at destination. 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.', array('%destination' => $derivative_uri)); @@ -317,6 +386,23 @@ public function createDerivativeFromImage(ImageInterface $image, $derivative_uri /** * {@inheritdoc} */ + public function apply(ImageInterface $image) { + // If the image doesn't exist or is invalid, return FALSE immediately. + if (!$image->isValid()) { + return FALSE; + } + + // Apply the effects to the image object. + foreach ($this->getEffects() as $effect) { + $effect->applyEffect($image); + } + + return TRUE; + } + + /** + * {@inheritdoc} + */ public function transformDimensions(array &$dimensions, $uri) { foreach ($this->getEffects() as $effect) { $effect->transformDimensions($dimensions, $uri); @@ -338,7 +424,7 @@ public function getDerivativeExtension($extension) { */ public function getPathToken($uri) { // Return the first 8 characters. - return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8); + return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->privateKey->get() . $this->getHashSalt()), 0, 8); } /** @@ -362,7 +448,7 @@ public function getEffect($effect) { */ public function getEffects() { if (!$this->effectsCollection) { - $this->effectsCollection = new ImageEffectPluginCollection($this->getImageEffectPluginManager(), $this->effects); + $this->effectsCollection = new ImageEffectPluginCollection($this->effectManager, $this->effects); $this->effectsCollection->sort(); } return $this->effectsCollection; @@ -407,26 +493,6 @@ public function setName($name) { } /** - * Returns the image effect plugin manager. - * - * @return \Drupal\Component\Plugin\PluginManagerInterface - * The image effect plugin manager. - */ - protected function getImageEffectPluginManager() { - return \Drupal::service('plugin.manager.image.effect'); - } - - /** - * Gets the Drupal private key. - * - * @return string - * The Drupal private key. - */ - protected function getPrivateKey() { - return \Drupal::service('private_key')->get(); - } - - /** * Gets a salt useful for hardening against SQL injection. * * @return string @@ -462,26 +528,6 @@ protected function addExtension($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. diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index 13dd45b..26d9f9d 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -114,14 +114,24 @@ 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 as of Drupal 8.1.0, will be removed in Drupal 9.0.0. + * Instead, get an Image object from the factory and use the + * ::applyAndSave() method on it. + * @code + * $image = \Drupal::service('image.factory')->get($original_uri); + * $image_style->applyAndSave($image, $derivative_uri); + * @endcode + * + * @see \Drupal\image\ImageStyleInterface::applyAndSave() */ public function createDerivative($original_uri, $derivative_uri); /** - * Creates a new image derivative from a source Image object. + * Applies the style to an Image object and saves image to file. * - * Generates an image derivative applying all image effects and saving the - * resulting image. + * Alter the source image applying all image effects and saving the resulting + * image. * * @param \Drupal\Core\Image\ImageInterface $image * An Image object. @@ -129,9 +139,24 @@ public function createDerivative($original_uri, $derivative_uri); * Derivative image file URI. * * @return bool - * TRUE if an image derivative was generated, FALSE otherwise. + * TRUE if the image derivative was saved, FALSE otherwise. + * + * @see \Drupal\image\ImageStyleInterface::apply() + */ + public function applyAndSave(ImageInterface $image, $derivative_uri); + + /** + * Applies the style to an Image object. + * + * Alter the source image applying all image effects. + * + * @param \Drupal\Core\Image\ImageInterface $image + * An Image object. + * + * @return bool + * TRUE if an the image was successfully altered, FALSE otherwise. */ - public function createDerivativeFromImage(ImageInterface $image, $derivative_uri); + public function apply(ImageInterface $image); /** * Determines the dimensions of this image style. diff --git a/core/modules/image/src/Tests/ImageAdminStylesTest.php b/core/modules/image/src/Tests/ImageAdminStylesTest.php index 21080d5..44b45e1 100644 --- a/core/modules/image/src/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/src/Tests/ImageAdminStylesTest.php @@ -393,9 +393,8 @@ public function testFlushUserInterface() { // Create an image to make sure it gets flushed. $files = $this->drupalGetTestFiles('image'); $image_uri = $files[0]->uri; - $image = \Drupal::service('image.factory')->get($image_uri); $derivative_uri = $style->buildUri($image_uri); - $this->assertTrue($style->createDerivativeFromImage($image, $derivative_uri)); + $this->assertTrue($style->applyAndSave(\Drupal::service('image.factory')->get($image_uri), $derivative_uri)); $this->assertEqual($this->getImageCount($style), 1); // Go to image styles list page and check if the flush operation link diff --git a/core/modules/image/tests/src/Unit/ImageStyleTest.php b/core/modules/image/tests/src/Unit/ImageStyleTest.php index 80ce713..1a3b972 100644 --- a/core/modules/image/tests/src/Unit/ImageStyleTest.php +++ b/core/modules/image/tests/src/Unit/ImageStyleTest.php @@ -18,6 +18,13 @@ class ImageStyleTest extends UnitTestCase { /** + * The ID of the type of the entity under test. + * + * @var string + */ + protected $entityTypeId; + + /** * The entity type used for testing. * * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject @@ -32,11 +39,32 @@ class ImageStyleTest extends UnitTestCase { protected $entityManager; /** - * The ID of the type of the entity under test. + * The file system used for testing. * - * @var string + * @var \Drupal\Core\File\FileSystemInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $entityTypeId; + protected $fileSystem; + + /** + * The image factory used for testing. + * + * @var \Drupal\Core\Image\ImageFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $imageFactory; + + /** + * The stream wrapper manager used for testing. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $streamWrapperManager; + + /** + * The private key service used for testing. + * + * @var \Drupal\Core\PrivateKey|\PHPUnit_Framework_MockObject_MockObject + */ + protected $privateKey; /** * Gets a mocked image style for testing. @@ -57,27 +85,25 @@ protected function getImageStyleMock($image_effect_id, $image_effect, $stubs = a ->method('createInstance') ->with($image_effect_id) ->will($this->returnValue($image_effect)); + $default_stubs = array( - 'getImageEffectPluginManager', - 'fileUriScheme', 'fileUriTarget', 'fileDefaultScheme', ); $image_style = $this->getMockBuilder('\Drupal\image\Entity\ImageStyle') - ->setConstructorArgs(array( + ->setConstructorArgs([ array('effects' => array($image_effect_id => array('id' => $image_effect_id))), $this->entityTypeId, - )) + $this->fileSystem, + $this->imageFactory, + $effectManager, + $this->streamWrapperManager, + $this->privateKey, + ]) ->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(array($this, 'fileUriScheme'))); - $image_style->expects($this->any()) ->method('fileUriTarget') ->will($this->returnCallback(array($this, 'fileUriTarget'))); $image_style->expects($this->any()) @@ -97,11 +123,33 @@ public function setUp() { $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)); + + $mock_builder = $this->getMockBuilder('\Drupal\Core\File\FileSystem'); + $this->fileSystem = $mock_builder + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + + $mock_builder = $this->getMockBuilder('\Drupal\Core\Image\ImageFactory'); + $this->imageFactory = $mock_builder + ->disableOriginalConstructor() + ->getMock(); + + $mock_builder = $this->getMockBuilder('\Drupal\Core\StreamWrapper\StreamWrapperManagerInterface'); + $this->streamWrapperManager = $mock_builder + ->disableOriginalConstructor() + ->getMock(); + + $mock_builder = $this->getMockBuilder('\Drupal\Core\PrivateKey'); + $this->privateKey = $mock_builder + ->disableOriginalConstructor() + ->getMock(); } /** @@ -173,9 +221,9 @@ public function testGetPathToken() { ->method('getDerivativeExtension') ->will($this->returnValue('png')); - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getPrivateKey', 'getHashSalt')); - $image_style->expects($this->any()) - ->method('getPrivateKey') + $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getHashSalt')); + $this->privateKey->expects($this->any()) + ->method('get') ->will($this->returnValue($private_key)); $image_style->expects($this->any()) ->method('getHashSalt') @@ -195,9 +243,9 @@ public function testGetPathToken() { ->method('getDerivativeExtension') ->will($this->returnArgument(0)); - $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getPrivateKey', 'getHashSalt')); - $image_style->expects($this->any()) - ->method('getPrivateKey') + $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getHashSalt')); + $this->privateKey->expects($this->any()) + ->method('get') ->will($this->returnValue($private_key)); $image_style->expects($this->any()) ->method('getHashSalt') @@ -209,18 +257,6 @@ public function testGetPathToken() { } /** - * 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) {