Problem/Motivation

This is a follow-up of parent issue: #2103621: Move GD logic from ImageInterface to toolkit, tie toolkit instance to Image instance, toolkits should no longer be instantiated separately
This is part of meta issue: #2105863: [meta] Images, toolkits and operations

This issue aims to refactor the existing, valid and supported handling on Image, imageinterface, ImageToolkitInterface and GDtoolkit.

  • In issue #2103621: Move GD logic from ImageInterface to toolkit, tie toolkit instance to Image instance, toolkits should no longer be instantiated separately it was discovered that besides removing GD specific elements form ImageInterface, the interface needs further cleaning. in that issue it was decided to tie 1 image toolkit instance to 1 image instance. Consequently, image toolkits should preferably no longer be called directly outside the image system. The only place where this still occurs is in the file module, that needs a list of supported image types (in an error message).
  • supportedTypes() is a method on ImageToolkitInterface, whereas isSupported() is a method on ImageInterface. This is not logical
  • supportedTypes() is a static method but all current calls are via an instance.
  • isExisting does/did not check on an image being of a supported type, though the latest patch seems to fix this. We should research whether indeed only supported images will be handled by a toolkit instance.
  • After the changes made by the parent issue, the property $processed acts more like a flag that denotes isValid or isExisting. We should rename that property.
  • The whole chain of the concepts of existing, valid, and supported needs a good look and where and if necessary some refactoring, including the documentation and error handling

