diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 131e4f68a..5d7249dd5 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -8,6 +8,7 @@ * customized by user themes. */ +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Crypt; @@ -785,6 +786,9 @@ function template_preprocess_links(&$variables) { * - http://dev.w3.org/html5/spec/Overview.html#alt * - title: The title text is displayed when the image is hovered in some * popular browsers. + * - timestamp: The changed date of the image file. It busts the browser cache + * if the image file has been modified by appending the date as + * query string. * - attributes: Associative array of attributes to be placed in the img tag. * - srcset: Array of multiple URIs and sizes/multipliers. * - sizes: The sizes attribute for viewport-based selection of images. @@ -792,8 +796,18 @@ function template_preprocess_links(&$variables) { */ function template_preprocess_image(&$variables) { if (!empty($variables['uri'])) { - $variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri'])); + if(isset($variables['timestamp'])) { + $path = UrlHelper::parse($variables['uri']); + $query = $path['query']; + $query += array('timestamp' => $variables['timestamp']); + $file_url = file_create_url($path['path']); + $variables['attributes']['src'] = $file_url . '?'. UrlHelper::buildQuery($query); + } + else { + $variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri'])); + } } + // Generate a srcset attribute conforming to the spec at // http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset if (!empty($variables['srcset'])) { @@ -1781,7 +1795,7 @@ function drupal_common_theme() { // - http://dev.w3.org/html5/spec/Overview.html#alt // The title attribute is optional in all cases, so it is omitted by // default. - 'variables' => ['uri' => NULL, 'width' => NULL, 'height' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => [], 'sizes' => NULL, 'srcset' => [], 'style_name' => NULL], + 'variables' => ['uri' => NULL, 'width' => NULL, 'height' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => [], 'sizes' => NULL, 'srcset' => [], 'style_name' => NULL, 'timestamp' => NULL], ], 'breadcrumb' => [ 'variables' => ['links' => []], diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc index bf69b9044..27410ccf4 100644 --- a/core/modules/image/image.field.inc +++ b/core/modules/image/image.field.inc @@ -62,6 +62,10 @@ function template_preprocess_image_formatter(&$variables) { $item = $variables['item']; + if ($entity = $item->entity) { + $variables['image']['#timestamp'] = $item->entity->get('changed')->value; + } + // Do not output an empty 'title' attribute. if (mb_strlen($item->title) != 0) { $variables['image']['#title'] = $item->title; diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 4c0e8cca9..c1402fa35 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -133,6 +133,7 @@ function image_theme() { 'alt' => '', 'title' => NULL, 'attributes' => [], + 'timestamp' => NULL, ], ], @@ -308,6 +309,7 @@ function template_preprocess_image_style(&$variables) { '#height' => $dimensions['height'], '#attributes' => $variables['attributes'], '#style_name' => $variables['style_name'], + '#timestamp' => $variables['timestamp'], ]; // If the current image toolkit supports this file type, prepare the URI for diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php index b1355fe05..e2fb4bbb1 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php @@ -102,12 +102,14 @@ public function _testImageFieldFormatters($scheme) { // Test that the default formatter is being used. $file = $node->{$field_name}->entity; $image_uri = $file->getFileUri(); + $timestamp = $file->get('changed')->value; $image = [ '#theme' => 'image', '#uri' => $image_uri, '#width' => 40, '#height' => 20, '#alt' => $alt, + '#timestamp' => $timestamp, ]; $default_output = str_replace("\n", NULL, $renderer->renderRoot($image)); $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.'); @@ -127,6 +129,7 @@ public function _testImageFieldFormatters($scheme) { '#width' => 40, '#height' => 20, '#alt' => $alt, + '#timestamp' => $timestamp, ]; $default_output = '' . $renderer->renderRoot($image) . ''; $this->drupalGet('node/' . $nid); @@ -162,6 +165,7 @@ public function _testImageFieldFormatters($scheme) { '#uri' => $image_uri, '#width' => 40, '#height' => 20, + '#timestamp' => $timestamp, ]; $this->drupalGet('node/' . $nid); $this->assertCacheTag($file->getCacheTags()[0]); @@ -171,7 +175,7 @@ public function _testImageFieldFormatters($scheme) { '//a[@href=:path]/img[@src=:url and @alt=:alt and @width=:width and @height=:height]', [ ':path' => $node->toUrl()->toString(), - ':url' => file_url_transform_relative(file_create_url($image['#uri'])), + ':url' => file_url_transform_relative(file_create_url($image['#uri']) . '?timestamp=' . $timestamp), ':width' => $image['#width'], ':height' => $image['#height'], ':alt' => $alt, @@ -195,6 +199,7 @@ public function _testImageFieldFormatters($scheme) { '#height' => 20, '#style_name' => 'thumbnail', '#alt' => $alt, + '#timestamp' => $timestamp, ]; $default_output = $renderer->renderRoot($image_style); $this->drupalGet('node/' . $nid); @@ -277,7 +282,7 @@ public function testImageFieldSettings() { $node_storage->resetCache([$nid]); $node = $node_storage->load($nid); $file = $node->{$field_name}->entity; - + $timestamp = $file->get('changed')->value; $url = file_url_transform_relative(ImageStyle::load('medium')->buildUrl($file->getFileUri())); $this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]')); @@ -289,6 +294,7 @@ public function testImageFieldSettings() { '#title' => $this->randomMachineName(), '#width' => 40, '#height' => 20, + '#timestamp' => $timestamp, ]; $edit = [ $field_name . '[0][alt]' => $image['#alt'], @@ -379,6 +385,7 @@ public function testImageFieldDefaultImage() { $field_storage = FieldStorageConfig::loadByName('node', $field_name); $default_image = $field_storage->getSetting('default_image'); $file = \Drupal::service('entity.repository')->loadEntityByUuid('file', $default_image['uuid']); + $timestamp = $file->get('changed')->value; $this->assertTrue($file->isPermanent(), 'The default image status is permanent.'); $image = [ '#theme' => 'image', @@ -387,6 +394,8 @@ public function testImageFieldDefaultImage() { '#title' => $title, '#width' => 40, '#height' => 20, + '#timestamp' => $timestamp, + ]; $default_output = str_replace("\n", NULL, $renderer->renderRoot($image)); $this->drupalGet('node/' . $node->id()); diff --git a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php index 8a1c2c6e7..8eb52cb55 100644 --- a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php +++ b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php @@ -105,10 +105,11 @@ public function testImageFormatterTheme() { '#url' => Url::fromUri('base:' . $path), ]; + $timestamp = $entity->image_test->entity->get('changed')->value; // Test using theme_image_formatter() with a NULL value for the alt option. $element = $base_element; $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); + $elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height]', [':path' => base_path() . $path, ':url' => $url . '×tamp=' . $timestamp, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders with a NULL value for the alt option.'); // Test using theme_image_formatter() without an image title, alt text, or @@ -116,7 +117,7 @@ public function testImageFormatterTheme() { $element = $base_element; $element['#item']->alt = ''; $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height and @alt=""]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); + $elements = $this->xpath('//a[@href=:path]/img[@src=:url and @width=:width and @height=:height and @alt=""]', [':path' => base_path() . $path, ':url' => $url . '×tamp=' . $timestamp, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders without title, alt, or path options.'); // Link the image to a fragment on the page, and not a full URL. diff --git a/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php index b8b471967..9990d5d4a 100644 --- a/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/ImageFieldAttributesTest.php @@ -99,7 +99,7 @@ public function testNodeTeaser() { // Construct the node and image URIs for testing. $node_uri = $this->node->toUrl('canonical', ['absolute' => TRUE])->toString(); - $image_uri = ImageStyle::load('medium')->buildUrl($this->file->getFileUri()); + $image_uri = ImageStyle::load('medium')->buildUrl($this->file->getFileUri()) . '×tamp=' . $this->file->get('changed')->value;; // Test relations from node to image. $expected_value = [ diff --git a/core/modules/rdf/tests/src/Functional/StandardProfileTest.php b/core/modules/rdf/tests/src/Functional/StandardProfileTest.php index 8355c19c7..5646be0d2 100644 --- a/core/modules/rdf/tests/src/Functional/StandardProfileTest.php +++ b/core/modules/rdf/tests/src/Functional/StandardProfileTest.php @@ -227,7 +227,7 @@ protected function doFrontPageRdfaTests() { // @todo Once the image points to the original instead of the processed // image, move this to testArticleProperties(). $image_file = $this->article->get('field_image')->entity; - $image_uri = ImageStyle::load('medium')->buildUrl($image_file->getFileUri()); + $image_uri = ImageStyle::load('medium')->buildUrl($image_file->getFileUri()) . '×tamp=' . $this->file->get('changed')->value;; $expected_value = [ 'type' => 'uri', 'value' => $image_uri, @@ -256,11 +256,12 @@ protected function doArticleRdfaTests() { // Test the comment properties displayed on articles. $this->assertRdfaNodeCommentProperties($graph); + $timestamp = $this->article->get('field_image')->entity->get('changed')->value; // @todo Once the image points to the original instead of the processed // image, move this to testArticleProperties(). $expected_value = [ 'type' => 'uri', - 'value' => $this->imageUri, + 'value' => $this->imageUri . '×tamp=' . $timestamp, ]; $this->assertTrue($graph->hasProperty($this->articleUri, 'http://schema.org/image', $expected_value), "Article image was found (schema:image)."); } diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php index ac3a0f007..eb54dbd1d 100644 --- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php @@ -186,13 +186,16 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = $node = $node_storage->load($nid); // Test that the default formatter is being used. - $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); + $file = File::load($node->{$field_name}->target_id); + $image_uri = $file->getFileUri(); + $timestamp = $file->get('changed')->value; $image = [ '#theme' => 'image', '#uri' => $image_uri, '#width' => 360, '#height' => 240, '#alt' => $alt, + '#timestamp' => $timestamp, ]; $default_output = str_replace("\n", NULL, $renderer->renderRoot($image)); $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.');