Problem/Motivation

When you create content with an image without crop and edit to add a crop. We do not have the image must be emptied directly croper covers (ctrl + r) to see the image correctly croper.

Proposed resolution

When you have an manipulation of visual of image you need to invalidate the correspondant cache lie to this image generated by image.style. The pattern is config:image.style.* (eg: config:image.style.image_style_machine_name).

CommentFileSizeAuthor
#43 interdiff-cache_persistence_after-2658268-43.patch.txt908 byteswoprrr
#43 cache_persistence_after-2658268-43.patch6.71 KBwoprrr
#40 cache_persistence_after-2658268-40-interdiff.txt1.35 KBmbovan
#40 cache_persistence_after-2658268-40.patch6.5 KBmbovan
#36 cache_persistence_after-2658268-36-interdiff.txt5.57 KBmbovan
#36 cache_persistence_after-2658268-36.patch5.94 KBmbovan
#36 cache_persistence_after-2658268-36-TEST.patch5.95 KBmbovan
#33 cache_persistence_after-2658268-33-interdiff.txt2.1 KBmbovan
#33 cache_persistence_after-2658268-33.patch3.14 KBmbovan
#31 cache_persistence_after-2658268-31-interdiff.txt1.8 KBmbovan
#31 cache_persistence_after-2658268-31.patch3.35 KBmbovan
#29 cache_persistence_after-2658268-29-interdiff.txt1.72 KBmbovan
#29 cache_persistence_after-2658268-29.patch2.17 KBmbovan
#25 cache_persistance_after-2658268-25-interdiff.txt797 bytesmbovan
#25 cache_persistance_after-2658268-25.patch1.96 KBmbovan
#22 cache_persistance_after-2658268-22-interdiff.txt2.17 KBmbovan
#22 cache_persistance_after-2658268-22.patch2.03 KBmbovan
#18 cache_persistance_after-2658268-18-interdiff.txt2.31 KBmbovan
#18 cache_persistance_after-2658268-18.patch1.94 KBmbovan
#13 cache_persistance_after-2658268-13-CROP.patch1.26 KBmbovan
#4 Capture d’écran 2016-01-28 à 19.53.09.png878.4 KBwoprrr
#4 Capture d’écran 2016-01-28 à 19.52.03.png811.39 KBwoprrr
#2 2658268-2.patch1.06 KBwoprrr
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

woprrr created an issue. See original summary.

woprrr’s picture

Status: Active » Needs review
FileSize
1.06 KB

That patch fix this problem and invalidate only the cache tag corresponding to an image_style.

Edit :
The patch fix another problem.
When the user decides to change the picture style directly without touching the crop, then the change was not visible without emptying the browser cache with the cache invalidation after the application of the effect this will see the changes directly.

woprrr’s picture

Another think,

I notice something that could be quite problematic with D8, the itok lie to images never changes even after a crop (or without crop after a effect modification to image). This can be very annoying in the case of using CDN akamai kind or other. The itok should change when an image is the derivative

What do you think about it ?

woprrr’s picture

an example to illustrate this think.

woprrr’s picture

Assigned: woprrr » Unassigned
Status: Needs review » Postponed (maintainer needs more info)

