diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php index ce0e283c8efcd6d6c0a0555ddd9ac8a639a68e8e..1fba360804859092715015929993c0e2091e3da1 100644 --- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php @@ -127,8 +127,14 @@ public function apply($operation, array $arguments = []) { $this->logger->error("The selected image handling toolkit '@toolkit' can not process operation '@operation'.", ['@toolkit' => $this->getPluginId(), '@operation' => $operation]); return FALSE; } - catch (\InvalidArgumentException $e) { - $this->logger->warning($e->getMessage(), []); + catch (\Throwable $t) { + $this->logger->warning("The image toolkit '@toolkit' failed processing '@operation' for image '@image'. Reported error: @class - @message", [ + '@toolkit' => $this->getPluginId(), + '@operation' => $operation, + '@image' => $this->getSource(), + '@class' => get_class($t), + '@message' => $t->getMessage(), + ]); return FALSE; } } diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php index dcc3b3d12134ab43ba5f35f10c9267549b4a674e..95b8a9c4f7884687969f5232d4a83eeee1bca928 100644 --- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php @@ -185,6 +185,9 @@ final public function apply(array $arguments) { * * @return bool * TRUE if the operation was performed successfully, FALSE otherwise. + * + * @throws \RuntimeException + * If the operation can not be performed. */ abstract protected function execute(array $arguments); diff --git a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php index 2753273a41327a7476341f9eeb126bcf4d4bb1cc..2abda1bdbecf3e356df0d216672c604c6971b840 100644 --- a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php @@ -180,31 +180,54 @@ protected function load() { return FALSE; } + // Invalidate the image object and return if there's no function to load the + // image file. $function = 'imagecreatefrom' . image_type_to_extension($this->getType(), FALSE); - if (function_exists($function) && $resource = $function($this->getSource())) { - $this->setResource($resource); - if (imageistruecolor($resource)) { - return TRUE; - } - else { - // Convert indexed images to truecolor, copying the image to a new - // truecolor resource, so that filters work correctly and don't result - // in unnecessary dither. - $data = [ - 'width' => imagesx($resource), - 'height' => imagesy($resource), - 'extension' => image_type_to_extension($this->getType(), FALSE), - 'transparent_color' => $this->getTransparentColor(), - 'is_temp' => TRUE, - ]; - if ($this->apply('create_new', $data)) { - imagecopy($this->getResource(), $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource)); - imagedestroy($resource); - } + if (!function_exists($function)) { + $this->logger->error("The image toolkit '@toolkit' can not process image '@image'.", [ + '@toolkit' => $this->getPluginId(), + '@image' => $this->getSource(), + ]); + $this->preLoadInfo = NULL; + return FALSE; + } + + // Invalidate the image object and return if the load fails. + try { + $resource = $function($this->getSource()); + } + catch (\Throwable $t) { + $this->logger->error("The image toolkit '@toolkit' failed loading image '@image'. Reported error: @class - @message", [ + '@toolkit' => $this->getPluginId(), + '@image' => $this->getSource(), + '@class' => get_class($t), + '@message' => $t->getMessage(), + ]); + $this->preLoadInfo = NULL; + return FALSE; + } + + $this->setResource($resource); + if (imageistruecolor($resource)) { + return TRUE; + } + else { + // Convert indexed images to truecolor, copying the image to a new + // truecolor resource, so that filters work correctly and don't result + // in unnecessary dither. + $data = [ + 'width' => imagesx($resource), + 'height' => imagesy($resource), + 'extension' => image_type_to_extension($this->getType(), FALSE), + 'transparent_color' => $this->getTransparentColor(), + 'is_temp' => TRUE, + ]; + if ($this->apply('create_new', $data)) { + imagecopy($this->getResource(), $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource)); + imagedestroy($resource); } - return (bool) $this->getResource(); } - return FALSE; + return (bool) $this->getResource(); } /** @@ -236,7 +259,18 @@ public function save($destination) { return FALSE; } if ($this->getType() == IMAGETYPE_JPEG) { - $success = $function($this->getResource(), $destination, $this->configFactory->get('system.image.gd')->get('jpeg_quality')); + try { + $success = $function($this->getResource(), $destination, $this->configFactory->get('system.image.gd')->get('jpeg_quality')); + } + catch (\Throwable $t) { + $this->logger->error("The image toolkit '@toolkit' failed saving image '@image'. Reported error: @class - @message", [ + '@toolkit' => $this->getPluginId(), + '@image' => $destination, + '@class' => get_class($t), + '@message' => $t->getMessage(), + ]); + $success = FALSE; + } } else { // Image types that support alpha need to be saved accordingly. @@ -244,7 +278,18 @@ public function save($destination) { imagealphablending($this->getResource(), FALSE); imagesavealpha($this->getResource(), TRUE); } - $success = $function($this->getResource(), $destination); + try { + $success = $function($this->getResource(), $destination); + } + catch (\Throwable $t) { + $this->logger->error("The image toolkit '@toolkit' failed saving image '@image'. Reported error: @class - @message", [ + '@toolkit' => $this->getPluginId(), + '@image' => $destination, + '@class' => get_class($t), + '@message' => $t->getMessage(), + ]); + $success = FALSE; + } } // Move temporary local file to remote destination. if (isset($permanent_destination) && $success) { diff --git a/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php b/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php index e4c44674f9a2ee5c10b034b994cc1c5cf846bb5d..aaa3a9aa3123459dd095635329c9a629aff85e1f 100644 --- a/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php +++ b/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php @@ -452,6 +452,39 @@ public function testCreateNewFailures(): void { $this->assertTrue($image->isValid(), 'CreateNew with valid arguments validates the Image.'); } + /** + * Tests creation of an image that will exceed the memory limit. + */ + public function testInsufficientMemory(): void { + // ini_get() may return -1 or null for memory_limit to indicate there is no + // limit set. In that case, we need to skip this test. + $size = ini_get('memory_limit'); + if (!$size || (int) $size === -1) { + $this->markTestSkipped("There is no memory limit set in the PHP environment."); + } + + $image = $this->imageFactory->get('core/tests/fixtures/files/image-test.png'); + + $oldGdImage = $image->getToolkit()->getResource(); + $this->assertFalse($image->createNew(2000000, 2000000)); + $newGdImage = $image->getToolkit()->getResource(); + + // Check that a new resource has not been created, and the old one is still + // valid. + $this->assertEquals($oldGdImage, $newGdImage); + } + + /** + * Tests resizing of an image that will exceed the memory available. + */ + public function testInsufficientAvailableMemory(): void { + $image = $this->imageFactory->get('core/tests/fixtures/files/image-test.png'); + + $memory_in_use = memory_get_usage(TRUE); + ini_set('memory_limit', $memory_in_use + 2048); + $this->assertFalse($image->resize(200000, 200000)); + } + /** * Tests for GIF images with transparency. */