Proposed resolution

  • Public APIs:
  1. ImageInterface::isExisting() and ImageInterface::isSupported() methods are replaced by a new ImageInterface::isValid() method. The two methods were somehow overlapping, so this is a cleanup. 'Validity' at this stage means that Image and ImageToolkit could successfully parse an image file into their internal properties. In #2063373: Cannot save image created from scratch, when we will look at instantiating Images from scratch, the concept of 'validity' will be extended to identify that an Image is fully available for manipulation and/or saving to file.
  2. ImageInterface::setSource() methods is removed. The protected $source property is now set only within the Image class upon construction or when an image is saved to a different destination. This enforces the tie-in between the Image object and its ImageToolkit instance.
  3. Image::$type and ImageInterface::getType() are removed, and implemented in GDToolkit instead, to reflect the PHP/GD related nature of IMAGETYPE_* constants. Alternative toolkits are no longer constrained to use only image formats for which an IMAGETYPE_* constant exists.
  4. ImageToolkitInterface is expanded with a getMimeType() and a getSupportedExtensions() methods, implemented in GDToolkit. The GDToolkit implementation 'internally' manages the image type as an IMAGETYPE_* constant, and uses image_type_to_*() functions to return mime type and supported extensions as needed. So we do not have direct calls to image_type_to_*() functions outside of the toolkit (and GD toolkit tests) any more. ImageToolkitInterface::supportedTypes() is removed and implemented as a protected method internal to GDToolkit correspondingly.
  5. ImageToolkitInterface::getInfo() is renamed to ImageToolkitInterface::parseFile() and now returns a bool to indicate if an image file is valid.
  6. a new ImageFactory::getSupportedExtensions($toolkit_id) method is implemented to return the file extensions supported by the specified toolkit (or the default one if not specified). This is in line to the 'internalization' of the IMAGETYPE_* to the GDToolkit. Consuming code in fact requires the supported extensions (see comment #4), not the types. This is implemented in the factory as we can avoid instantiating an Image object to get the list of supported extensions.
  • Consuming code:
    1. occurences of Image::isExisting() and Image::isSupported() in the code base are changed to the new Image::isValid() method.
    2. cleans up file_validate_is_image() to use ImageFactory::getSupportedExtensions()
    3. cleans up file_validate_image_resolution() to remove a duplicated instantiation of Image. In doing so, introduces an additional check on success of image resizing in file_validate_image_resolution(), a test for that, and height/width parameters checks in GDToolkit to be able to respond to the test.
  • Internals:
    1. the internal Image::processInfo() method is renamed to Image::parseFile() to make it consistent with the ImageToolkitInterface::parseFile() method that it calls to defer image file parsing to the toolkit. Also, this method is now called only from the Image constructor if a source file is specified upon Image instantiation.

    Remaining tasks

    Review and accept patch.

    User interface changes

    None.

    API changes

    Some, see above. Will be documented in the "Complete Image API overhaul" change record https://drupal.org/node/2084547.

    Support from Acquia helps fund testing for Drupal Acquia logo

    Comments

    fietserwin’s picture

    Issue summary: View changes

    To add to the confusion:
    - supportedTypes() is a static method on the toolkit interface but only called on instances.
    - comment #2196067-11: Remove setWidth and setHeight from ImageInterface proposes to add these methods to the image factory as well: choose the toolkit to use based on support?

    mondrake’s picture

    This is a no-test patch built on top of

    #2196067: Remove setWidth and setHeight from ImageInterface and
    #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead

    just sharing here to start discussion.

    What it does:

    • merges both isExisting() and isSupported() ImageInterface methods in a new isValid() method (in fact equal to current isExisting()). In fact, isSupported is no longer relevant is current HEAD as only supported types get loaded by getInfo().
    • provides a ImageFactory::getSupportedExtensions($toolkit_id) method that returns the file extensions supported by the specified toolkit (or the default one if not specified). In fact, there is no call in current HEAD to ImageToolkitInterface::supportedTypes() outside of file.module ... but in fact that call is just instrumental to get a list of supported extensions.
    • cleans up file_validate_size() and file_validate_image_resolution() functions to use the new methods. In doing so, introduces an additional check on success of image resizing in file_validate_image_resolution(), a test for that, and height/width parameters checks in GDToolkit to be able to respond to the test. This is an anticipation of a chunk currently in #2110499: Unify the call and interfaces of image toolkit operations that by being introduced here would reduce the patch there.
    • changes occurences of isExisting() and isSupported() to isValid() in the code base.
    fietserwin’s picture

    Thanks for taking a stab at it. I had a quick look at the patch. In general, it looks good, though I need to review in more detail, if all concerns are addressed.

    • I especially like the getSupportedExtensions() as that is indeed what file.module needs and, as #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead point out, should not be done outside the Image system as long as we use IMAGETYPE constants.
    • Not sure though if that is the task of a factory.
    • It does not rename the internal property $processed (which in turn may lead to renaming processInfo())
    • We need to study if this handles creating new images without source (and if we want to handle that in this issue).
    • I am not sure if I like a constructs like "$definition['class']::supportedTypes();" (I personally find call_user_func() a bit more explicit).

    I will await the other issues (that are RTBC), to get a clearer view on this.

    mondrake’s picture

    FileSize
    24.69 KB

    New idea.

    Do we really need a $type property in Image? In reality, 'outside' of Image<->ImageToolkit, we need two things:

    • respond to requests about what is the mime type of an image, to properly represent it in HTML
    • respond to requests from validators on what are the supported image file extensions

    So my answer is no, we do not need it. We can make it an internal of the toolkit, given that now a toolkit object is instantiated for every image. Also, it's the toolkit that can provide information about supported extensions (in general) and the file mime type (for a specific image). These can be proxied by Image and ImageFactory when it's handy.

    So in this patch I moved the $type property to the GDToolkit, and exposed to ImageToolkitInterface a getMimeType() and a getSupportedExtensions() methods, implemented in GDToolkit. The GDToolkit implementation 'internally' manages the image type as an IMAGETYPE_* constant, and uses image_type_to_*() functions to return mime type and supported extensions as needed. So we do not have direct calls to image_type_to_*() function outside of the toolkit (and GD toolkit tests) any more.

    This would mean that #2219349: Use file_mimetype_mapping instead of IMAGETYPE_* constants in Image class becomes redundant, too - we no longer have to care about managing the image type in the Image class.

    Imagemagick could implement this alternatively - e.g. without managing a list of internal types, but just listing directly the supported file extensions in the relevant method, and using file_mimetype_mapping() to return a mime type from the extension.

    Patch is still a no-test built on top of the two blockers as described in #2.

    fietserwin’s picture

    I think that your reasoning is correct: IMAGETYPE constants are GD specific (or at least oriented towards GD), so using it outside the GDToolkit is tricky at best, and your newest patch solves that.

    I'm still not sure about a mime type getter on Image. So we can close the other issue if my question over there will be answered here.

    Question:
    The image system is about image manipulation not about file handling. If information about an image file is needed, think of extension or mime type, it should be approached as a file or stream (wrapper) not as an image object.
    * Current usage 1: in image style delivery. An image derivative is created and subsequently served as the result of the request. This can be done by file handling.
    * Current usage 2: image effects logging, why do we need to log the mime type, full file name is enough identification to do error hunting.

    fietserwin’s picture

    Title: Refactor image and imagetoolkit isExisting, isSupported, supportedTypes » Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType

    Changing title, but perhaps we even better move my question to issue #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead. What do you think @mondrake?

    mondrake’s picture

    Re #6 I wouldn't reopen #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead which is RTBC and anyway paving the way to this one. Let's discuss #5 here.

    Re your question, can you point to the code elements you refer to in current usage 1/2? I'd need to visualize it and think about :)

    Thanks

    fietserwin’s picture

    usage 1a: function image_file_download($uri) in image.module
    usage 1b: public function deliver(...) in ImageStyleDownloadController.php

    usage 2: public function applyEffect(...) in all image effects: watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)'

    An example where a File is already used to get the mime type of an image fle: core\vendor\symfony\http-foundation\Symfony\Component\HttpFoundation\Tests\File\FileTest.php, public function testGetMimeTypeUsesMimeTypeGuessers()

    Usage 1 is of the tricky type as I described in comment #5 of #2217783-5: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead:
    However, things will get a bit tricky here. Currently, our image class correctly determines mime type based on file contents, not file extension. It looks like the file and stream wrapper functions only do so based on file extension, not contents.
    But on the public file system that is only the first time (on creation), after that Apache serves the mime type header based on extension. So I think the problem is not that big.

    fietserwin’s picture

    Issue summary: View changes
    Status: Postponed » Active
    mondrake’s picture

    Status: Active » Needs review
    FileSize
    33.43 KB

    Let's see what bot says about this. I won't be able to work on this for the next week or so, but if it turns green I'll try and recap what the changes are before going off.

    mondrake’s picture

    Please disregard #10.

    mondrake’s picture

    Summary of what #11 brings:

    • Public APIs:
    1. ImageInterface::isExisting() and ImageInterface::isSupported() methods are replaced by a new ImageInterface::isValid() method. The two methods were somehow overlapping, so this is a cleanup. 'Validity' at this stage means that Image and ImageToolkit could successfully parse an image file into their internal properties. In #2063373: Cannot save image created from scratch, when we will look at instantiating Images from scratch, the concept of 'validity' will be extended to identify that an Image is fully available for manipulation and/or saving to file.
    2. Image::$type and ImageInterface::getType() are removed, and implemented in GDToolkit instead, to reflect the PHP/GD related nature of IMAGETYPE_* constants. Alternative toolkits are no longer constrained to use only image formats for which an IMAGETYPE_* constant exists.
    3. ImageToolkitInterface is expanded with a getMimeType() and a getSupportedExtensions() methods, implemented in GDToolkit. The GDToolkit implementation 'internally' manages the image type as an IMAGETYPE_* constant, and uses image_type_to_*() functions to return mime type and supported extensions as needed. So we do not have direct calls to image_type_to_*() functions outside of the toolkit (and GD toolkit tests) any more. ImageToolkitInterface::supportedTypes() is removed and implemented as a protected method internal to GDToolkit correspondingly.
    4. ImageToolkitInterface::getInfo() is renamed to ImageToolkitInterface::parseFile() and now returns a bool to indicate if an image file is valid.
    5. a new ImageFactory::getSupportedExtensions($toolkit_id) method is implemented to return the file extensions supported by the specified toolkit (or the default one if not specified). This is in line to the 'internalization' of the IMAGETYPE_* to the GDToolkit. Consuming code in fact requires the supported extensions (see comment #4), not the types. This is implemented in the factory as we can avoid instantiating an Image object to get the list of supported extensions.
  • Consuming code:
    1. occurences of Image::isExisting() and Image::isSupported() in the code base are changed to the new Image::isValid() method.
    2. cleans up file_validate_size() to use ImageFactory::getSupportedExtensions() and file_validate_image_resolution() to remove a duplicated instantiation of Image. In doing so, introduces an additional check on success of image resizing in file_validate_image_resolution(), a test for that, and height/width parameters checks in GDToolkit to be able to respond to the test.
  • Internals:
    1. the internal Image::processInfo() method is renamed to Image::parseFile() to make it consistent with the ImageToolkitInterface::parseFile() method that it calls to defer image file parsing to the toolkit. Also, this method is now called only from the Image constructor if a source file is specified upon Image instantiation.

    While working on this patch, I realized that in the GD toolikit we are currently *always* loading fully an image into a GD resource. I believe this is unnecessary overhead, as the actual resource is needed only if an image needs to be manipulated by applying operations to it. In most cases, $imageFactory->get() will only be needed to determine mimetype, height and width to deliver an image to the client, which can be achieved by only calling getimagesize(), and avoiding the current calls to image_type_to_extension(), imagecreatefromxxx(), and even overlaying if an image is not truecolor.
    So we could actually split the logic here:

    • ImageToolkitInterface::parseFile() only run getimagesize()
    • fully load the image in the GD resource only when an operation has to be applied on it

    I will open a follow up for this, as this issue is already pretty big. Also, it would make sense to do #2073759: Convert toolkit operations to plugins before as with that issue we will introduce a single entry point for image operations, which would be the natural tollgate to determine if the GD resource has to be available.

    mondrake’s picture

    fietserwin’s picture

    Issue summary: View changes
    Status: Needs review » Needs work
    1. +++ b/core/lib/Drupal/Core/Image/Image.php
      @@ -20,7 +20,7 @@
         /**
      -   * String specifying the path of the image file.
      +   * Path of the image file.
          *
          * @var string
          */
        protected $source;
      

      Initialize $source to '', so it always returns a string according to the specs. It would be better to also update the docs on this method to specify that it returns an empty string if no source is set (yet).

    2. +++ b/core/lib/Drupal/Core/Image/Image.php
      @@ -178,33 +152,34 @@ public function save($destination = NULL) {
      +    if (!is_file($source) && !is_uploaded_file($source)) {
             return FALSE;
      

      I think that is_uploaded_file() implies is_file(), so this looks a bit suspicious to me. Moreover, I'm afraid that this won't work with all stream wrappers, especially not remote stream wrappers (e.g. wrapping around a flickr image) or internal memory wrappers. As I don't think it is the task of the image system to restrict what type of sources can be read from, we should leave that check to the toolkit. Currently, that means calling getimagesize() (not GD specific, so can be moved to the base toolkit in another issue) which accepts all kinds of stream wrappers. If that returns FALSE, it is not an image, otherwise it is.

    3. +++ b/core/lib/Drupal/Core/Image/Image.php
      @@ -178,33 +152,34 @@ public function save($destination = NULL) {
      +    $this->setSource($source);
      +    if ($this->toolkit->parseFile($this)) {
      

      If I call setSource() directly (which I can, as it is part of the interface), nothing happens to the internal state of the image and toolkit object (ok, except for the property source). So I think that setSource() should call parseFile() and the constructor should call setSource() (if source was passed in).

    4. +++ b/core/lib/Drupal/Core/Image/Image.php
      @@ -178,33 +152,34 @@   protected function parseFile($source) {
      +      $this->valid = TRUE;
      +      return TRUE;
      

      On error, valid should be set to FALSE. Remember that we can change source along the way ...

    5. +++ b/core/lib/Drupal/Core/Image/ImageInterface.php
      @@ -13,20 +13,12 @@
      +   *   TRUE if the image objects contains a valid image, FALSE otherwise.
          */
      

      object (instead of objects)

    6. +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitInterface.php
      @@ -185,19 +185,15 @@ public function scale(ImageInterface $image, $width = NULL, $height = NULL, $ups
      +   *   TRUE, if the file could be found and is an image. Otherwise, FALSE.
          */
      

      TRUE if ... , FALSE otherwise
      (remove comma after true and reverse last 2 words)

    7. +++ b/core/modules/file/file.module
      @@ -409,13 +409,11 @@ function file_validate_size(File $file, $file_limit = 0, $user_limit = 0) {
      +    $errors[] = t('Image type not supported. Allowed types: %types', array('%types' => implode(' ', $supported_extensions)));
         }
      

      ... implode(' ,', ...); (add comma in glue string)

    8. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
      @@ -159,7 +171,11 @@ public function crop(ImageInterface $image, $x, $y, $width, $height) {
      +    if ($width < 0 || $height < 0) {
      +      return FALSE;
      +    }
      +
      

      I don't understand where this check is coming from. if we want a check here, I would check for <= 0 as resizing to 0 looks erroneous either.

    9. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
      @@ -291,15 +308,18 @@ public function parseFile(ImageInterface $image) {
      +    $source = $image->getSource();
      +
      +    $data = getimagesize($source);
       
           if (isset($data) && is_array($data) && in_array($data[2], static::supportedTypes())) {
      -      $details['type'] = $data[2];
      -      $this->load($image->getSource(), $details);
      +      $this->setType($data[2]);
      +      $this->load($source, $image->getType());
      +      return (bool) $this->getResource();
           }

      - empty line is not needed.
      - We can check on: if ($data && in_array(...)) { ... }
      - $this->getType()! (not $image->getType()). Probably passes tests due to __call in image :(

    10. +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
      +  public function setType($type) {
      +    if (in_array($type, static::supportedTypes())) {
      +      $this->type = $type;
      +    }
      +    return $this;
      +  }
      

      Does this need to be public? To me, this is a bit like the setHeight/setWidth on image we removed because they do not make sense in isolation. I don't think that operations will need access to it, so protected is better. I also don't like the "error handling" or absence of it. So do not check what is coming in, or accept the consequences of checking: throw an error. The checking is done before calling this method and if we make it protected that remains so.

    11. +++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
      @@ -62,35 +70,21 @@ public function settingsFormSubmit($form, &$form_state) {
      +    $source = $image->getSource();
      +
      +    $data = getimagesize($source);
      

      remove empty line.

    12. +++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
      @@ -62,35 +70,21 @@ public function settingsFormSubmit($form, &$form_state) {
           if (isset($data) && is_array($data) && in_array($data[2], static::supportedTypes())) {
      -      $details['type'] = $data[2];
      

      if ($data && ...)

    A few serious remarks, but most remarks are about small doc standards/typos.

    What bothers me more is:
    Image(Interface):
    - Do we want to allow to change source after an image has been created? It really does complicate things: internal state like the valid flag, internal state on the toolkit. I think that we wouldn't loose any functionality by restricting an Image object to 1 source, being its initial source as passed in the constructor. Only upon saving and if the file gets saved to a different destination, we should change that property. If, at that point, we use direct access to the property to change it, we can remove the setSource() method.
    - Do we really need getFileSize() and getMimeType() on ImageInterface? I think it is better done by leaving that up to File. The only place where it is used is in ImageStyleDownloadController where the image is handled as a file and thus a file object would be better.

    GDToolkit:
    - Use of $image as parameter in toolkit methods getWidth, getHeight and getMimeType. If I look at this in the code after this patch, it doesn't seem to make sense to me. I'm not sure why I didn't see it in the previous issue, but I would like to correct it here.
    - Passing in $type to load(): I think that load() is capable of determining that itself, it is a property after all.

    IMO, these 4 points can and should also be corrected in this issue. We are going halfway only now and I think this would really clean up the interfaces further.

    Note: I also edited the summary a bit, but typos and small rephrasing only.

    mondrake’s picture

    Status: Needs work » Needs review
    FileSize
    9.82 KB
    36.03 KB

    Thanks for review. Here a new patch and my considerations.

    1 - OK

    2.a - That line was implemented for a very specific reason, see #307636: image_get_info() fails on IIS on new uploads. I would rather not touch it, apart from just the change of the name of the variable.
    2.b - I'd say that extending the Image class to support any wrapper goes beyond the scope of this issue.
    2.c - IMO, getimagesize() should only be called within the GDToolkit implementation, in core, and not be part of a base class implementation. Even if it is not strictly GD dependent, still it only handles image formats for which a IMAGETYPE_* constant is available, and in this issue we are making an effort to internalise this into the GD toolkit. In core, getimagesize() is called in two places outside of the image system, filter.module and color.module, which are accessing images directly. Potentially, these calls could be converted to use the image system instead. Also note that an Imagemagick issue #2064223: image_imagemagick_get_info() should use Imagemagick Identify command is suggesting to use the identify command line application for retrieving basic information about an image, instead of getimagesize(). That would open up to manage a much broader set of image formats.

    3, and

    Do we want to allow to change source after an image has been created? It really does complicate things: internal state like the valid flag, internal state on the toolkit. I think that we wouldn't loose any functionality by restricting an Image object to 1 source, being its initial source as passed in the constructor. Only upon saving and if the file gets saved to a different destination, we should change that property. If, at that point, we use direct access to the property to change it, we can remove the setSource() method.

    I also found the usage of setSource weird, and was thinking about opening a separate issue to remove it. But since we are on the same line, I am doing it here.

    4 - No longer relevant I believe?

    5 - OK

    6 - OK

    7 - Here I made the message consistent to other similar messages in core. Example: when creating a new Article, the image field bears the following description: "One file only. 10 MB limit. Allowed types: png gif jpg jpeg."

    8 - OK to test for <= 0. This comes from comment #2110499-15: Unify the call and interfaces of image toolkit operations, point 3, in order to provide a test for file_validate_image_resolution().

    9 - OK. Added 'getType' to the Image::_call blacklist too.

    10 - We potentially need to manage the type in two more occasions, not sure whether to be in core or contrib: (a) when creating an image from scratch, we will have to specify its type. Will see how to do that in #2063373: Cannot save image created from scratch. (b) when changing an image format (e.g. from png to jpg). So I'd rather keep it public for GDToolkit so that operations can access, like setResource().

    11 - OK

    12 - OK

    Do we really need getFileSize() and getMimeType() on ImageInterface? I think it is better done by leaving that up to File. The only place where it is used is in ImageStyleDownloadController where the image is handled as a file and thus a file object would be better.

    Well we also need getWidth() and getHeight() for image themes to build the proper img HTML. I have no particular opinion on moving to File, but that should be a separate issue filed under file.module. However, given my view in 2.c above, still I believe that this should be provided by the ImageToolkitInterface (via getimagesize() in the GDToolkit, but allowing alternatives approaches), and since we made a lot of effort to internalize the toolkit into Image, still I believe that Image is the best object to use to return this information.

    Use of $image as parameter in toolkit methods getWidth, getHeight and getMimeType. If I look at this in the code after this patch, it doesn't seem to make sense to me. I'm not sure why I didn't see it in the previous issue, but I would like to correct it here.

    I also thought about this, and IMO it is good to keep passing $image in all the ImageToolkitInterface methods that apply to images (i.e. excluding the toolkit-level ones like settingsForm(), getRequirements() etc). The reason is that custom/contrib may want to extend Image class with own methods and be made public for calling from an extended ImageToolkit.

    Passing in $type to load(): I think that load() is capable of determining that itself, it is a property after all.

    OK removed the argument from the method. Still, I think that the $type should be determined in parseFile(), and load() be based on that. That's because later on in #2244359: Lazy-load GD resource in GDToolkit we will need to be able to respond to getMimeType() without a loaded GD resource.

    fietserwin’s picture

    RE #15)

    • 2a: IMO, those 3 lines should be removed completely as it is redoing what the toolkit has to do again and in more detail anyway. It would also mean that getimagesize() should be called with a @ in front to prevent warnings/notices on expected situations. However, as getimagesize() also emits an E_NOTICE if the file is not an image, this @ should be done anyway, even if we leave these line for now. Follow up?
    • 2b: In PHP5.4 stream wrappers are handled completely transparently, and, on better reading the docs, is_file() will do as well, so that's no problem here.
    • 2c: I guess you are right, though even then, it could serve as a base implementation that can be overridden by toolkits that want to support more file types.
    • 3: This is so much better. So we now have 1 toolkit linked to exactly 1 image linked to 1 unchangeable source, except upon saving (and only then). So that means that we can also remove:
      - passing the source from Image::parseFile() to ImageToolkitInterface::parseFile(), provided we first set the source property, in the image constructor or in image parseFile).
      - passing the source to GDToolkit::load().
      - BTW: load is called with 2 arguments, while it has only 1 parameter left. Thus even if you do not change the above for now, that should be corrected.
    • 4: It remains tricky as it relies on too much context and we keep the case of the need for reparsing after saving to a(nother) file. I would rather see something like (combining with 2a):
        protected function parseFile($source) {
        // Defer file parsing to toolkit.
          $this->valid = $this->toolkit->parseFile($this, $source);
          if ($this->valid) {
            ... (existing lines except the return)
          }
          return $this->valid;
        }
    • 7: OK fine with me. I noticed that it had changed, so made a remark about it.
    • 9: the check on is_array() is still there and still superfluous.
    • 10 a: No, a GD resource has no type, so we can create one without having this info, but even if we want this info to be available during processing, it does not have to be public,.
    • 10b: No, saving to another type is done by passing in save options. But as that issue is not in, leave it for now: I have added a comment to issue #2168511: Allow to pass save options to ImageInterface::save.
    • Do we really need getFileSize() and getMimeType(): getWidth() and getHeight() are used when serving an html-img element, while file size and mime type are used when serving the image itself. So we indeed do not see a combined use of this in the (core) code base. I will file a separate issue, but under the image system, as the only current use is in image (image style download controller), so OK for now.
    • Use of $image as parameter in toolkit methods: Your reasoning is OK, but should be solved differently. As a toolkit object is tied to exactly 1 image object, it is a natural case for a property (+ getter for the operations). However setting it is a bit difficult. It should be passed in via the constructor, but we can't, as we first create the toolkit object to pass that to the image constructor, so there is no image object upon construction time. We should solve this by adding a setImage(ImageInterface $image) method to the toolkit and calling that from the image constructor (subsequent calls should fail).
      If we do so, we can remove them from the operations as well. it would really clean up that part as well. (in operations it is only used to pass to getWidth/getHeight or to other operations (and a few error messages) ...)

    [EDITED: response to 3.]

    mondrake’s picture

    FileSize
    27.27 KB
    55.07 KB

    Hm. I am afraid this is becoming too big and noone will further review, not to say commit, it :(

    This is just doing

    ... adding a setImage(ImageInterface $image) method to the toolkit ...

    which I think is a good idea, but I start to believe it should be a follow-up.

    @fietserwin if we agree to this then what is really left to be done here?

    mondrake’s picture

    For 2.a, there is a #1522348: Notice: getimagesize read error in image_gd_get_info() line 349 even though it's been left on 7.x queue.

    EDIT

    ... and for 3, my idea is to set the Image:$source only *after* ImageToolkitInterface::parseFile() has returned successfully - to avoid having that property set while Image:$valid is still false.

    Status: Needs review » Needs work

    The last submitted patch, 17: 2211227_image-refactor_17.patch, failed testing.

    mondrake’s picture

    Status: Needs work » Needs review

    needs review per #17. patch to review in #15.

    fietserwin’s picture

    OK to defer:
    - Removing $image parameter. Created child issue #2257587: Remove $image parameter in calls between and within Image and toolkit.
    - 10: I already added a comment to that other issue.
    - getFileSize() and getMimeType():I already created a child issue.

    Let's do (based on #15)
    - Let's do 2a/4, 3 , and 9 here (very small changes wrt to #15). (We let the other issue about using @ remain D7)
    - Wrt to $source parameter: we create an image based on a source, the source cannot change after that anymore. So the source is an intrinsic property of an image object even if it is not valid. Moreover, let's not use source to determine validity, we did something like that with getResource() in the past and concluded that it was abuse of that getter. So let's change that here as well.

    With those minor changes to #15, I will RTBC.

    mondrake’s picture

    FileSize
    5.6 KB
    35.73 KB

    OK. Interdiff vs. #15.

    fietserwin’s picture

    Status: Needs review » Reviewed & tested by the community

    Nice cleanup.

    I tried to test the thing about allowed file extensions, by adding svg to to the list of extensions on the image field on Article. However, the (js) client-side and server side validation kept telling me that only png gif jpg jpeg are allowed. This has nothing to do with this issue, but I could thus not manually test file_validate_is_image().

    I will write the patch for child issue #2257587: Remove $image parameter in calls between and within Image and toolkit and post it as a combined patch (for the test bot) and as an interdiff (for manual review).

    I added a comment to #1522348: Notice: getimagesize read error in image_gd_get_info() line 349 that it can remain D7.

    mondrake’s picture

    Re. #23, in order to test manually file_validate_is_image(), you can try to change the logo of the site, which is the only place in core where that function is called. (Appearance > Global Settings, untick 'Use the default logo supplied by the theme', then try loading a non image file in the 'Upload logo image' field')

    fietserwin’s picture

    A strange place to test it, as the logo will not undergo any manipulation, so the toolkit should not be involved. On testing I got:
    - situation 1: placing an svg ifle on the root of the web site and adding its name into "Path to custom logo": no problem, logo works
    - situation 2: Browsing the same file tos et the logo via the field "Upload logo image", error message:

        The specified file logo.svg could not be uploaded.
            - Image type not supported. Allowed types: png jpeg gif
            - Only files with the following extensions are allowed: jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp.
        The logo could not be uploaded.
    

    I created a separate issue #2259567: Support SVG files for theme logo setting , but it did test the ImageFactory::getSupportedExtensions() method, so thanks.

    fietserwin’s picture

    Note to the core committer: the child issue #2257587: Remove $image parameter in calls between and within Image and toolkit contains a working and reviewed patch that combines this issue and that child issue. So, it is possible to:
    - Review that patch in its entirety and commit/decline.
    - Review this patch and the interdiff over there and commit/decline the patch over there (this as the interdiff over there is the difference between this patch and the combined patch in the child issue)
    - Only review this patch and commit/decline, we will reroll the child issue by making the interdiff the patch.

    mondrake’s picture

    Issue summary: View changes

    Updated proposed resoltion in the issue summary with summary in #12 and #15.3

    mondrake’s picture

    xjm’s picture

    The last submitted patch, 28: 2211227_image-refactor_28.patch, failed testing.

    xjm’s picture

    Can we edit https://drupal.org/node/2084547 to add a reference to this issue? That way, the CR can document the work in progress and will appear in the sidebar of this node.

    mondrake’s picture

    @xjm thanks for the reroll. CR at https://drupal.org/node/2084547 updated with reference to this issue; will have to be edited again after commit to reflect what this issue changes.

    xjm’s picture

    Thanks @mondrake!

    One option on a change record is to document both completed and planned work separately on the CR, and move items from "planned/still under development" to the main section as different issues go in. That way the documentation can be reviewed together.

    Status: Reviewed & tested by the community » Needs work

    The last submitted patch, 29: 2211227-psr4-reroll.patch, failed testing.

    mondrake’s picture

    Status: Needs work » Needs review

    29: 2211227-psr4-reroll.patch queued for re-testing.

    mondrake’s picture

    Status: Needs review » Reviewed & tested by the community

    Testbot failures.

    Status: Reviewed & tested by the community » Needs work

    The last submitted patch, 29: 2211227-psr4-reroll.patch, failed testing.

    mondrake’s picture

    Status: Needs work » Needs review

    29: 2211227-psr4-reroll.patch queued for re-testing.

    mondrake’s picture

    Status: Needs review » Reviewed & tested by the community
    alexpott’s picture

    Status: Reviewed & tested by the community » Fixed

    Committed ae8eb10 and pushed to 8.x. Thanks!

    • Commit ae8eb10 on 8.x by alexpott:
      Issue #2211227 by mondrake, xjm | fietserwin: Refactor image and...
    mondrake’s picture

    Status: Fixed » Closed (fixed)

    Automatically closed - issue fixed for 2 weeks with no activity.

    Dave Reid’s picture

    This missed an update to an existing change record that still references isSupported(): https://www.drupal.org/node/2104301

    mondrake’s picture

    #44 - Updated the change record.