The previous comportement are fixed by ( https://github.com/drupal-media/crop/blob/8.x-1.x/src/Entity/Crop.php#L188 ) in last version of crop API. But i stay septic for interaction with akamai / varnish ... If path or query string not change the browser not send a query to cdn for inform the changes ?

@slashrsm you can inform me on that ?

woprrr’s picture

Status: Postponed (maintainer needs more info) » Closed (fixed)

I ve investigate in this subject and found a solutions / explications thanks @scald team @jcisio @defr.

itok must not change it as long as the file is not physically move (the path).
The browser, makes him still the HTTP query asking if the image had been altered not by chance. What we will usually reply "304 Not modified" level of response HEADER in this case no problem nav uses the cache. In case the image correctly have suffered a flush (image_path_flush) and not necessarily the flush ($uri) that it does not go far enough. The fix is ​​present in The latest version of crop_api (https://github.com/drupal-media/crop/blob/8.x-1.x/src/Entity/Crop.php#L188) with image_path_flush ( $ this-> uri-> value); the file that has an action applied imageStyle. Action that will lead to change the header of this resource and return a response '200' so the image must be recharged by the nav / CDN.

Most commercial CDN does not have an API to tell them "Attention this file to change, re-recovers before serving" (less). And it is the case of our only solution is to change the file name to work around these limitations, telling them to keep the "infinite".

woprrr’s picture

Assigned: Unassigned » slashrsm
Status: Closed (fixed) » Needs review

I think we need to add an other action like https://www.drupal.org/node/2658268#comment-10796022 because

$style->flush($path)

flush() with arg not empty the caches when we pass an uri to this function we return juste $this if the image exist @see \Drupal\image\Entity\ImageStyle::flush() we not need an violent drupal_theme_rebuild(), but we need to invalidate the cache for applicative (drupal).

For me the #2 patch works well.

miro_dietiker’s picture

Note that with NP8, we integrated with fastly and it can perfectly flush, even cache tag based.
Also you need to be careful when applying a min cache time since browsers can locally cache it without reasking for a change and your explicit flushes will never reach the browser.

IMHO the itok should change or some other thing in the URL should change if a crop changes (which doesn't).
ImageStyleDownloadController::deliver() finally uses getPathToken() and it seems that getPathToken() is not supporting anything to vary itok.
So we end up on the same problem as #2617818: Support multiple crop variants per URI and crop type

woprrr’s picture

Assigned: slashrsm » Unassigned
Status: Needs review » Postponed

@miro Yes but for my project we have an akamai & varnish and with that when you change the crop, it's very problematic. The degraded solution found to get arround this limitation is add a query string with crop values saved when ImageStyle generate / re generate an image.

function hook_file_url_alter(&$uri) {
  if (strpos($uri, 'http://') !== FALSE && strpos($uri, 'sites/default/files/styles/') !== FALSE) {
    preg_match("/http:\/\/.*\/styles\/(.*?)\/public\/(.*?)\?/", $uri, $match);
    $style = $match[1];
    $file_uri = "public://" . $match[2];

    /** @var \Drupal\image_widget_crop\ImageWidgetCropManager $image_widget_manager */
    $image_widget_manager = \Drupal::service('image_widget_crop.manager');

    /** @var \Drupal\image\Entity\ImageStyle $style */
    $style = ImageStyle::load($style);

    $crop = \Drupal\crop\Entity\Crop::findCrop($file_uri, $image_widget_manager->getEffectData($style, 'crop_type'));
    if ($crop) {
      $query_string = implode($crop->position(), '-') . '-' . implode($crop->anchor(), '-');
      $uri .= '&v=' . $query_string;
    }
  }
}
woprrr’s picture

Issue tags: +Media Initiative

ATM the D8 core file system not permit to change something in the URL CDN to inform of a change in an image. This issue is related to all imageStyle that will be bring a change without changing the destination. The previous hook fix the problem by adding a query in the URL to inform of the change in the effect on the image.

ATM we need more informations about Drupal Core to continue i go create an issue to talk about it.

vurt’s picture

Like you said, it is a general problem: All crop modules are affected. Drupal providers with Varnish (like Pantheon) take extra meassures to purge the image from Varnish, see: https://www.drupal.org/node/2148619

To make the proposed solution from #9 works fine. For responsive images I had to use similar code in template_preprocess_responsive_image().

Berdir’s picture

Status: Postponed » Active

Lets just try to convert #9 into a patch then, not sure what this is postponed. I don't think this is something that core can solve for us.

There are some interesting cases where not even having working cache invalidation (file name based, not cache tags, as files have no cache tags) helps. For example when using file entity and entity browser, and editing/croping a file when clicking on the edit button and expecting it to refresh when saving. Even when updating the HTML, which we do know, the browser dosn't reload the picture without a full page refresh.

mbovan’s picture

Status: Active » Needs review
FileSize
1.26 KB

Here is the patch based on #9 placed in crop module.

Berdir’s picture

Status: Needs review » Needs work
+++ b/crop.module
@@ -108,3 +110,26 @@ function crop_file_delete(FileInterface $file) {
+    preg_match("/http:\/\/.*\/styles\/(.*?)\/public\/(.*?)\?/", $uri, $match);
+    $style = $match[1];
+    $file_uri = "public://" . $match[2];

we should not hardcode public but parse it from the string, just like we do with the image style.

lets also add a comment to explain what we are doing.

mbovan’s picture

.

mbovan’s picture

Project: Image Widget Crop » Crop API
woprrr’s picture

Fully agree about @Berdir,

We not should use public hardcoded (in $file_uri and in pregmatch). we need to get schema with \Drupal\Core\File\FileSystem::uriScheme() ?

mbovan’s picture

I agree too. :) #13 was actually #9 as a patch file.

Tried to improve things with a new patch.

+++ b/crop.module
@@ -108,3 +110,26 @@ function crop_file_delete(FileInterface $file) {
+    $crop = Crop::findCrop($file_uri, $image_widget_manager->getEffectData($style, 'crop_type'));

I'm a bit confused with this line. In the case of editing entity with entity browser (explained in #12), this means we are looking for (the last) "manual crop" effect added in the image style that's used to preview the image on the edit form?

Let's say global image widget configuration (admin/config/media/crop-widget) uses an image style (with no manual crop) for crop preview and all crop types are selected.

In case the cropping happens using the same cropping type used in mentioned preview-image-style, the image will be updated. In any other case, it won't.

If this is not the expected behaviour, it looks to me that missing information in this function is (chosen) crop type. Then, we could use that value to load the proper crop entity.

Berdir’s picture

Status: Needs review » Needs work

Finding the crop type has nothing to do with entity browser. It's based on the image style, and it will render the entity and based on the image style that is displayed, pick the right crop type. So please don't mention browser in the comments :)

miro_dietiker’s picture

+++ b/crop.module
@@ -108,3 +111,37 @@ function crop_file_delete(FileInterface $file) {
+      $query_string = implode($crop->position(), '-') . '-' . implode($crop->anchor(), '-');
+      $uri .= '&v=' . $query_string;

I think i would prefer to see a (shortened?) hash here than raw values. It's not about the numbers. It's just about uniqueness.

We are now depending on the crop record to determine changed values.
Alternatively we could say updating a crop should touch / update the file change timestamp. That's what we need to guarantee.
Thus, the only thing we would need to lookup and hash is the file change timestamp.

And then we automatically cover replacing a file and other kinds of potential alters, too.
But then also not sure if Crop API should provide the URL alter. We could easily apply this to all managed files.

Berdir’s picture

Discussed with miro. Relying on changed of the file entity (not the physical file) sounds like an interesting idea. using image styles on something else is possible but rare I'd say.

This will simplify this (although loading a file entity based on uri needs a query and will actually be slower) and it has the advantage that we could even suggest that core ads that.

For that to work, we'll also need to resave the file entity when saving a crop.

mbovan’s picture

mbovan’s picture

In discussion with @Berdir and @miro_dietiker, it seems that #18 is acceptable patch at this point.

Created a Core followup: #2800731: Add file change timestamp to the file URI related to #20 and #21.

Edit:
Created a Crop followup: #2800739: Improve findCrop() performance

Berdir’s picture

Status: Needs review » Needs work

Yes, lets go with #18, but please update the comment.

mbovan’s picture

Comment improvement by @Berdir.

The interdiff is against #18.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

I think this is ready now.

slashrsm’s picture

Status: Reviewed & tested by the community » Needs work

#18 looks ok to me. Noticed two things:

  1. +    /** @var \Drupal\image_widget_crop\ImageWidgetCropManager $image_widget_manager */
    +    $image_widget_manager = \Drupal::service('image_widget_crop.manager');
    +
    +    /** @var \Drupal\image\Entity\ImageStyle $image_style */
    +    $image_style = ImageStyle::load($image_style);
    +
    +    // In case the image style uses "crop_type" effect, load the crop entity.
    +    if ($crop = Crop::findCrop($file_uri, $image_widget_manager->getEffectData($image_style, 'crop_type'))) {
    
    

    If this is going to live in Crop API we can't depend on Image widget crop.

  2. +++ b/crop.module
    @@ -108,3 +111,37 @@ function crop_file_delete(FileInterface $file) {
    +      $query_string = implode($crop->position(), '-') . '-' . implode($crop->anchor(), '-');
    +      $uri .= '&v=' . $query_string;
    

    I like the idea of using a shortened hash here.

EDIT: Commented on wrong hunk.

miro_dietiker’s picture

Title: Cache persistance after derivative creation » Cache persistence after derivative creation
mbovan’s picture

Removed image widget crop manager and copied almost the same code to crop_file_url_alter().

Berdir’s picture

Status: Needs review » Needs work

This looks good now I think. Since the hash is predictable, I think it actually shouldn't be too hard to extend e.g. \Drupal\Tests\crop\Kernel\CropCRUDTest::testCropSave() to generate an image style url for the test image and check that it has crop hash appended?

mbovan’s picture

Here are the tests. Didn't find any other way to test this except the one that's added here.

Also, seems there was a bug with regular expressions...

Berdir’s picture

Status: Needs review » Needs work
+++ b/crop.module
@@ -108,3 +111,45 @@ function crop_file_delete(FileInterface $file) {
+  // Parse given URI.
+  $uri_info = parse_url($uri);
+  // Use schema information to skip relative URIs.
+  $protocol = isset($uri_info['scheme']) ? in_array($uri_info['scheme'], ['http', 'https']) : FALSE;
+
+  // Process only files that are stored in "styles" directory.
+  if ($protocol && strpos($uri, PublicStream::basePath() . '/styles/') !== FALSE) {

I honestly don't know what the http/https check is for. Lets remove that, the test shows that it doesn't really work (manually calling the alter hook is cheating :))

mbovan’s picture

This is the test code that I would except to return the attached hash (based on crop that's created for the original file).

    // Build an image style derivative for the file URI.
    $image_style_uri = $this->testStyle->buildUri($file->getFileUri());
    // Creating the image style URL.
    $image_style_url = file_create_url($image_style_uri);

As file_create_url() triggers crop_file_url_alter():
- The processed URI (in crop_file_alter()) is: public://styles/test/public/sarajevo.png
- While the public stream is something like this: vfs://root/sites/simpletest/78359729/files

Does this mismatch mean we need to call file_create_url() twice: the first time to get absolute URL and the second time to assert the hash?
Or it means PublicStream::basePath() in crop_file_url_alter() is not necessary?

Status: Needs review » Needs work

The last submitted patch, 33: cache_persistence_after-2658268-33.patch, failed testing.

The last submitted patch, 33: cache_persistence_after-2658268-33.patch, failed testing.

mbovan’s picture

Switched to the web test with some support for rare cases.

Still not sure about PublicStream::basePath() in the first condition...

Uploaded a "test" patch that has changed URI in the crop entity.

The last submitted patch, 36: cache_persistence_after-2658268-36-TEST.patch, failed testing.

The last submitted patch, 36: cache_persistence_after-2658268-36-TEST.patch, failed testing.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/src/Tests/CropFunctionalTest.php
    @@ -130,4 +140,44 @@ class CropFunctionalTest extends WebTestBase {
     
    +  /**
    +   * Asserts a shortened has is added to the file URI.
    +   *
    

    hash.

  2. +++ b/src/Tests/CropFunctionalTest.php
    @@ -130,4 +140,44 @@ class CropFunctionalTest extends WebTestBase {
    +    $shortened_hash = substr(md5(implode($crop->position()) . implode($crop->anchor())), 0, 8);
    +    $this->assertTrue(strpos($altered_image_style_url, $shortened_hash) !== FALSE, 'The image style URL contains a shortened hash.');
    

    I think the actual hash isn't even that important. What would be nice is to change the crop now and make sure we get a different hash than the first one.

  3. +++ b/src/Tests/CropFunctionalTest.php
    @@ -130,4 +140,44 @@ class CropFunctionalTest extends WebTestBase {
    +    // Delete the file and the crop entity.
    +    $file->delete();
    +    $crop->delete();
    

    what's this for?

mbovan’s picture

Added a new test to assert the hash has changed.

#39.3: The UI tests executed after tries to delete the crop type via UI and fails because there are some entities using it.

woprrr’s picture

@mbovan I promise i can test your patch Fast too.

woprrr’s picture

Status: Needs review » Needs work
  1. +++ b/crop.module
    @@ -108,3 +111,47 @@ function crop_file_delete(FileInterface $file) {
    +  if (strpos($uri, PublicStream::basePath() . '/styles/') !== FALSE) {
    

    I think we need to cover all cases purposed by Drupal file, private file storage particulary with PrivateStream::basePath()i think we have few problems with this. I need your opinion @slashrsm / berdir / mbovan / miro. Can we support this in this issue or move it on other issue to cover this case ? I am sure in IWC we have users using private file storage or external file storage. That can be an complexity to support this but necessary (not sure in this issue).

  2. +++ b/src/Tests/CropFunctionalTest.php
    @@ -130,4 +140,53 @@ class CropFunctionalTest extends WebTestBase {
    +    $crop->delete();
    

    That is not required because crop api already have an mecanic when file are deleted @see crop_file_delete()

woprrr’s picture

think we need to cover all cases purposed by Drupal file, private file storage particulary with PrivateStream::basePath()i think we have few problems with this. I need your opinion @slashrsm / berdir / mbovan / miro. Can we support this in this issue or move it on other issue to cover this case ? I am sure in IWC we have users using private file storage or external file storage. That can be an complexity to support this but necessary (not sure in this issue).

Infact i think ATM that case need to be traited in another issue, is very specific to private Stream. I prefer to validate that generic and more case step by step and stop the polemic in 4X reply :P

For the second point, i ve deleted wrong crop->delete() in test because that is already deleted by file whenever you have use $file->deleted(). And add an check to unsure crop are correctly deleted for our case. It's NOT an duplicate of CRUD test testOrphanRemoval() but complementary into this functional test.

All other cases ALL WORK A CHARM for me :)

I think we have a final patch here *_* (i hope) mbovan can you test it too and i think can we pass to RTBC (My dream for that issue :D)

moshe weitzman’s picture

I was excited to try out this patch, but it seems to not work for my site. We are using the 'FocalPointScaleAndCropImageEffect' effect from Focal Point module. This effect has a CropStorage object, so definitely builds uno crop module. Yet the patch checks for if (isset($data_effect['crop_type'])) { and that check fails for this effect. It would be good if it did not. Any advice? Should this be fixed in Focal Point module?

woprrr’s picture

Assigned: Unassigned » slashrsm

@slashrsm, I think that can be merged now patch sound good and we need to have that important support with stable version !

@moshe weitzman I think that is due to an focal problem during implement of these effect ? That doesn't block that issue IMHO we can work on it on another and permit to other users to work with this patch.

woprrr’s picture

Assigned: slashrsm » Unassigned
Status: Needs review » Reviewed & tested by the community
Issue tags: +DevDaysSeville

Great LGTM :)

  • woprrr committed 0b9a105 on 8.x-1.x authored by mbovan
    Issue #2658268 by mbovan, woprrr, Berdir, miro_dietiker, slashrsm, vurt...
woprrr’s picture

Status: Reviewed & tested by the community » Fixed

GOD !!! This issue is merged *_* Now I can drink lots of beer to forget :D Just one thing not blocker for merged this issue. In picture (Classic) context the patch work well but I guess we have an specific case with responsive images and srcset to correctly refresh the picture because all images listed in srcset aren’t flagged with shorten_hash... Not sure if this works but it’s isolated problems that do be fixed in another issue if we have a bug with that.

This BIG BIG Part merged !!!

Status: Fixed » Closed (fixed)

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

Berdir’s picture

@moshe: This module currently only works with the manual crop effect added by this, if focal point has its own effect then it either has to at least match the configuration structure, then it might work or duplicate the logic here.

Anyway, this is not working very well and definitely broken for responsive images, where it doesn't even get that far: #2868339: Public folder check in crop_file_url_alter() is incorrect, does not work for responsive images