From 7370aa77323190eee2a3ebc31367d2e3e74ef0cb Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Mon, 16 Sep 2013 19:49:59 +0300
Subject: [PATCH] Issue #2066219 by claudiu.cristea: Decouple image type from
 image extension.

---
 core/lib/Drupal/Core/Image/Image.php               | 34 ++++++++++++++++++++--
 core/lib/Drupal/Core/Image/ImageInterface.php      | 17 +++++++++++
 core/modules/file/file.module                      | 12 ++++++--
 core/modules/image/image.field.inc                 |  2 +-
 core/modules/image/image.module                    |  2 +-
 .../image/Plugin/field/field_type/ImageItem.php    |  2 +-
 .../system/Plugin/ImageToolkit/GDToolkit.php       | 27 +++++++++--------
 .../Drupal/system/Plugin/ImageToolkitInterface.php | 33 +++++++++++++--------
 .../Drupal/system/Tests/Image/ToolkitGdTest.php    |  6 ++--
 .../image_test/Plugin/ImageToolkit/TestToolkit.php | 12 ++++++--
 core/tests/Drupal/Tests/Core/Image/ImageTest.php   |  8 +++++
 11 files changed, 117 insertions(+), 38 deletions(-)

diff --git a/core/lib/Drupal/Core/Image/Image.php b/core/lib/Drupal/Core/Image/Image.php
index a515fe4..0f2fc85 100644
--- a/core/lib/Drupal/Core/Image/Image.php
+++ b/core/lib/Drupal/Core/Image/Image.php
@@ -63,7 +63,14 @@ class Image implements ImageInterface {
   protected $extension = '';
 
   /**
-   * MIME type ('image/jpeg', 'image/gif', 'image/png').
+   * Image type represented by a PHP IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG).
+   *
+   * @var int
+   */
+  protected $type;
+
+  /**
+   * MIME type (e.g. 'image/jpeg', 'image/gif', 'image/png').
    *
    * @var string
    */
@@ -99,6 +106,13 @@ public function __construct($source, ImageToolkitInterface $toolkit) {
   /**
    * {@inheritdoc}
    */
+  public function isSupported() {
+    return in_array($this->getType(), $this->toolkit->supportedTypes());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getExtension() {
     $this->processInfo();
     return $this->extension;
@@ -147,6 +161,14 @@ public function getFileSize() {
   /**
    * {@inheritdoc}
    */
+  public function getType() {
+    $this->processInfo();
+    return $this->type;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getMimeType() {
     $this->processInfo();
     return $this->mimeType;
@@ -245,9 +267,17 @@ protected function processInfo() {
     if ($details = $this->toolkit->getInfo($this)) {
       $this->height = $details['height'];
       $this->width = $details['width'];
-      $this->extension = $details['extension'];
+      $this->type = $details['type'];
       $this->mimeType = $details['mime_type'];
       $this->fileSize = filesize($destination);
+      $this->extension = pathinfo($destination, PATHINFO_EXTENSION);
+
+      // It may be a temporary file, without extension, or an image created from
+      // an image resource. Fallback to default extension for this image type.
+      if (empty($this->extension)) {
+        $this->extension = image_type_to_extension($this->type, FALSE);
+      }
+
       $this->processed = TRUE;
     }
     return TRUE;
diff --git a/core/lib/Drupal/Core/Image/ImageInterface.php b/core/lib/Drupal/Core/Image/ImageInterface.php
index b83c578..5966ab2 100644
--- a/core/lib/Drupal/Core/Image/ImageInterface.php
+++ b/core/lib/Drupal/Core/Image/ImageInterface.php
@@ -13,6 +13,14 @@
 interface ImageInterface {
 
   /**
+   * Checks if the image format is supported.
+   *
+   * @return bool
+   *   Returns TRUE if the image format is supported by the toolkit.
+   */
+  public function isSupported();
+
+  /**
    * Returns the extension of the image file.
    *
    * @return string
@@ -65,6 +73,15 @@ public function setWidth($width);
   public function getFileSize();
 
   /**
+   * Returns the type of the image.
+   *
+   * @return int
+   *   The image type represented by a PHP IMAGETYPE_* constant (e.g.
+   *   IMAGETYPE_JPEG).
+   */
+  public function getType();
+
+  /**
    * Returns the MIME type of the image file.
    *
    * @return string
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 68b33cf..65b3ca0 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -7,6 +7,7 @@
 
 use Drupal\file\Entity\File;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Template\Attribute;
 use Drupal\file\FileUsage\FileUsageInterface;
@@ -411,8 +412,13 @@ function file_validate_is_image(File $file) {
   $errors = array();
 
   $image = \Drupal::service('image.factory')->get($file->getFileUri());
-  if (!$image->getExtension()) {
-    $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
+  if (!$image->isSupported()) {
+    $toolkit = \Drupal::service('image.toolkit');
+    $extensions = array();
+    foreach ($toolkit->supportedTypes() as $image_type) {
+      $extensions[] = Unicode::strtoupper(image_type_to_extension($image_type));
+    }
+    $errors[] = t('Image type not supported. Allowed types: @types.', array('@types' => implode(', ', $extensions)));
   }
 
   return $errors;
@@ -447,7 +453,7 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi
   // Check first that the file is an image.
   $image_factory = \Drupal::service('image.factory');
   $image = $image_factory->get($file->getFileUri());
-  if ($image->getExtension()) {
+  if ($image->isSupported()) {
     if ($maximum_dimensions) {
       // Check that it is smaller than the given dimensions.
       list($width, $height) = explode('x', $maximum_dimensions);
diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc
index 1111b8a..4e26f4d 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -41,7 +41,7 @@ function image_field_widget_process($element, &$form_state, $form) {
     }
     else {
       $image = \Drupal::service('image.factory')->get($file->getFileUri());
-      if ($image->getExtension()) {
+      if ($image->isSupported()) {
         $variables['width'] = $image->getWidth();
         $variables['height'] = $image->getHeight();
       }
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index b93bc53..aa13940 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -242,7 +242,7 @@ function image_file_download($uri) {
 
     // Check that the file exists and is an image.
     $image = \Drupal::service('image.factory')->get($uri);
-    if ($image->getExtension()) {
+    if ($image->isSupported()) {
       // Check the permissions of the original to grant access to this image.
       $headers = \Drupal::moduleHandler()->invokeAll('file_download', array($original_uri));
       // Confirm there's at least one module granting access and none denying access.
diff --git a/core/modules/image/lib/Drupal/image/Plugin/field/field_type/ImageItem.php b/core/modules/image/lib/Drupal/image/Plugin/field/field_type/ImageItem.php
index a6d8972..6073377 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/field/field_type/ImageItem.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/field/field_type/ImageItem.php
@@ -293,7 +293,7 @@ public function preSave() {
     // Determine the dimensions if necessary.
     if (empty($width) || empty($height)) {
       $image = \Drupal::service('image.factory')->get($this->entity->getFileUri());
-      if ($image->getExtension()) {
+      if ($image->isSupported()) {
         $this->width = $image->getWidth();
         $this->height =$image->getHeight();
       }
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
index 3219edb..f8ba4d2 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
@@ -98,7 +98,7 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL) {
 
     // Images are assigned a new color palette when rotating, removing any
     // transparency flags. For GIF images, keep a record of the transparent color.
-    if ($image->getExtension() == 'gif') {
+    if ($image->getType() == IMAGETYPE_GIF) {
       $transparent_index = imagecolortransparent($image->getResource());
       if ($transparent_index != 0) {
         $transparent_gif_color = imagecolorsforindex($image->getResource(), $transparent_index);
@@ -155,8 +155,7 @@ public function desaturate(ImageInterface $image) {
    * {@inheritdoc}
    */
   public function load(ImageInterface $image) {
-    $extension = str_replace('jpg', 'jpeg', $image->getExtension());
-    $function = 'imagecreatefrom' . $extension;
+    $function = 'imagecreatefrom' . image_type_to_extension($image->getType(), FALSE);
     if (function_exists($function) && $resource = $function($image->getSource())) {
       $image->setResource($resource);
       if (!imageistruecolor($resource)) {
@@ -190,17 +189,16 @@ public function save(ImageInterface $image, $destination) {
       $destination = drupal_realpath($destination);
     }
 
-    $extension = str_replace('jpg', 'jpeg', $image->getExtension());
-    $function = 'image' . $extension;
+    $function = 'image' . image_type_to_extension($image->getType(), FALSE);
     if (!function_exists($function)) {
       return FALSE;
     }
-    if ($extension == 'jpeg') {
+    if ($image->getType() == IMAGETYPE_JPEG) {
       $success = $function($image->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality'));
     }
     else {
       // Always save PNG images with full transparency.
-      if ($extension == 'png') {
+      if ($image->getType() == IMAGETYPE_PNG) {
         imagealphablending($image->getResource(), FALSE);
         imagesavealpha($image->getResource(), TRUE);
       }
@@ -221,12 +219,10 @@ public function getInfo(ImageInterface $image) {
     $data = getimagesize($image->getSource());
 
     if (isset($data) && is_array($data)) {
-      $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
-      $extension = isset($extensions[$data[2]]) ?  $extensions[$data[2]] : '';
       $details = array(
         'width'     => $data[0],
         'height'    => $data[1],
-        'extension' => $extension,
+        'type'      => $data[2],
         'mime_type' => $data['mime'],
       );
     }
@@ -250,7 +246,7 @@ public function getInfo(ImageInterface $image) {
   public function createTmp(ImageInterface $image, $width, $height) {
     $res = imagecreatetruecolor($width, $height);
 
-    if ($image->getExtension() == 'gif') {
+    if ($image->getType() == IMAGETYPE_GIF) {
       // Grab transparent color index from image resource.
       $transparent = imagecolortransparent($image->getResource());
 
@@ -264,7 +260,7 @@ public function createTmp(ImageInterface $image, $width, $height) {
         imagecolortransparent($res, $transparent);
       }
     }
-    elseif ($image->getExtension() == 'png') {
+    elseif ($image->getType() == IMAGETYPE_PNG) {
       imagealphablending($res, FALSE);
       $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
       imagefill($res, 0, 0, $transparency);
@@ -290,4 +286,11 @@ public static function isAvailable() {
     }
     return FALSE;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function supportedTypes() {
+    return array(IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF);
+  }
 }
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
index 774f050..5918160 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
@@ -50,14 +50,14 @@
    *
    * @see system_image_toolkit_settings()
    */
-  function settingsForm();
+  public function settingsForm();
 
   /**
    * Handles submissions for toolkit's settings form.
    *
    * @see system_image_toolkit_settings_submit()
    */
-  function settingsFormSubmit($form, &$form_state);
+  public function settingsFormSubmit($form, &$form_state);
 
   /**
    * Scales an image to the specified size.
@@ -73,7 +73,7 @@ function settingsFormSubmit($form, &$form_state);
    * @return bool
    *   TRUE or FALSE, based on success.
    */
-  function resize(ImageInterface $image, $width, $height);
+  public function resize(ImageInterface $image, $width, $height);
 
   /**
    * Rotates an image the given number of degrees.
@@ -93,7 +93,7 @@ function resize(ImageInterface $image, $width, $height);
    * @return bool
    *   TRUE or FALSE, based on success.
    */
-  function rotate(ImageInterface $image, $degrees, $background = NULL);
+  public function rotate(ImageInterface $image, $degrees, $background = NULL);
 
   /**
    * Crops an image.
@@ -115,7 +115,7 @@ function rotate(ImageInterface $image, $degrees, $background = NULL);
    *
    * @see image_crop()
    */
-  function crop(ImageInterface $image, $x, $y, $width, $height);
+  public function crop(ImageInterface $image, $x, $y, $width, $height);
 
   /**
    * Converts an image resource to grayscale.
@@ -129,7 +129,7 @@ function crop(ImageInterface $image, $x, $y, $width, $height);
    * @return bool
    *   TRUE or FALSE, based on success.
    */
-  function desaturate(ImageInterface $image);
+  public function desaturate(ImageInterface $image);
 
   /**
    * Creates an image resource from a file.
@@ -140,7 +140,7 @@ function desaturate(ImageInterface $image);
    * @return bool
    *   TRUE or FALSE, based on success.
    */
-  function load(ImageInterface $image);
+  public function load(ImageInterface $image);
 
   /**
    * Writes an image resource to a destination file.
@@ -153,7 +153,7 @@ function load(ImageInterface $image);
    * @return bool
    *   TRUE or FALSE, based on success.
    */
-  function save(ImageInterface $image, $destination);
+  public function save(ImageInterface $image, $destination);
 
   /**
    * Gets details about an image.
@@ -166,12 +166,12 @@ function save(ImageInterface $image, $destination);
    *   keyed array containing information about the image:
    *   - "width": Width, in pixels.
    *   - "height": Height, in pixels.
-   *   - "extension": Commonly used file extension for the image.
-   *   - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
+   *   - "type": Image type represented as an IMAGETYPE_* constant.
+   *   - "mime_type": MIME type (e.g. 'image/jpeg', 'image/gif', 'image/png').
    *
    * @see \Drupal\Core\Image\ImageInterface::processInfo()
    */
-  function getInfo(ImageInterface $image);
+  public function getInfo(ImageInterface $image);
 
   /**
    * Verifies Image Toolkit is set up correctly.
@@ -179,5 +179,14 @@ function getInfo(ImageInterface $image);
    * @return bool
    *   True if the GD toolkit is available on this machine.
    */
-  static function isAvailable();
+  public static function isAvailable();
+
+  /**
+   * Returns a list of image types supported by the toolkit.
+   *
+   * @return array
+   *   An array of available image types. An image type is represented by a PHP
+   *   IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.).
+   */
+  public static function supportedTypes();
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
index 1797b78..0e81a77 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -230,7 +230,7 @@ function testManipulations() {
         $image_truecolor = imageistruecolor($image->getResource());
         $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file)));
 
-        if ($image->getExtension() == 'gif') {
+        if ($image->getType() == IMAGETYPE_GIF) {
           if ($op == 'desaturate') {
             // Transparent GIFs and the imagefilter function don't work together.
             $values['corners'][3][3] = 0;
@@ -264,7 +264,7 @@ function testManipulations() {
         $this->assertTrue($correct_dimensions_object, format_string('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op)));
 
         // JPEG colors will always be messed up due to compression.
-        if ($image->getExtension() != 'jpg') {
+        if ($image->getType() != IMAGETYPE_JPEG) {
           // Now check each of the corners to ensure color correctness.
           foreach ($values['corners'] as $key => $corner) {
             // Get the location of the corner.
@@ -293,6 +293,6 @@ function testManipulations() {
         }
       }
     }
-
   }
+
 }
diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
index eede36a..836ef7c 100644
--- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
+++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
@@ -46,12 +46,10 @@ public function getInfo(ImageInterface $image) {
     $data = getimagesize($image->getSource());
 
     if (isset($data) && is_array($data)) {
-      $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
-      $extension = isset($extensions[$data[2]]) ?  $extensions[$data[2]] : '';
       $details = array(
         'width'     => $data[0],
         'height'    => $data[1],
-        'extension' => $extension,
+        'type'      => $data[2],
         'mime_type' => $data['mime'],
       );
     }
@@ -133,4 +131,12 @@ protected function logCall($op, $args) {
   public static function isAvailable() {
     return TRUE;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function supportedTypes() {
+    return array(IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF);
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Image/ImageTest.php b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
index c96f4c9..700973e 100644
--- a/core/tests/Drupal/Tests/Core/Image/ImageTest.php
+++ b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
@@ -54,6 +54,7 @@ protected function setUp() {
         'width'     => 88,
         'height'    => 100,
         'extension' => 'png',
+        'type'      => IMAGETYPE_PNG,
         'mime_type' => 'image/png',
       )));
 
@@ -107,6 +108,13 @@ public function testGetFileSize() {
   }
 
   /**
+   * Tests \Drupal\Core\Image\Image::getType().
+   */
+  public function testGetType() {
+    $this->assertEquals($this->image->getType(), IMAGETYPE_PNG);
+  }
+
+  /**
    * Tests \Drupal\Core\Image\Image::getMimeType().
    */
   public function testGetMimeType() {
-- 
1.8.3.1

