Problem/Motivation

If file is converted to another format, i.e. from jpg to gif, filepath does not match anymore. We already have a piece of code to match converted image uri but it works only for non-private files.

See Drupal\image\Controller\ImageStyleDownloadController::deliver():

    // Don't try to generate file if source is missing.
    if (!file_exists($image_uri)) {
      // If the image style converted the extension, it has been added to the
      // original file, resulting in filenames like image.png.jpeg. So to find
      // the actual source image, we remove the extension and check if that
      // image exists.
      $path_info = pathinfo($image_uri);
      $converted_image_uri = $path_info['dirname'] . DIRECTORY_SEPARATOR . $path_info['filename'];
      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.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
        return new Response($this->t('Error generating image, missing source file.'), 404);
      }
      else {
        // The converted file does exist, use it as the source.
        $image_uri = $converted_image_uri;
      }
    }

Proposed resolution

  1. Wait for #2702227: Image styles for private files are serving the original instead of derivative
  2. Pass the correct image URI to the implementations of hook_file_download. The image URI should be one of the following:

Remaining tasks

as above

User interface changes

none

API changes

none

Data model changes

none

Issue fork drupal-2786735

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

zaporylie created an issue. See original summary.

zaporylie’s picture

Title: Image efect convert does not work if file is stored in private filesystem » Image effect "Convert" does not work if file is stored in private filesystem
zaporylie’s picture

Status: Postponed » Active
zaporylie’s picture

Title: Image effect "Convert" does not work if file is stored in private filesystem » Image derivative generation does not work if effect "Convert" in use and file stored in private filesystem
Priority: Normal » Major
FileSize
1.71 KB

Only patch but it would need some tests too.

zaporylie’s picture

Status: Active » Needs review
claudiu.cristea’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

We need tests. Please post also a "test only" patch that proves the bug.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jefuri’s picture

Version: 8.4.x-dev » 8.5.x-dev
Status: Needs work » Needs review
FileSize
1.64 KB

Rerolled it against the 8.5.x branch.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

eiriksm’s picture

Here is a test only patch and a patch with the test.

Did not add an interdiff, since it's literally the same as the test-only patch :)

Status: Needs review » Needs work

The last submitted patch, 12: 2786735.patch, failed testing. View results

eiriksm’s picture

Status: Needs work » Needs review
Related issues: +#2630230: Image effect convert fails when image file is in the public files root

Hm, interestingly enough it seems the logic we depend on here, does not work if the file is in the top directory.

