diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php index 06ae05febb..686564f73b 100644 --- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php +++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php @@ -140,7 +140,7 @@ public function testEntityDisplaySettings() { $this->assertSame($expected, $component); $display = EntityViewDisplay::load('node.story.default'); $expected['type'] = 'file_url_plain'; - $expected['settings'] = []; + $expected['settings'] = ['absolute_url' => FALSE]; $component = $display->getComponent('field_test_filefield'); $this->assertSame($expected, $component); diff --git a/core/modules/file/config/schema/file.schema.yml b/core/modules/file/config/schema/file.schema.yml index f94fbec950..43a21866e7 100644 --- a/core/modules/file/config/schema/file.schema.yml +++ b/core/modules/file/config/schema/file.schema.yml @@ -124,6 +124,10 @@ field.formatter.settings.file_table: field.formatter.settings.file_url_plain: type: mapping label: 'URL to file format settings' + mapping: + absolute_url: + type: boolean + label: 'Render as absolute URL' field.widget.settings.file_generic: type: mapping diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php index 73a8aed0f8..8770b84b20 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/UrlPlainFormatter.php @@ -3,6 +3,7 @@ namespace Drupal\file\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\file\FileInterface; /** @@ -18,6 +19,42 @@ */ class UrlPlainFormatter extends FileFormatterBase { + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $settings = parent::defaultSettings(); + + $settings['absolute_url'] = FALSE; + + return $settings; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + + $form['absolute_url'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Render as absolute url'), + '#description' => $this->t('If checked, links will be rendered as absolute urls.'), + '#default_value' => $this->getSetting('absolute_url'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary[] = $this->getSetting('absolute_url') ? $this->t('Rendered as absolute url') : $this->t('Rendered as relative url'); + + return $summary; + } + /** * {@inheritdoc} */ @@ -27,11 +64,16 @@ public function viewElements(FieldItemListInterface $items, $langcode) { foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { assert($file instanceof FileInterface); $elements[$delta] = [ - '#markup' => $file->createFileUrl(), + '#markup' => $file->createFileUrl(!$this->getSetting('absolute_url')), '#cache' => [ 'tags' => $file->getCacheTags(), ], ]; + + // Add url.site cache context if url is absolute URL. + if ($this->getSetting('absolute_url')) { + $elements[$delta]['#cache']['contexts'] = ['url.site']; + } } return $elements; diff --git a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php index 3e342dae4c..a0ab637294 100644 --- a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php @@ -241,4 +241,59 @@ public function testDescriptionDefaultFileFieldDisplay() { $this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]', $description); } + /** + * Test "Absolute url" settings for "file_url_plain" formatter. + */ + public function testAbsoluteFileUrlFormatterValue() { + $node_storage = $this->container->get('entity_type.manager')->getStorage('node'); + $field_name = strtolower($this->randomMachineName()); + $type_name = 'article'; + $field_storage_settings = [ + 'display_field' => '1', + 'display_default' => '1', + 'cardinality' => '1', + ]; + $field_settings = [ + 'file_directory' => '', + ]; + $widget_settings = []; + $this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings); + + $display_options = [ + 'type' => 'file_url_plain', + 'settings' => [ + 'absolute_url' => TRUE, + ], + ]; + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', $type_name); + $display->setComponent($field_name, $display_options) + ->save(); + + $test_file = $this->getTestFile('text'); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node_storage->resetCache([$nid]); + $node = $node_storage->load($nid); + $file = $node->{$field_name}->entity; + $expected_url = \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri()); + $this->assertEquals($expected_url, $node->{$field_name}->view($display_options)[0]['#markup']); + $this->assertContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + + // Disable the "absolute_url" option and validate the cache context. + $display_options = [ + 'type' => 'file_url_plain', + 'settings' => [ + 'absolute_url' => FALSE, + ], + ]; + $display = \Drupal::service('entity_display.repository') + ->getViewDisplay('node', $type_name); + $display->setComponent($field_name, $display_options) + ->save(); + $this->assertNotContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + + } + } diff --git a/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php b/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php index 20dfa742fd..e557c49d95 100644 --- a/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php +++ b/core/modules/file/tests/src/FunctionalJavascript/FileManagedFileElementTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\file\FunctionalJavascript; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\file\Functional\FileFieldCreationTrait; /** * Tests the 'managed_file' element type. @@ -10,6 +11,7 @@ * @group file */ class FileManagedFileElementTest extends WebDriverTestBase { + use FileFieldCreationTrait; /** * {@inheritdoc} @@ -106,6 +108,46 @@ public function testManagedFile() { } } + /** + * Test "Absolute url" settings on field display mode settings form. + */ + public function testAbsoluteFileUrlFormatterConfiguration() { + $field_name = strtolower($this->randomMachineName()); + $type_name = 'article'; + $field_storage_settings = [ + 'display_field' => '1', + 'display_default' => '1', + 'cardinality' => '1', + ]; + $field_settings = [ + 'description_field' => '1', + ]; + $widget_settings = []; + $this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings); + + $edit = [ + "fields[$field_name][type]" => 'file_url_plain', + ]; + $this->drupalGet("admin/structure/types/manage/$type_name/display"); + $this->submitForm($edit, 'Save'); + $this->assertSession()->responseContains('Your settings have been saved.'); + + $this->drupalGet("admin/structure/types/manage/$type_name/display"); + $page = $this->getSession()->getPage(); + $page->pressButton("${field_name}_settings_edit"); + $this->assertSession()->waitForElement('css', '.ajax-new-content'); + $edit = [ + "fields[${field_name}][settings_edit_form][settings][absolute_url]" => TRUE, + ]; + foreach ($edit as $name => $value) { + $page->fillField($name, $value); + } + $page->pressButton("${field_name}_plugin_settings_update"); + $this->assertSession()->waitForElement('css', '.field-plugin-summary-cell > .ajax-new-content'); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Rendered as absolute url'); + } + /** * Retrieves the fid of the last inserted file. */ diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 5368f85f4c..440264dc7d 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -157,6 +157,9 @@ field.formatter.settings.image_url: image_style: type: string label: 'Image style' + absolute_url: + type: boolean + label: 'Render as absolute URL' field.widget.settings.image_image: type: mapping diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php index dc4557949c..578aeb3e45 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Session\AccountInterface; @@ -39,6 +40,13 @@ class ImageUrlFormatter extends ImageFormatterBase { */ protected $currentUser; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Constructs an ImageFormatter object. * @@ -60,11 +68,14 @@ class ImageUrlFormatter extends ImageFormatterBase { * The image style storage. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, EntityStorageInterface $image_style_storage, AccountInterface $current_user) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, EntityStorageInterface $image_style_storage, AccountInterface $current_user, FileUrlGeneratorInterface $file_url_generator) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); $this->imageStyleStorage = $image_style_storage; $this->currentUser = $current_user; + $this->fileUrlGenerator = $file_url_generator; } /** @@ -81,6 +92,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['third_party_settings'], $container->get('entity_type.manager')->getStorage('image_style'), $container->get('current_user'), + $container->get('file_url_generator') ); } @@ -89,6 +101,7 @@ public static function create(ContainerInterface $container, array $configuratio */ public static function defaultSettings() { return [ + 'absolute_url' => FALSE, 'image_style' => '', ]; } @@ -99,6 +112,13 @@ public static function defaultSettings() { public function settingsForm(array $form, FormStateInterface $form_state) { $element = parent::settingsForm($form, $form_state); + $element['absolute_url'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Render as absolute url'), + '#description' => $this->t('If checked, links will be rendered as absolute urls.'), + '#default_value' => $this->getSetting('absolute_url'), + ]; + unset($element['image_link'], $element['image_loading']); $image_styles = image_style_options(FALSE); @@ -139,7 +159,9 @@ public function settingsSummary() { $summary[] = $this->t('Original image'); } - return array_merge($summary, parent::settingsSummary()); + $summary[] = $this->getSetting('absolute_url') ? $this->t('Rendered as absolute url') : $this->t('Rendered as relative url'); + + return $summary; } /** @@ -163,6 +185,11 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $image_uri = $image->getFileUri(); $url = $image_style ? $file_url_generator->transformRelative($image_style->buildUrl($image_uri)) : $file_url_generator->generateString($image_uri); + // Generate absolute url for the image. + if (!$this->getSetting('absolute_url')) { + $url = $this->fileUrlGenerator->generateString($url); + } + // Add cacheability metadata from the image and image style. $cacheability = CacheableMetadata::createFromObject($image); if ($image_style) { @@ -171,6 +198,11 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $elements[$delta] = ['#markup' => $url]; $cacheability->applyTo($elements[$delta]); + + // Add url.site cache context if url is absolute URL. + if ($this->getSetting('absolute_url')) { + array_push($elements[$delta]['#cache']['contexts'], 'url.site'); + } } return $elements; } diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php index 7502129a79..868d1734d5 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php @@ -229,12 +229,41 @@ public function _testImageFieldFormatters($scheme) { 'type' => 'image_url', 'settings' => [ 'image_style' => 'thumbnail', + 'absolute_url' => TRUE, ], ]; $display = \Drupal::service('entity_display.repository')->getViewDisplay('node', $node->getType(), 'default'); $display->setComponent($field_name, $display_options)->save(); $this->drupalGet("admin/structure/types/manage/" . $node->getType() . "/display"); $this->assertSession()->responseContains('Image style: Thumbnail (100×100)'); + + $expected_url = \Drupal::service('file_url_generator')->generateAbsoluteString($image_uri); + $this->assertEquals($expected_url, $node->{$field_name}->view($display_options)[0]['#markup']); + if ($scheme === 'public') { + $this->assertContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + } + if ($scheme === 'private') { + $this->assertEquals($expected_url, $node->{$field_name}->view($display_options)[0]['#markup']); + $this->assertContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + $this->drupalLogout(); + } + + // Disable absolute_url option again to validate cache contexts. + $display_options = [ + 'type' => 'image_url', + 'settings' => [ + 'image_style' => '', + 'absolute_url' => FALSE, + ], + ]; + $display = \Drupal::service('entity_display.repository')->getViewDisplay('node', $node->getType()); + $display->setComponent($field_name, $display_options)->save(); + if ($scheme === 'public') { + $this->assertNotContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + } + elseif ($scheme === 'private') { + $this->assertNotContains('url.site', $node->{$field_name}->view($display_options)[0]['#cache']['contexts']); + } } /**