$path_info = pathinfo($image_uri);
$converted_image_uri = $path_info['dirname'] . DIRECTORY_SEPARATOR . $path_info['filename'];
if (!file_exists($converted_image_uri)) {

This code ends up checking if private:/test_image.png exists.

While if the image was inside of "images" it would end up checking if private://images/test_image.png

This also fails for public files by the way. And is tracked in the issue #2630230: Image effect convert fails when image file is in the public files root.

So not sure if we should postpone until that one is landed?

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

TrevorBradley’s picture

+1 to patch #12 - which solves my issue over in #3072051: "Convert JPG" Image Effect doesn't work for private files

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

TrevorBradley’s picture

Hmm, something's not quite right here - patch #12 works (converting PDF to JPG) but the server seems to serve it up as the wrong file type. It displays fine on Chrome on Linux, shows up as a question mark on Safari. I open the myfile.pdf.jpeg image in a new tab, Chrome says it's having difficulty displaying the PDF.

If I move the generated myfile.pdf.jpeg to a public portion of the server, it serves up just fine.

So even with patch #12, there's something wildly wrong about the returned mimetype.

TrevorBradley’s picture

Status: Needs review » Needs work
TrevorBradley’s picture

Just starting to look at this.

I've found the issue. Originally, I was thinking that private file headers were being incorrectly generated by this block of code:

    // If using the private scheme, let other modules provide headers and
    // control access to the file.
    if ($scheme == 'private') {
      $headers = $this->moduleHandler()->invokeAll('file_download', [$image_uri]);
      if (in_array(-1, $headers) || empty($headers)) {
        throw new AccessDeniedHttpException();
      }
    }

This is using the original $image_url to generate the headers. But if the content type had changed in the derivative form (like PDF to JPEG), $headers would remain an application/pdf. I thought this should be $derivative_url, not $image_url. But $derivative_url isn't a file in the file DB table, so this method of figuring out the mimetype would fail.

What I later noticed is that derivative image's $headers are recalculated just a few lines down in the same function:

    if ($success) {
      $image = $this->imageFactory->get($derivative_uri);
      $uri = $image->getSource();
      $headers += [
        'Content-Type' => $image->getMimeType(),
        'Content-Length' => $image->getFileSize(),
      ];
      // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
      // sets response as not cacheable if the Cache-Control header is not
      // already modified. We pass in FALSE for non-private schemes for the
      // $public parameter to make sure we don't change the headers.
      return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
    }

The problem with this is that $headers['Content-Type'] and $headers['Content-Length'] are already set, and this += operator on the $headers array is not replacing existing values. As defined on php.net:

The + operator returns the right-hand array appended to the left-hand array; for keys that exist in both arrays, the elements from the left-hand array will be used, and the matching elements from the right-hand array will be ignored.

Instead, this additional block works great:

    if ($success) {
      $image = $this->imageFactory->get($derivative_uri);
      $uri = $image->getSource();
      $headers['Content-Type'] = $image->getMimeType();
      $headers['Content-Length'] = $image->getFileSize();
      // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
      // sets response as not cacheable if the Cache-Control header is not
      // already modified. We pass in FALSE for non-private schemes for the
      // $public parameter to make sure we don't change the headers.
      return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
    }

I'm not sure why the += operator was there. Presumably the headers from the derivative file are really want we really want to serve up here.

Here's a patch that should work much better.

TrevorBradley’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

PhilippVerpoort’s picture

Just to say that I installed the patch mentioned in #20, and it seems to be working on my end! JPEG images in the private file system are converted correctly, and I haven't come across any issues yet.

Hope this gets submitted soon!

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Prashant.c’s picture

Thanks to https://www.drupal.org/project/drupal/issues/2786735#comment-13378964 after doing research for 2 hours this patch worked for me on Drupal 9.3.

I was also using "Convert" on all image styles in Drupal and the Private File system and was not able to generate image styles.

Thanks to all the contributers.

bceyssens’s picture

I can confirm patch #20 works as expected!

immaculatexavier’s picture

Status: Needs work » Needs review
FileSize
4.15 KB
3.33 KB

Rerolled patch against #20 for 9.5.

ranjith_kumar_k_u’s picture

Status: Needs review » Needs work

The last submitted patch, 32: 2786735-32.patch, failed testing. View results

bceyssens’s picture

Rerolled patch against #20 for 9.4.3.

Status: Needs review » Needs work
johnle’s picture

Tested the latest patch on #34, if the private files in in the root, ie private://something.jpg.webp

$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']);

converts the uri to `private://./something.jpg, which will failed on the invoke call below

$headers = $this->moduleHandler()->invokeAll('file_download', [$image_uri]);

Can we make the converted image to remove the period of the path file is in the root something like this?

$converted_image_uri = sprintf('%s://%s%s', $this->streamWrapperManager->getScheme($derivative_uri), $path_info['dirname'] === '.' ? '' : $path_info . DIRECTORY_SEPARATOR, $path_info['filename']);
johnle’s picture

Here is the updated patch to fixed the root path to show up correctly when the converted images are in the root private path.

Anybody’s picture

#3310963: Wrong file header returned, when converting an image for example to webp seems to have the same root cause, described in #20 so the priority is definitely major here.

ravi.shankar’s picture

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tbsiqueira’s picture

I tested patch #39 and everything is working as expected.

PhilY’s picture

Patching Drupal 9.4.8 with #39 generates image styles (with WEBP convertion) in private folder.
Thanks

SocialNicheGuru’s picture

I am getting intermittent file not being found after applying the patch. The image will not appear and I cannot refresh the page to bring it back. Instead I have to flush image style cache, drush if my-image-style, then it will be regenerated.

I am not sure why the image url is what it is.

I am using the private file system. I am converting a jpg to webp
Drupal 9.5.8
php8.1

the image url is:

http://mysite/system/files/styles/my-image-style/private/2023-05/myimage.jpg.webp?itok=R0SbukIK

might be helpful: https://www.drupal8.ovh/en/tutoriels/83/get-a-image-file-path-src-on-dru...
The url seems odd.
1. On the sever the converted images are in
mysite/private/files/styles/my-image-style/private/2023-05

So the actual file is here: private/files/styles/my-image-style/private/2023-05/myimage.jpg.webp.

The url that the site accesses is the public file system mysites/files/styles/ then seems to append a path.

Is this what should be happening with the Convert function?

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

recrit made their first commit to this issue’s fork.

recrit’s picture

The test "testImageStylePrivateWithConversion" was failing since the "ImageStyleDownloadController" converted the image URI to "private://./image-test_0.png" instead of "private://image-test_0.png".

Original conversion code in "ImageStyleDownloadController":

$converted_image_uri = sprintf('%s://%s%s%s', $this->streamWrapperManager->getScheme($derivative_uri), $path_info['dirname'], DIRECTORY_SEPARATOR, $path_info['filename']);
      if (!$this->sourceImageExists($converted_image_uri, $token_is_valid)) {

To "reduce" the extensions correctly, a new utility function was created in image.module. This may not be the best place for it but I wanted it globally available. The original URI also needed to be reduced in the "image_file_download" hook so that it pass the correct original URI to the file_download hooks.
image.module:

function image_reduce_file_extensions($uri) {
  $converted_uri = $uri;
  $path_info = pathinfo(StreamWrapperManager::getTarget($uri));
  // Only convert the URI when the filename still has an extension.
  if (!empty($path_info['filename']) && pathinfo($path_info['filename'], PATHINFO_EXTENSION)) {
    $converted_uri = StreamWrapperManager::getScheme($uri) . '://';
    if (!empty($path_info['dirname']) && $path_info['dirname'] !== '.') {
      $converted_uri .= $path_info['dirname'] . DIRECTORY_SEPARATOR;
    }
    $converted_uri .= $path_info['filename'];
  }

  return $converted_uri;
}
recrit’s picture

Status: Needs work » Needs review
poker10’s picture

Status: Needs review » Needs work
@@ -216,10 +216,8 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
-      $headers += [
-        'Content-Type' => $image->getMimeType(),
-        'Content-Length' => $image->getFileSize(),
-      ];
+      $headers['Content-Type'] = $image->getMimeType();
+      $headers['Content-Length'] = $image->getFileSize();

There is a related/duplicate issue: #2786763: Incorrect headers for image derivative if effect "Convert" in use and file stored in private filesystem, which is trying to fix the headers in ImageStyleDownloadController::deliver() as well. I think we should remove the fix from this MR, so that we do not combine multiple fixes in one issue (otherwise we need an explanation, why it needs to be fixed together, because it seems to be unrelated to the original issue summary). If needed, this issue can be postponed on that fix. Thanks!

recrit’s picture

@poker10 - I reverted the file header changes in this issue and created new patches in #2786763: Incorrect headers for image derivative if effect "Convert" in use and file stored in private filesystem.

recrit’s picture

Attached is a static patch of the MR 4772 at commit 5d72a9763e.

recrit’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 51: 2786735-MR4772-5d72a9763e--20230926.diff, failed testing. View results

recrit’s picture

Status: Needs work » Needs review
SocialNicheGuru’s picture

smustgrave made their first commit to this issue’s fork.

smustgrave’s picture

Rebased the issue to run the test only

There was 1 error:
1) Drupal\Tests\image\Functional\ImageStylesPathAndUrlTest::testImageStylePrivateWithConversion
Behat\Mink\Exception\ExpectationException: Current response status code is 403, but 200 expected.
/builds/issue/drupal-2786735/vendor/behat/mink/src/WebAssert.php:794
/builds/issue/drupal-2786735/vendor/behat/mink/src/WebAssert.php:130
/builds/issue/drupal-2786735/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:221
/builds/issue/drupal-2786735/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:135
/builds/issue/drupal-2786735/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
ERRORS!
Tests: 10, Assertions: 232, Errors: 1.

Which is good.

Did some manual testing next

Installed Media library
Updated Image bundle image field to use private folder vs public
Setup a private folder
Updated thumbnail image style to convert to JPEG
Created a Media object and the thumbnail failed to generate
Applied MR 4772
Refreshed the page and the thumbnail generated

Hiding patches to make it more clear for committers.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Nice to see some progress on this old bug. I've added some comments to the MR that can be addressed.

recrit’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work
1) Drupal\Tests\image\Functional\ImageStylesPathAndUrlTest::testImageStylePrivateWithConversion
Behat\Mink\Exception\ExpectationException: Current response status code is 403, but 200 expected.

/var/www/html/web/vendor/behat/mink/src/WebAssert.php:794
/var/www/html/web/vendor/behat/mink/src/WebAssert.php:130
/var/www/html/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:221
/var/www/html/web/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:135
/var/www/html/web/vendor/phpunit/phpunit/src/Framework/TestResult.php:728

Seems the test-only feature of gitlab doesn't work with javascript tests yet. But ran locally and confirmed the test failure.

Testing this out following the title, confirmed the problem and that the MR does fix it.

Left 1 small nitpicky comment on the MR.

recrit’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks LGTM!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

We also need to add a test when someone uploads a file like with additional dots in the name not related to the convert plugin - ie something like: test.me.jpg or even test.jpg.jpg where the user has unnecessarily doubled the file extension.

viniciusrp’s picture

FileSize
4.22 KB

I recreate the patch #39 to be compatible with Drupal 10.2.4.

LeDucDuBleuet’s picture

Patch in comment #64 is working on Drupal 10.2.4 under Open Social 12.2.0.

Thank you.

fskreuz’s picture

The patch in #64 contains the fix from https://www.drupal.org/project/drupal/issues/2786763

But according to #49, #50, and #51, 2786763's fix should not be included.

GaëlG made their first commit to this issue’s fork.

recrit’s picture

Status: Needs work » Needs review
recrit’s picture

hiding patches that are not from the MR

smustgrave’s picture

Status: Needs review » Needs work

MR appears to have test failures.

A rebase may solve some of them.

recrit changed the visibility of the branch 11.x to hidden.

recrit’s picture

@smustgrave I created a clean MR !7349 but there are still failures that are not related to the changes in this issue - https://git.drupalcode.org/issue/drupal-2786735/-/pipelines/138852/failures. Failed test appears to be "Drupal\Tests\image\Functional\ImageAdminStylesTest"

I am seeing the same errors on other MRs too - example https://git.drupalcode.org/issue/drupal-2786763/-/pipelines/138806/failures. - this appears to be different.

recrit’s picture

This may be related to this issue since the failure is for a private scheme test "Drupal\Tests\image\Functional\ImageAdminStylesTest:testPreviewImageShowInPrivateScheme"

recrit’s picture

Some more debugging: The changes in #3127116: Image styles - thumbnails are broken in config page when private file system is used to target the sample images is causing this test failure. This change was committed in February 2024 and it removes the scheme when the sample images are requested. See commit fcc1ba6.

recrit’s picture

recrit’s picture

Cause of test failure:
Failed test: Drupal\Tests\image\Functional\ImageAdminStylesTest::testPreviewImageShowInPrivateScheme
The following code that was added in #3127116: Image styles - thumbnails are broken in config page when private file system is used must be after the hook_file_download has been called since that hook is expected a URI and not a file path.

    // If it is default sample.png, ignore scheme.
    if ($image_uri === $sample_image_uri) {
      $image_uri = $target;
    }

Fix implemented:
- Moved the code that removes the scheme from $image_uri to after the hook_file_download call.
- Added a check for $image_uri !== $sample_image_uri when checking whether to test the converted image uri.

recrit’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

Test only feature showed the coverage

1) Drupal\Tests\image\Functional\ImageStylesPathAndUrlTest::testImageStylePrivateWithConversion
Behat\Mink\Exception\ExpectationException: Current response status code is 403, but 200 expected.
/builds/issue/drupal-2786735/vendor/behat/mink/src/WebAssert.php:888
/builds/issue/drupal-2786735/vendor/behat/mink/src/WebAssert.php:145
/builds/issue/drupal-2786735/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:223
/builds/issue/drupal-2786735/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php:137
/builds/issue/drupal-2786735/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
ERRORS!
Tests: 10, Assertions: 232, Errors: 1.

Issue summary solution appears slightly different then the fix if that could be updated

But solution seems fine and does solve the issue so probably fine to self RTBC after updating the IS.

recrit’s picture

Issue summary: View changes
recrit’s picture

Issue summary: View changes
recrit’s picture

Status: Needs work » Reviewed & tested by the community

the "Proposed resolution" in the issue summary has been updated to reflect the final solution

alexpott’s picture

Version: 11.x-dev » 10.2.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 31f9e8a9c6 to 11.x and 36619bd1b4 to 10.3.x and 0299bec90b to 10.2.x. Thanks!

  • alexpott committed 0299bec9 on 10.2.x
    Issue #2786735 by recrit, eiriksm, ranjith_kumar_k_u, immaculatexavier,...

  • alexpott committed 36619bd1 on 10.3.x
    Issue #2786735 by recrit, eiriksm, ranjith_kumar_k_u, immaculatexavier,...

  • alexpott committed 31f9e8a9 on 11.x
    Issue #2786735 by recrit, eiriksm, ranjith_kumar_k_u, immaculatexavier,...