From 3e4b17a7c9a926021202140022011bbefac29e99 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Fri, 9 Jul 2021 14:18:01 +0100 Subject: [PATCH] Issue #2669074 by kim.pepper, Berdir, gaydabura, andypost, ravi.shankar, KapilV, Wim Leers, bradjones1, nickolaj, alexpott, mondrake, 20th, yogeshmpawar, gaurav.kapoor, voleger, Pavan B S, Neslee Canil Pinto, aerozeppelin, Cameron Tod, daffie, dhruveshdtripathi, abramm, Nono95230, s.messaris, Mile23, claudiu.cristea, larowlan, catch, jibran, SpadXIII: Convert file_create_url() & file_url_transform_relative() to service, deprecate it --- core/core.services.yml | 10 +- core/includes/file.inc | 98 ++----- core/includes/theme.inc | 20 +- .../Core/Asset/CssCollectionRenderer.php | 19 +- core/lib/Drupal/Core/Asset/CssOptimizer.php | 44 ++- .../Core/Asset/JsCollectionRenderer.php | 19 +- .../InvalidStreamWrapperException.php | 9 + .../lib/Drupal/Core/File/FileUrlGenerator.php | 229 ++++++++++++++++ .../Core/File/FileUrlGeneratorInterface.php | 97 +++++++ core/lib/Drupal/Core/File/file.api.php | 6 +- .../Core/Render/Element/ImageButton.php | 2 +- .../Drupal/Core/Template/TwigExtension.php | 21 +- .../tests/src/Functional/ImportOpmlTest.php | 4 +- .../ckeditor/src/Plugin/Editor/CKEditor.php | 28 +- .../tests/modules/src/Form/AjaxCssForm.php | 29 +- .../Functional/CKEditorToolbarButtonTest.php | 4 +- .../tests/src/Kernel/CKEditorTest.php | 36 ++- core/modules/color/color.module | 2 +- .../src/ColorSystemBrandingBlockAlter.php | 2 +- .../color/tests/src/Functional/ColorTest.php | 4 +- core/modules/file/file.module | 18 +- core/modules/file/src/ComputedFileUrl.php | 4 +- core/modules/file/src/Entity/File.php | 8 +- core/modules/file/src/FileInterface.php | 3 +- .../BaseFieldFileFormatterBase.php | 71 ++++- .../Field/FieldFormatter/FileUriFormatter.php | 5 +- .../file/src/Plugin/views/field/File.php | 45 ++- .../tests/src/Functional/DownloadTest.php | 29 +- .../src/Functional/FileFieldDisplayTest.php | 4 +- .../Functional/FileFieldRSSContentTest.php | 2 +- .../tests/src/Functional/FileListingTest.php | 2 +- .../tests/src/Functional/FilePrivateTest.php | 14 +- .../src/Functional/FileTokenReplaceTest.php | 2 +- .../Formatter/FileVideoFormatterTest.php | 2 +- .../Functional/Hal/FileHalJsonAnonTest.php | 2 +- .../Hal/FileUploadHalJsonTestBase.php | 2 +- .../PrivateFileOnTranslatedEntityTest.php | 4 +- .../file/tests/src/Kernel/FileUrlTest.php | 2 +- .../Formatter/FileEntityFormatterTest.php | 18 +- core/modules/filter/filter.module | 5 +- core/modules/image/image.admin.inc | 8 +- core/modules/image/src/Entity/ImageStyle.php | 4 +- .../modules/image/src/ImageStyleInterface.php | 2 +- .../Field/FieldFormatter/ImageFormatter.php | 30 +- .../FieldFormatter/ImageUrlFormatter.php | 5 +- .../src/Functional/ImageAdminStylesTest.php | 15 +- .../src/Functional/ImageDimensionsTest.php | 8 +- .../Functional/ImageEffect/ConvertTest.php | 2 +- .../src/Functional/ImageFieldDisplayTest.php | 16 +- .../Functional/ImageStylesPathAndUrlTest.php | 2 +- .../src/Kernel/ImageThemeFunctionTest.php | 4 +- .../Functional/JsonApiFunctionalTestBase.php | 2 +- .../Functional/Hal/MediaHalJsonAnonTest.php | 2 +- .../CKEditorIntegrationTest.php | 4 +- .../FunctionalJavascript/MediaDisplayTest.php | 4 +- .../MediaSourceImageTest.php | 4 +- .../MediaStandardProfileTest.php | 19 +- .../tests/src/Kernel/MediaEmbedFilterTest.php | 2 +- .../src/Functional/NodeRSSContentTest.php | 10 +- .../Functional/FileFieldAttributesTest.php | 2 +- .../responsive_image/responsive_image.module | 8 +- .../ResponsiveImageFieldDisplayTest.php | 29 +- .../EntityReferenceFieldItemNormalizer.php | 5 +- .../src/Form/FormTestDisabledElementsForm.php | 1 + .../Functional/System/RetrieveFileTest.php | 2 +- .../tests/src/Functional/System/ThemeTest.php | 10 +- .../src/Functional/Theme/EngineTwigTest.php | 4 +- .../src/Functional/TaxonomyImageTest.php | 4 +- .../tests/src/Functional/UserPictureTest.php | 2 +- .../Core/Asset/AttachedAssetsTest.php | 30 +- .../Core/File/FileSystemDeprecationTest.php | 26 ++ .../Core/File/FileUrlGeneratorTest.php | 257 ++++++++++++++++++ .../Core/File/StreamWrapperTest.php | 11 +- .../Core/File/UrlRewritingTest.php | 120 -------- .../Core/File/UrlTransformRelativeTest.php | 4 +- .../KernelTests/Core/Theme/ImageTest.php | 19 +- .../Core/Theme/ThemeSettingsTest.php | 4 +- .../Drupal/Tests/BrowserHtmlDebugTrait.php | 4 +- .../Asset/CssCollectionRendererUnitTest.php | 194 +++++-------- .../Tests/Core/Asset/CssOptimizerUnitTest.php | 75 ++--- .../Tests/Core/Template/TwigExtensionTest.php | 27 +- 81 files changed, 1303 insertions(+), 603 deletions(-) create mode 100644 core/lib/Drupal/Core/File/Exception/InvalidStreamWrapperException.php create mode 100644 core/lib/Drupal/Core/File/FileUrlGenerator.php create mode 100644 core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php create mode 100644 core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php delete mode 100644 core/tests/Drupal/KernelTests/Core/File/UrlRewritingTest.php diff --git a/core/core.services.yml b/core/core.services.yml index f1b8355e793..9b35d53c36e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -392,6 +392,9 @@ services: file_system: class: Drupal\Core\File\FileSystem arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file'] + file_url_generator: + class: Drupal\Core\File\FileUrlGenerator + arguments: ['@stream_wrapper_manager', '@request_stack', '@module_handler'] form_builder: class: Drupal\Core\Form\FormBuilder arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token'] @@ -1580,12 +1583,13 @@ services: arguments: ['@settings'] asset.css.collection_renderer: class: Drupal\Core\Asset\CssCollectionRenderer - arguments: [ '@state' ] + arguments: [ '@state', '@file_url_generator' ] asset.css.collection_optimizer: class: Drupal\Core\Asset\CssCollectionOptimizer arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@asset.css.dumper', '@state', '@file_system'] asset.css.optimizer: class: Drupal\Core\Asset\CssOptimizer + arguments: ['@file_url_generator'] asset.css.collection_grouper: class: Drupal\Core\Asset\CssCollectionGrouper asset.css.dumper: @@ -1593,7 +1597,7 @@ services: arguments: ['@file_system'] asset.js.collection_renderer: class: Drupal\Core\Asset\JsCollectionRenderer - arguments: [ '@state' ] + arguments: [ '@state', '@file_url_generator' ] asset.js.collection_optimizer: class: Drupal\Core\Asset\JsCollectionOptimizer arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@asset.js.dumper', '@state', '@file_system'] @@ -1634,7 +1638,7 @@ services: - { name: service_collector, tag: 'twig.extension', call: addExtension } twig.extension: class: Drupal\Core\Template\TwigExtension - arguments: ['@renderer', '@url_generator', '@theme.manager', '@date.formatter'] + arguments: ['@renderer', '@url_generator', '@theme.manager', '@date.formatter', '@file_url_generator'] tags: - { name: twig.extension, priority: 100 } # @todo Figure out what to do about debugging functions. diff --git a/core/includes/file.inc b/core/includes/file.inc index 2486e6689e9..83812983a32 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -5,9 +5,8 @@ * API for handling file uploads and server file management. */ -use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\File\Exception\InvalidStreamWrapperException; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\StreamWrapper\StreamWrapperManager; /** * @defgroup file File interface @@ -47,58 +46,24 @@ const FILE_STATUS_PERMANENT = 1; * '/', nothing is done and the same string is returned. If a stream wrapper * could not be found to generate an external URL, then FALSE is returned. * + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. + * Use the appropriate method on \Drupal\Core\File\FileUrlGeneratorInterface + * instead. + * + * @see https://www.drupal.org/node/2940031 * @see https://www.drupal.org/node/515192 - * @see file_url_transform_relative() + * @see \Drupal\Core\File\FileUrlGeneratorInterface::generate() + * @see \Drupal\Core\File\FileUrlGeneratorInterface::generateString() + * @see \Drupal\Core\File\FileUrlGeneratorInterface::generateAbsoluteString() + * @see \Drupal\Core\File\FileUrlGeneratorInterface::transformRelative() */ function file_create_url($uri) { - // Allow the URI to be altered, e.g. to serve a file from a CDN or static - // file server. - \Drupal::moduleHandler()->alter('file_url', $uri); - - $scheme = StreamWrapperManager::getScheme($uri); - - if (!$scheme) { - // Allow for: - // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) - // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to - // http://example.com/bar.jpg by the browser when viewing a page over - // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) - // Both types of relative URIs are characterized by a leading slash, hence - // we can use a single check. - if (mb_substr($uri, 0, 1) == '/') { - return $uri; - } - else { - // If this is not a properly formatted stream, then it is a shipped file. - // Therefore, return the urlencoded URI with the base URL prepended. - $options = UrlHelper::parse($uri); - $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']); - // Append the query. - if ($options['query']) { - $path .= '?' . UrlHelper::buildQuery($options['query']); - } - - // Append fragment. - if ($options['fragment']) { - $path .= '#' . $options['fragment']; - } - - return $path; - } + @trigger_error('file_create_url() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use the appropriate method on \Drupal\Core\File\FileUrlGeneratorInterface instead. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + try { + return \Drupal::service('file_url_generator')->generateAbsoluteString($uri); } - elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') { - // Check for HTTP and data URI-encoded URLs so that we don't have to - // implement getExternalUrl() for the HTTP and data schemes. - return $uri; - } - else { - // Attempt to return an external URL using the appropriate wrapper. - if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) { - return $wrapper->getExternalUrl(); - } - else { - return FALSE; - } + catch (InvalidStreamWrapperException $e) { + return FALSE; } } @@ -109,38 +74,23 @@ function file_create_url($uri) { * content errors when using HTTPS + HTTP. * * @param string $file_url - * A file URL of a local file as generated by file_create_url(). + * A file URL of a local file as generated by + * FileUrlGeneratorInterface::generateString(). * * @return string * If the file URL indeed pointed to a local file and was indeed absolute, * then the transformed, relative URL to the local file. Otherwise: the * original value of $file_url. * - * @see file_create_url() + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. + * Use \Drupal\Core\File\FileUrlGenerator::transformRelative() instead. + * + * @see https://www.drupal.org/node/2940031 + * @see \Drupal\Core\File\FileUrlGeneratorInterface::transformRelative() */ function file_url_transform_relative($file_url) { - // Unfortunately, we pretty much have to duplicate Symfony's - // Request::getHttpHost() method because Request::getPort() may return NULL - // instead of a port number. - $request = \Drupal::request(); - $host = $request->getHost(); - $scheme = $request->getScheme(); - $port = $request->getPort() ?: 80; - - // Files may be accessible on a different port than the web request. - $file_url_port = parse_url($file_url, PHP_URL_PORT) ?? $port; - if ($file_url_port != $port) { - return $file_url; - } - - if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { - $http_host = $host; - } - else { - $http_host = $host . ':' . $port; - } - - return preg_replace('|^https?://' . preg_quote($http_host, '|') . '|', '', $file_url); + @trigger_error('file_url_transform_relative() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\File\FileUrlGenerator::transformRelative() instead. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + return \Drupal::service('file_url_generator')->transformRelative($file_url); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 1b3e34f00a6..d2f65b2bfcc 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -347,13 +347,16 @@ function theme_get_setting($setting_name, $theme = NULL) { } } + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + // Generate the path to the logo image. if ($cache[$theme]->get('logo.use_default')) { $logo = \Drupal::service('theme.initialization')->getActiveThemeByName($theme)->getLogo(); - $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo))); + $cache[$theme]->set('logo.url', $file_url_generator->generateString($logo)); } elseif ($logo_path = $cache[$theme]->get('logo.path')) { - $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo_path))); + $cache[$theme]->set('logo.url', $file_url_generator->generateString($logo_path)); } // Generate the path to the favicon. @@ -361,14 +364,14 @@ function theme_get_setting($setting_name, $theme = NULL) { $favicon_path = $cache[$theme]->get('favicon.path'); if ($cache[$theme]->get('favicon.use_default')) { if (file_exists($favicon = $theme_object->getPath() . '/favicon.ico')) { - $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon))); + $cache[$theme]->set('favicon.url', $file_url_generator->generateString($favicon)); } else { - $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url('core/misc/favicon.ico'))); + $cache[$theme]->set('favicon.url', $file_url_generator->generateString('core/misc/favicon.ico')); } } elseif ($favicon_path) { - $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon_path))); + $cache[$theme]->set('favicon.url', $file_url_generator->generateString($favicon_path)); } else { $cache[$theme]->set('features.favicon', FALSE); @@ -820,8 +823,11 @@ function template_preprocess_links(&$variables) { * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 */ function template_preprocess_image(&$variables) { + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + if (!empty($variables['uri'])) { - $variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri'])); + $variables['attributes']['src'] = $file_url_generator->generateString($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 @@ -829,7 +835,7 @@ function template_preprocess_image(&$variables) { $srcset = []; foreach ($variables['srcset'] as $src) { // URI is mandatory. - $source = file_url_transform_relative(file_create_url($src['uri'])); + $source = $file_url_generator->generateString($src['uri']); if (isset($src['width']) && !empty($src['width'])) { $source .= ' ' . $src['width']; } diff --git a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php index 13c53e24b4c..e81beab5df9 100644 --- a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php +++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Asset; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\State\StateInterface; /** @@ -16,14 +17,28 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface { */ protected $state; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Constructs a CssCollectionRenderer. * * @param \Drupal\Core\State\StateInterface $state * The state key/value store. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. */ - public function __construct(StateInterface $state) { + public function __construct(StateInterface $state, FileUrlGeneratorInterface $file_url_generator = NULL) { $this->state = $state; + if (!$file_url_generator) { + @trigger_error('Calling CssCollectionRenderer::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and will be required before drupal:10.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; } /** @@ -55,7 +70,7 @@ class CssCollectionRenderer implements AssetCollectionRendererInterface { switch ($css_asset['type']) { // For file items, output a LINK tag for file CSS assets. case 'file': - $element['#attributes']['href'] = file_url_transform_relative(file_create_url($css_asset['data'])); + $element['#attributes']['href'] = $this->fileUrlGenerator->generateString($css_asset['data']); // Only add the cache-busting query string if this isn't an aggregate // file. if (!isset($css_asset['preprocessed'])) { diff --git a/core/lib/Drupal/Core/Asset/CssOptimizer.php b/core/lib/Drupal/Core/Asset/CssOptimizer.php index e939b3d0542..4ba7a24f4d0 100644 --- a/core/lib/Drupal/Core/Asset/CssOptimizer.php +++ b/core/lib/Drupal/Core/Asset/CssOptimizer.php @@ -4,6 +4,7 @@ namespace Drupal\Core\Asset; use Drupal\Component\Utility\Unicode; use Drupal\Core\StreamWrapper\StreamWrapperManager; +use Drupal\Core\File\FileUrlGeneratorInterface; /** * Optimizes a CSS asset. @@ -17,6 +18,27 @@ class CssOptimizer implements AssetOptimizerInterface { */ public $rewriteFileURIBasePath; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + + /** + * Constructs a CssOptimizer. + * + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. + */ + public function __construct(FileUrlGeneratorInterface $file_url_generator = NULL) { + if (!$file_url_generator) { + @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0. The $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; + } + /** * {@inheritdoc} */ @@ -258,7 +280,27 @@ class CssOptimizer implements AssetOptimizerInterface { $last = $path; $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); } - return 'url(' . file_url_transform_relative(file_create_url($path)) . ')'; + return 'url(' . $this->getFileUrlGenerator()->generateString($path) . ')'; + } + + /** + * Returns the file URL generator. + * + * This is provided for BC as sub-classes may not call the parent constructor. + * + * @return \Drupal\Core\File\FileUrlGeneratorInterface + * The file URL generator. + * + * @internal + * This can be removed in Drupal 10.0.x when the constructor deprecation is + * removed. + */ + private function getFileUrlGenerator(): FileUrlGeneratorInterface { + if (!$this->fileUrlGenerator) { + @trigger_error('Calling CssOptimizer::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0. The $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $this->fileUrlGenerator = \Drupal::service('file_url_generator'); + } + return $this->fileUrlGenerator; } } diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php index 99d0e14f545..d4a618fd6b0 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Asset; use Drupal\Component\Serialization\Json; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\State\StateInterface; /** @@ -17,14 +18,28 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface { */ protected $state; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Constructs a JsCollectionRenderer. * * @param \Drupal\Core\State\StateInterface $state * The state key/value store. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. */ - public function __construct(StateInterface $state) { + public function __construct(StateInterface $state, FileUrlGeneratorInterface $file_url_generator = NULL) { $this->state = $state; + if (!$file_url_generator) { + @trigger_error('Calling JsCollectionRenderer::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0. The $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; } /** @@ -74,7 +89,7 @@ class JsCollectionRenderer implements AssetCollectionRendererInterface { case 'file': $query_string = $js_asset['version'] == -1 ? $default_query_string : 'v=' . $js_asset['version']; $query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?'; - $element['#attributes']['src'] = file_url_transform_relative(file_create_url($js_asset['data'])); + $element['#attributes']['src'] = $this->fileUrlGenerator->generateString($js_asset['data']); // Only add the cache-busting query string if this isn't an aggregate // file. if (!isset($js_asset['preprocessed'])) { diff --git a/core/lib/Drupal/Core/File/Exception/InvalidStreamWrapperException.php b/core/lib/Drupal/Core/File/Exception/InvalidStreamWrapperException.php new file mode 100644 index 00000000000..aca01946374 --- /dev/null +++ b/core/lib/Drupal/Core/File/Exception/InvalidStreamWrapperException.php @@ -0,0 +1,9 @@ +streamWrapperManager = $stream_wrapper_manager; + $this->requestStack = $request_stack; + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public function generateString(string $uri): string { + return $this->doGenerateString($uri, TRUE); + } + + /** + * {@inheritdoc} + */ + public function generateAbsoluteString(string $uri): string { + return $this->doGenerateString($uri, FALSE); + } + + /** + * Creates an absolute web-accessible URL string. + * + * @param string $uri + * The URI to a file for which we need an external URL, or the path to a + * shipped file. + * @param bool $relative + * Whether to return an relative or absolute URL. + * + * @return string + * An absolute string containing a URL that may be used to access the + * file. + * + * @throws \Drupal\Core\File\Exception\InvalidStreamWrapperException + * If a stream wrapper could not be found to generate an external URL. + */ + protected function doGenerateString(string $uri, bool $relative): string { + // Allow the URI to be altered, e.g. to serve a file from a CDN or static + // file server. + $this->moduleHandler->alter('file_url', $uri); + + $scheme = StreamWrapperManager::getScheme($uri); + + if (!$scheme) { + $baseUrl = $relative ? base_path() : $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost() . base_path(); + return $this->generatePath($baseUrl, $uri); + } + elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') { + // Check for HTTP and data URI-encoded URLs so that we don't have to + // implement getExternalUrl() for the HTTP and data schemes. + return $relative ? $this->transformRelative($uri) : $uri; + } + elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) { + // Attempt to return an external URL using the appropriate wrapper. + $externalUrl = $wrapper->getExternalUrl(); + return $relative ? $this->transformRelative($externalUrl) : $externalUrl; + } + throw new InvalidStreamWrapperException(); + } + + /** + * Generate a URL path. + * + * @param string $base_url + * The base URL. + * @param string $uri + * The URI. + * + * @return string + * The URL path. + */ + protected function generatePath(string $base_url, string $uri): string { + // Allow for: + // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) + // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to + // http://example.com/bar.jpg by the browser when viewing a page over + // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) + // Both types of relative URIs are characterized by a leading slash, hence + // we can use a single check. + if (mb_substr($uri, 0, 1) == '/') { + return $uri; + } + else { + // If this is not a properly formatted stream, then it is a shipped + // file. Therefore, return the urlencoded URI with the base URL + // prepended. + $options = UrlHelper::parse($uri); + $path = $base_url . UrlHelper::encodePath($options['path']); + // Append the query. + if ($options['query']) { + $path .= '?' . UrlHelper::buildQuery($options['query']); + } + + // Append fragment. + if ($options['fragment']) { + $path .= '#' . $options['fragment']; + } + + return $path; + } + } + + /** + * {@inheritdoc} + */ + public function generate(string $uri): Url { + // Allow the URI to be altered, e.g. to serve a file from a CDN or static + // file server. + $this->moduleHandler->alter('file_url', $uri); + + $scheme = StreamWrapperManager::getScheme($uri); + + if (!$scheme) { + // Allow for: + // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) + // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to + // http://example.com/bar.jpg by the browser when viewing a page over + // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) + // Both types of relative URIs are characterized by a leading slash, hence + // we can use a single check. + if (mb_substr($uri, 0, 2) == '//') { + return Url::fromUri($uri); + } + elseif (mb_substr($uri, 0, 1) == '/') { + return Url::fromUri('base:' . str_replace($this->requestStack->getCurrentRequest()->getBasePath(), '', $uri)); + } + else { + // If this is not a properly formatted stream, then it is a shipped + // file. Therefore, return the urlencoded URI. + $options = UrlHelper::parse($uri); + return Url::fromUri('base:' . UrlHelper::encodePath($options['path']), $options); + } + } + elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') { + // Check for HTTP and data URI-encoded URLs so that we don't have to + // implement getExternalUrl() for the HTTP and data schemes. + $options = UrlHelper::parse($uri); + return Url::fromUri(urldecode($options['path']), $options); + } + elseif ($wrapper = $this->streamWrapperManager->getViaUri($uri)) { + // Attempt to return an external URL using the appropriate wrapper. + return Url::fromUri('base:' . $this->transformRelative(urldecode($wrapper->getExternalUrl()), FALSE)); + } + throw new InvalidStreamWrapperException(); + } + + /** + * {@inheritdoc} + */ + public function transformRelative(string $file_url, bool $root_relative = TRUE): string { + // Unfortunately, we pretty much have to duplicate Symfony's + // Request::getHttpHost() method because Request::getPort() may return NULL + // instead of a port number. + $request = $this->requestStack->getCurrentRequest(); + $host = $request->getHost(); + $scheme = $request->getScheme(); + $port = $request->getPort() ?: 80; + + // Files may be accessible on a different port than the web request. + $file_url_port = parse_url($file_url, PHP_URL_PORT) ?? $port; + if ($file_url_port != $port) { + return $file_url; + } + + if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { + $http_host = $host; + } + else { + $http_host = $host . ':' . $port; + } + + // If this should not be a root-relative path but relative to the drupal + // base path, add it to the host to be removed from the URL as well. + if (!$root_relative) { + $http_host .= $request->getBasePath(); + } + + return preg_replace('|^https?://' . preg_quote($http_host, '|') . '|', '', $file_url); + } + +} diff --git a/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php new file mode 100644 index 00000000000..1ab1bfcf4c9 --- /dev/null +++ b/core/lib/Drupal/Core/File/FileUrlGeneratorInterface.php @@ -0,0 +1,97 @@ +generateString($element['#src']); if (!empty($element['#title'])) { $element['#attributes']['alt'] = $element['#title']; $element['#attributes']['title'] = $element['#title']; diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 7deda60a356..34257e34930 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -6,6 +6,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Render\MarkupInterface; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Render\AttachmentsInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Markup; @@ -61,6 +62,13 @@ class TwigExtension extends AbstractExtension { */ protected $dateFormatter; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Constructs \Drupal\Core\Template\TwigExtension. * @@ -72,12 +80,19 @@ class TwigExtension extends AbstractExtension { * The theme manager. * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. */ - public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter) { + public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter, FileUrlGeneratorInterface $file_url_generator = NULL) { $this->renderer = $renderer; $this->urlGenerator = $url_generator; $this->themeManager = $theme_manager; $this->dateFormatter = $date_formatter; + if (!$file_url_generator) { + @trigger_error('Calling TwigExtension::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031.', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; } /** @@ -92,9 +107,7 @@ class TwigExtension extends AbstractExtension { new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), new TwigFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), new TwigFunction('link', [$this, 'getLink']), - new TwigFunction('file_url', function ($uri) { - return file_url_transform_relative(file_create_url($uri)); - }), + new TwigFunction('file_url', [$this->fileUrlGenerator, 'generateString']), new TwigFunction('attach_library', [$this, 'attachLibrary']), new TwigFunction('active_theme_path', [$this, 'getActiveThemePath']), new TwigFunction('active_theme', [$this, 'getActiveTheme']), diff --git a/core/modules/aggregator/tests/src/Functional/ImportOpmlTest.php b/core/modules/aggregator/tests/src/Functional/ImportOpmlTest.php index d60b4062ed2..a690fbd7222 100644 --- a/core/modules/aggregator/tests/src/Functional/ImportOpmlTest.php +++ b/core/modules/aggregator/tests/src/Functional/ImportOpmlTest.php @@ -68,7 +68,7 @@ class ImportOpmlTest extends AggregatorTestBase { $path = $this->getEmptyOpml(); $edit = [ 'files[upload]' => $path, - 'remote' => file_create_url($path), + 'remote' => \Drupal::service('file_url_generator')->generateAbsoluteString($path), ]; $this->drupalGet('admin/config/services/aggregator/add/opml'); $this->submitForm($edit, 'Import'); @@ -98,7 +98,7 @@ class ImportOpmlTest extends AggregatorTestBase { $this->assertSession()->pageTextContains('No new feed has been added.'); // Attempting to load empty OPML from remote URL - $edit = ['remote' => file_create_url($this->getEmptyOpml())]; + $edit = ['remote' => \Drupal::service('file_url_generator')->generateAbsoluteString($this->getEmptyOpml())]; $this->drupalGet('admin/config/services/aggregator/add/opml'); $this->submitForm($edit, 'Import'); $this->assertSession()->pageTextContains('No new feed has been added.'); diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php index 4e4ec799a9c..fd5a338b7a2 100644 --- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php +++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php @@ -4,6 +4,7 @@ namespace Drupal\ckeditor\Plugin\Editor; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\ckeditor\CKEditorPluginManager; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Render\Element; @@ -58,6 +59,13 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { */ protected $renderer; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * The state key/value store. * @@ -84,14 +92,21 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { * The renderer. * @param \Drupal\Core\State\StateInterface $state * The state key/value store. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer, StateInterface $state) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer, StateInterface $state, FileUrlGeneratorInterface $file_url_generator = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->ckeditorPluginManager = $ckeditor_plugin_manager; $this->moduleHandler = $module_handler; $this->languageManager = $language_manager; $this->renderer = $renderer; $this->state = $state; + if (!$file_url_generator) { + @trigger_error('Calling CKEditor::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and will be required before drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; } /** @@ -106,7 +121,8 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { $container->get('module_handler'), $container->get('language_manager'), $container->get('renderer'), - $container->get('state') + $container->get('state'), + $container->get('file_url_generator') ); } @@ -307,11 +323,8 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { ]; // Finally, set Drupal-specific CKEditor settings. - $root_relative_file_url = function ($uri) { - return file_url_transform_relative(file_create_url($uri)); - }; $settings += [ - 'drupalExternalPlugins' => array_map($root_relative_file_url, $external_plugin_files), + 'drupalExternalPlugins' => array_map([$this->fileUrlGenerator, 'generateString'], $external_plugin_files), ]; // Parse all CKEditor plugin JavaScript files for translations. @@ -444,8 +457,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface { $query_string_separator = (strpos($item, '?') !== FALSE) ? '&' : '?'; return $item . $query_string_separator . $query_string; }, $css); - $css = array_map('file_create_url', $css); - $css = array_map('file_url_transform_relative', $css); + $css = array_map([$this->fileUrlGenerator, 'generateString'], $css); return array_values($css); } diff --git a/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php b/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php index e8c066825a7..50429334100 100644 --- a/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php +++ b/core/modules/ckeditor/tests/modules/src/Form/AjaxCssForm.php @@ -4,8 +4,10 @@ namespace Drupal\ckeditor_test\Form; use Drupal\ckeditor\Ajax\AddStyleSheetCommand; use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * A form for testing delivery of CSS to CKEditor via AJAX. @@ -14,6 +16,30 @@ use Drupal\Core\Form\FormStateInterface; */ class AjaxCssForm extends FormBase { + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + + /** + * Constructs an AjaxCssForm. + * + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. + */ + public function __construct(FileUrlGeneratorInterface $file_url_generator) { + $this->fileUrlGenerator = $file_url_generator; + } + + /** + * @inheritDoc + */ + public static function create(ContainerInterface $container) { + return new static($container->get('file_url_generator')); + } + /** * {@inheritdoc} */ @@ -82,8 +108,7 @@ class AjaxCssForm extends FormBase { protected function generateResponse($editor_id) { // Build a URL to the style sheet that will be added. $url = drupal_get_path('module', 'ckeditor_test') . '/css/test.css'; - $url = file_create_url($url); - $url = file_url_transform_relative($url); + $url = $this->fileUrlGenerator->generateString($url); $response = new AjaxResponse(); return $response diff --git a/core/modules/ckeditor/tests/src/Functional/CKEditorToolbarButtonTest.php b/core/modules/ckeditor/tests/src/Functional/CKEditorToolbarButtonTest.php index ff75d6e4aa5..40e43050fac 100644 --- a/core/modules/ckeditor/tests/src/Functional/CKEditorToolbarButtonTest.php +++ b/core/modules/ckeditor/tests/src/Functional/CKEditorToolbarButtonTest.php @@ -76,7 +76,9 @@ class CKEditorToolbarButtonTest extends BrowserTestBase { $json_encode = function ($html) { return trim(Json::encode($html), '"'); }; - $markup = $json_encode(file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/icons/drupalimage.png'))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $markup = $json_encode($file_url_generator->generateString('core/modules/ckeditor/js/plugins/drupalimage/icons/drupalimage.png')); $this->assertRaw($markup); } diff --git a/core/modules/ckeditor/tests/src/Kernel/CKEditorTest.php b/core/modules/ckeditor/tests/src/Kernel/CKEditorTest.php index c0d21b2f5b8..ba994452dae 100644 --- a/core/modules/ckeditor/tests/src/Kernel/CKEditorTest.php +++ b/core/modules/ckeditor/tests/src/Kernel/CKEditorTest.php @@ -35,6 +35,13 @@ class CKEditorTest extends KernelTestBase { */ protected $ckeditor; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * The Editor Plugin Manager. * @@ -44,6 +51,7 @@ class CKEditorTest extends KernelTestBase { protected function setUp(): void { parent::setUp(); + $this->fileUrlGenerator = $this->container->get('file_url_generator'); // Install the Filter module. @@ -93,8 +101,8 @@ class CKEditorTest extends KernelTestBase { 'language' => 'en', 'stylesSet' => FALSE, 'drupalExternalPlugins' => [ - 'drupalimage' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js')), - 'drupallink' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js')), + 'drupalimage' => $this->fileUrlGenerator->generateString('core/modules/ckeditor/js/plugins/drupalimage/plugin.js'), + 'drupallink' => $this->fileUrlGenerator->generateString('core/modules/ckeditor/js/plugins/drupallink/plugin.js'), ], ]; $this->assertEquals($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.'); @@ -114,9 +122,9 @@ class CKEditorTest extends KernelTestBase { $expected_config['toolbar'][0]['items'][] = 'Format'; $expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6'; $expected_config['extraPlugins'] .= ',llama_contextual,llama_contextual_and_button'; - $expected_config['drupalExternalPlugins']['llama_contextual'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js')); - $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js')); - $expected_config['contentsCss'][] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css')) . $query_string; + $expected_config['drupalExternalPlugins']['llama_contextual'] = $this->fileUrlGenerator->generateString('core/modules/ckeditor/tests/modules/js/llama_contextual.js'); + $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = $this->fileUrlGenerator->generateString('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js'); + $expected_config['contentsCss'][] = $this->fileUrlGenerator->generateString('core/modules/ckeditor/tests/modules/ckeditor_test.css') . $query_string; $this->assertEquals($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.'); // Change the allowed HTML tags; the "allowedContent" and "format_tags" @@ -257,7 +265,7 @@ class CKEditorTest extends KernelTestBase { // Enable the editor_test module, which implements hook_ckeditor_css_alter(). $this->enableModules(['ckeditor_test']); - $expected[] = file_url_transform_relative(file_create_url(drupal_get_path('module', 'ckeditor_test') . '/ckeditor_test.css')) . $query_string; + $expected[] = $this->fileUrlGenerator->generateString(drupal_get_path('module', 'ckeditor_test') . '/ckeditor_test.css') . $query_string; $this->assertSame($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a hook_ckeditor_css_alter() implementation exists.'); // Enable LlamaCss plugin, which adds an additional CKEditor stylesheet. @@ -269,17 +277,17 @@ class CKEditorTest extends KernelTestBase { $settings['toolbar']['rows'][0][0]['items'][] = 'LlamaCSS'; $editor->setSettings($settings); $editor->save(); - $expected[] = file_url_transform_relative(file_create_url(drupal_get_path('module', 'ckeditor_test') . '/css/llama.css')) . $query_string; + $expected[] = $this->fileUrlGenerator->generateString(drupal_get_path('module', 'ckeditor_test') . '/css/llama.css') . $query_string; $this->assertSame($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a CKEditorPluginInterface implementation exists.'); // Enable the Bartik theme, which specifies a CKEditor stylesheet. \Drupal::service('theme_installer')->install(['bartik']); $this->config('system.theme')->set('default', 'bartik')->save(); - $expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/base/elements.css')) . $query_string; - $expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/captions.css')) . $query_string; - $expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/table.css')) . $query_string; - $expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/text-formatted.css')) . $query_string; - $expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/classy/components/media-embed-error.css')) . $query_string; + $expected[] = $this->fileUrlGenerator->generateString('core/themes/bartik/css/base/elements.css') . $query_string; + $expected[] = $this->fileUrlGenerator->generateString('core/themes/bartik/css/components/captions.css') . $query_string; + $expected[] = $this->fileUrlGenerator->generateString('core/themes/bartik/css/components/table.css') . $query_string; + $expected[] = $this->fileUrlGenerator->generateString('core/themes/bartik/css/components/text-formatted.css') . $query_string; + $expected[] = $this->fileUrlGenerator->generateString('core/themes/bartik/css/classy/components/media-embed-error.css') . $query_string; $this->assertSame($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.'); } @@ -543,8 +551,8 @@ class CKEditorTest extends KernelTestBase { protected function getDefaultContentsCssConfig() { $query_string = '?0='; return [ - file_url_transform_relative(file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css')) . $query_string, - file_url_transform_relative(file_create_url('core/modules/system/css/components/align.module.css')) . $query_string, + $this->fileUrlGenerator->generateString('core/modules/ckeditor/css/ckeditor-iframe.css') . $query_string, + $this->fileUrlGenerator->generateString('core/modules/system/css/components/align.module.css') . $query_string, ]; } diff --git a/core/modules/color/color.module b/core/modules/color/color.module index 712d65e7843..a2c0cab28e9 100644 --- a/core/modules/color/color.module +++ b/core/modules/color/color.module @@ -474,7 +474,7 @@ function color_scheme_form_submit($form, FormStateInterface $form_state) { } foreach ($files as $file) { - $css_optimizer = new CssOptimizer(); + $css_optimizer = new CssOptimizer(\Drupal::service('file_url_generator')); // Aggregate @imports recursively for each configured top level CSS file // without optimization. Aggregation and optimization will be // handled by drupal_build_css_cache() only. diff --git a/core/modules/color/src/ColorSystemBrandingBlockAlter.php b/core/modules/color/src/ColorSystemBrandingBlockAlter.php index 6408e1e71f7..337c6bc9d7f 100644 --- a/core/modules/color/src/ColorSystemBrandingBlockAlter.php +++ b/core/modules/color/src/ColorSystemBrandingBlockAlter.php @@ -25,7 +25,7 @@ class ColorSystemBrandingBlockAlter implements RenderCallbackInterface { // Override logo. $logo = $config->get('logo'); if ($logo && $build['content']['site_logo'] && preg_match('!' . $theme_key . '/logo.svg$!', $build['content']['site_logo']['#uri'])) { - $build['content']['site_logo']['#uri'] = file_url_transform_relative(file_create_url($logo)); + $build['content']['site_logo']['#uri'] = \Drupal::service('file_url_generator')->generateString($logo); } return $build; diff --git a/core/modules/color/tests/src/Functional/ColorTest.php b/core/modules/color/tests/src/Functional/ColorTest.php index a900cf2df25..e2072edb283 100644 --- a/core/modules/color/tests/src/Functional/ColorTest.php +++ b/core/modules/color/tests/src/Functional/ColorTest.php @@ -121,9 +121,11 @@ class ColorTest extends BrowserTestBase { $this->drupalGet(''); $stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets'); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); // Make sure the color stylesheet is included in the content. foreach ($stylesheets as $stylesheet) { - $this->assertSession()->responseMatches('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|'); + $this->assertSession()->responseMatches('|' . $file_url_generator->generateString($stylesheet) . '|'); $stylesheet_content = implode("\n", file($stylesheet)); $this->assertStringContainsString('color: #123456', $stylesheet_content, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')'); } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index ba2c6629601..33a57d9ba93 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1224,9 +1224,9 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta break; case 'url': - // Ideally, this would use file_url_transform_relative(), but because - // tokens are also often used in e-mails, it's better to keep absolute - // file URLs. The 'url.site' cache context is associated to ensure the + // Ideally, this would use return a relative URL, but because tokens + // are also often used in e-mails, it's better to keep absolute file + // URLs. The 'url.site' cache context is associated to ensure the // correct absolute URL is used in case of a multisite setup. $replacements[$original] = $file->createFileUrl(FALSE); $bubbleable_metadata->addCacheContexts(['url.site']); @@ -1482,13 +1482,9 @@ function template_preprocess_file_link(&$variables) { $file = $variables['file']; $options = []; - // @todo Wrap in file_url_transform_relative(). This is currently - // impossible. As a work-around, we currently add the 'url.site' cache context - // to ensure different file URLs are generated for different sites in a - // multisite setup, including HTTP and HTTPS versions of the same site. - // Fix in https://www.drupal.org/node/2646744. - $url = $file->createFileUrl(FALSE); - $variables['#cache']['contexts'][] = 'url.site'; + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $url = $file_url_generator->generate($file->getFileUri()); $mime_type = $file->getMimeType(); $options['attributes']['type'] = $mime_type; @@ -1516,7 +1512,7 @@ function template_preprocess_file_link(&$variables) { $variables['attributes']->addClass($classes); $variables['file_size'] = format_size($file->getSize()); - $variables['link'] = Link::fromTextAndUrl($link_text, Url::fromUri($url, $options))->toRenderable(); + $variables['link'] = Link::fromTextAndUrl($link_text, $url->setOptions($options))->toRenderable(); } /** diff --git a/core/modules/file/src/ComputedFileUrl.php b/core/modules/file/src/ComputedFileUrl.php index 2eb012edc65..276f2c7d8f7 100644 --- a/core/modules/file/src/ComputedFileUrl.php +++ b/core/modules/file/src/ComputedFileUrl.php @@ -27,7 +27,9 @@ class ComputedFileUrl extends TypedData { assert($this->getParent()->getEntity() instanceof FileInterface); $uri = $this->getParent()->getEntity()->getFileUri(); - $this->url = file_url_transform_relative(file_create_url($uri)); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $this->url = $file_url_generator->generateString($uri); return $this->url; } diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index e707b9ae4f8..e6e19a15dd1 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -80,11 +80,9 @@ class File extends ContentEntityBase implements FileInterface { * {@inheritdoc} */ public function createFileUrl($relative = TRUE) { - $url = file_create_url($this->getFileUri()); - if ($relative && $url) { - $url = file_url_transform_relative($url); - } - return $url; + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + return $relative ? $file_url_generator->generateString($this->getFileUri()) : $file_url_generator->generateAbsoluteString($this->getFileUri()); } /** diff --git a/core/modules/file/src/FileInterface.php b/core/modules/file/src/FileInterface.php index dd4ac32af75..0a565e23c95 100644 --- a/core/modules/file/src/FileInterface.php +++ b/core/modules/file/src/FileInterface.php @@ -59,8 +59,7 @@ interface FileInterface extends ContentEntityInterface, EntityChangedInterface, * @return string * A string containing a URL that may be used to access the file. * - * @see file_create_url() - * @see file_url_transform_relative() + * @see \Drupal\Core\File\FileUrlGeneratorInterface */ public function createFileUrl($relative = TRUE); diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php index b54b17b5ecd..f193d459089 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/BaseFieldFileFormatterBase.php @@ -6,14 +6,67 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterBase; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base class for file formatters, which allow to link to the file download URL. */ abstract class BaseFieldFileFormatterBase extends FormatterBase { + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + + /** + * Constructs a BaseFieldFileFormatterBase object. + * + * @param string $plugin_id + * The plugin_id for the formatter. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the formatter is associated. + * @param array $settings + * The formatter settings. + * @param string $label + * The formatter label display setting. + * @param string $view_mode + * The view mode. + * @param array $third_party_settings + * Any third party settings. + * @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, FileUrlGeneratorInterface $file_url_generator = NULL) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); + if (!$file_url_generator) { + @trigger_error('Calling BaseFieldFileFormatterBase::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and the $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('file_url_generator') + ); + } + /** * {@inheritdoc} */ @@ -47,9 +100,7 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase { $url = NULL; // Add support to link to the entity itself. if ($this->getSetting('link_to_file')) { - // @todo Wrap in file_url_transform_relative(). This is currently - // impossible. See below. - $url = file_create_url($items->getEntity()->uri->value); + $url = $this->fileUrlGenerator->generate($items->getEntity()->getFileUri()); } foreach ($items as $delta => $item) { @@ -59,17 +110,7 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase { $elements[$delta] = [ '#type' => 'link', '#title' => $view_value, - '#url' => Url::fromUri($url), - // @todo Remove the 'url.site' cache context by using a relative file - // URL (file_url_transform_relative()). This is currently impossible - // because #type => link requires a Url object, and Url objects do not - // support relative URLs: they require fully qualified URLs. Fix in - // https://www.drupal.org/node/2646744. - '#cache' => [ - 'contexts' => [ - 'url.site', - ], - ], + '#url' => $url, ]; } else { diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php index 0facb7e0338..7a0cb56f725 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileUriFormatter.php @@ -51,10 +51,7 @@ class FileUriFormatter extends BaseFieldFileFormatterBase { protected function viewValue(FieldItemInterface $item) { $value = $item->value; if ($this->getSetting('file_download_path')) { - // @todo Wrap in file_url_transform_relative(). This is currently - // impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in - // https://www.drupal.org/node/2646744. - $value = file_create_url($value); + $value = $this->fileUrlGenerator->generateString($value); } return $value; } diff --git a/core/modules/file/src/Plugin/views/field/File.php b/core/modules/file/src/Plugin/views/field/File.php index fdf2d5966f4..6a7e1ed4219 100644 --- a/core/modules/file/src/Plugin/views/field/File.php +++ b/core/modules/file/src/Plugin/views/field/File.php @@ -2,11 +2,13 @@ namespace Drupal\file\Plugin\views\field; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\views\ResultRow; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\field\FieldPluginBase; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Field handler to provide simple renderer that allows linking to a file. @@ -17,6 +19,41 @@ use Drupal\views\Plugin\views\field\FieldPluginBase; */ class File extends FieldPluginBase { + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + + /** + * Constructs a File object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * The file URL generator. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, FileUrlGeneratorInterface $file_url_generator = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + if (!$file_url_generator) { + @trigger_error('Calling File::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and the $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, $container->get('file_url_generator')); + } + /** * {@inheritdoc} */ @@ -64,13 +101,7 @@ class File extends FieldPluginBase { protected function renderLink($data, ResultRow $values) { if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') { $this->options['alter']['make_link'] = TRUE; - // @todo Wrap in file_url_transform_relative(). This is currently - // impossible. As a work-around, we could add the 'url.site' cache context - // to ensure different file URLs are generated for different sites in a - // multisite setup, including HTTP and HTTPS versions of the same site. - // But unfortunately it's impossible to bubble a cache context here. - // Fix in https://www.drupal.org/node/2646744. - $this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri')); + $this->options['alter']['url'] = $this->fileUrlGenerator->generate($this->getValue($values, 'uri')); } return $data; diff --git a/core/modules/file/tests/src/Functional/DownloadTest.php b/core/modules/file/tests/src/Functional/DownloadTest.php index 73d02a2116f..1792e1d203e 100644 --- a/core/modules/file/tests/src/Functional/DownloadTest.php +++ b/core/modules/file/tests/src/Functional/DownloadTest.php @@ -16,8 +16,16 @@ class DownloadTest extends FileManagedTestBase { */ protected $defaultTheme = 'stark'; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + protected function setUp(): void { parent::setUp(); + $this->fileUrlGenerator = $this->container->get('file_url_generator'); // Clear out any hook calls. file_test_reset(); } @@ -28,7 +36,7 @@ class DownloadTest extends FileManagedTestBase { public function testPublicFileTransfer() { // Test generating a URL to a created file. $file = $this->createFile(); - $url = file_create_url($file->getFileUri()); + $url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri()); // URLs can't contain characters outside the ASCII set so $filename has to be // encoded. $filename = $GLOBALS['base_url'] . '/' . \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath() . '/' . rawurlencode($file->getFilename()); @@ -40,7 +48,7 @@ class DownloadTest extends FileManagedTestBase { // Test generating a URL to a shipped file (i.e. a file that is part of // Drupal core, a module or a theme, for example a JavaScript file). $filepath = 'core/assets/vendor/jquery/jquery.min.js'; - $url = file_create_url($filepath); + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath, $url, 'Correctly generated a URL for a shipped file.'); $response = $http_client->head($url); $this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the shipped file.'); @@ -69,7 +77,7 @@ class DownloadTest extends FileManagedTestBase { $file->setPermanent(); $file->save(); - $url = file_create_url($file->getFileUri()); + $url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri()); // Set file_test access header to allow the download. file_test_reset(); @@ -94,7 +102,7 @@ class DownloadTest extends FileManagedTestBase { // Try non-existent file. file_test_reset(); - $url = file_create_url('private://' . $this->randomMachineName()); + $url = $this->fileUrlGenerator->generateAbsoluteString('private://' . $this->randomMachineName()); $response = $http_client->head($url, ['http_errors' => FALSE]); $this->assertSame(404, $response->getStatusCode(), 'Correctly returned 404 response for a non-existent file.'); // Assert that hook_file_download is not called. @@ -109,7 +117,7 @@ class DownloadTest extends FileManagedTestBase { } /** - * Tests file_create_url(). + * Test FileUrlGeneratorInterface::generateString() */ public function testFileCreateUrl() { // "Special" ASCII characters. @@ -139,15 +147,16 @@ class DownloadTest extends FileManagedTestBase { $this->checkUrl('public', '', $basename, $base_path . '/' . $public_directory_path . '/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_path . '/' . $script_path . 'system/files/' . $basename_encoded); } - $this->assertEquals('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', file_create_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), t('Generated URL matches expected URL.')); + $this->assertEquals('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', $this->fileUrlGenerator->generateString('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', FALSE), t('Generated URL matches expected URL.')); } /** - * Download a file from the URL generated by file_create_url(). + * Download a file from the URL generated by generateString(). * * Create a file with the specified scheme, directory and filename; check that - * the URL generated by file_create_url() for the specified file equals the - * specified URL; fetch the URL and then compare the contents to the file. + * the URL generated by FileUrlGeneratorInterface::generateString() for the + * specified file equals the specified URL; fetch the URL and then compare the + * contents to the file. * * @param string $scheme * A scheme, e.g. "public". @@ -167,7 +176,7 @@ class DownloadTest extends FileManagedTestBase { \Drupal::service('file_system')->prepareDirectory($directory_uri, FileSystemInterface::CREATE_DIRECTORY); $file = $this->createFile($filepath, NULL, $scheme); - $url = file_create_url($file->getFileUri()); + $url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri()); $this->assertEquals($expected_url, $url); if ($scheme == 'private') { diff --git a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php index 48267e0e0cf..8772cffd069 100644 --- a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php @@ -228,7 +228,7 @@ class FileFieldDisplayTest extends FileFieldTestBase { // Test default formatter. $this->drupalGet('node/' . $nid); - $this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl(FALSE) . '"]', $description); + $this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]', $description); // Change formatter to "Table of files". $display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load('node.' . $type_name . '.default'); @@ -238,7 +238,7 @@ class FileFieldDisplayTest extends FileFieldTestBase { ])->save(); $this->drupalGet('node/' . $nid); - $this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl(FALSE) . '"]', $description); + $this->assertSession()->elementTextContains('xpath', '//a[@href="' . $node->{$field_name}->entity->createFileUrl() . '"]', $description); } } diff --git a/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php b/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php index 39729b1ff69..fced38d132a 100644 --- a/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php @@ -65,7 +65,7 @@ class FileFieldRSSContentTest extends FileFieldTestBase { $this->drupalGet('rss.xml'); $selector = sprintf( '//enclosure[@url="%s" and @length="%s" and @type="%s"]', - file_create_url($node_file->getFileUri()), + $node_file->createFileUrl(FALSE), $node_file->getSize(), $node_file->getMimeType() ); diff --git a/core/modules/file/tests/src/Functional/FileListingTest.php b/core/modules/file/tests/src/Functional/FileListingTest.php index 395ec3ec797..f4081cd03ea 100644 --- a/core/modules/file/tests/src/Functional/FileListingTest.php +++ b/core/modules/file/tests/src/Functional/FileListingTest.php @@ -116,7 +116,7 @@ class FileListingTest extends FileFieldTestBase { foreach ($nodes as $node) { $file = File::load($node->file->target_id); $this->assertSession()->pageTextContains($file->getFilename()); - $this->assertSession()->linkByHrefExists(file_create_url($file->getFileUri())); + $this->assertSession()->linkByHrefExists($file->createFileUrl()); $this->assertSession()->linkByHrefExists('admin/content/files/usage/' . $file->id()); } $this->assertSession()->elementTextNotContains('css', 'table.views-table', 'Temporary'); diff --git a/core/modules/file/tests/src/Functional/FilePrivateTest.php b/core/modules/file/tests/src/Functional/FilePrivateTest.php index 841fb7e8658..e7f8cdfc655 100644 --- a/core/modules/file/tests/src/Functional/FilePrivateTest.php +++ b/core/modules/file/tests/src/Functional/FilePrivateTest.php @@ -57,11 +57,11 @@ class FilePrivateTest extends FileFieldTestBase { $this->drupalGet('node/' . $node->id()); $this->assertRaw($node_file->getFilename()); // Ensure the file can be downloaded. - $this->drupalGet(file_create_url($node_file->getFileUri())); + $this->drupalGet($node_file->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(200); $this->drupalLogOut(); // Ensure the file cannot be downloaded after logging out. - $this->drupalGet(file_create_url($node_file->getFileUri())); + $this->drupalGet($node_file->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(403); // Create a field with no view access. See @@ -76,7 +76,7 @@ class FilePrivateTest extends FileFieldTestBase { $node_file = File::load($node->{$no_access_field_name}->target_id); // Ensure the file cannot be downloaded. - $file_url = file_create_url($node_file->getFileUri()); + $file_url = $node_file->createFileUrl(FALSE); $this->drupalGet($file_url); $this->assertSession()->statusCodeEquals(403); @@ -151,7 +151,7 @@ class FilePrivateTest extends FileFieldTestBase { $this->assertTrue($file->isTemporary(), 'File is temporary.'); $usage = $this->container->get('file.usage')->listUsage($file); $this->assertEmpty($usage, 'No file usage found.'); - $file_url = file_create_url($file->getFileUri()); + $file_url = $file->createFileUrl(FALSE); // Ensure the anonymous uploader has access to the temporary file. $this->drupalGet($file_url); $this->assertSession()->statusCodeEquals(200); @@ -181,7 +181,7 @@ class FilePrivateTest extends FileFieldTestBase { $this->assertTrue($file->isTemporary(), 'File is temporary.'); $usage = $this->container->get('file.usage')->listUsage($file); $this->assertEmpty($usage, 'No file usage found.'); - $file_url = file_create_url($file->getFileUri()); + $file_url = $file->createFileUrl(FALSE); // Ensure the anonymous uploader has access to the temporary file. $this->drupalGet($file_url); $this->assertSession()->statusCodeEquals(200); @@ -204,7 +204,7 @@ class FilePrivateTest extends FileFieldTestBase { $this->assertTrue($file->isPermanent(), 'File is permanent.'); $usage = $this->container->get('file.usage')->listUsage($file); $this->assertCount(1, $usage, 'File usage found.'); - $file_url = file_create_url($file->getFileUri()); + $file_url = $file->createFileUrl(FALSE); // Ensure the anonymous uploader has access to the file. $this->drupalGet($file_url); $this->assertSession()->statusCodeEquals(200); @@ -231,7 +231,7 @@ class FilePrivateTest extends FileFieldTestBase { $this->assertTrue($file->isPermanent(), 'File is permanent.'); $usage = $this->container->get('file.usage')->listUsage($file); $this->assertCount(1, $usage, 'File usage found.'); - $file_url = file_create_url($file->getFileUri()); + $file_url = $file->createFileUrl(FALSE); // Ensure the anonymous uploader cannot access to the file. $this->drupalGet($file_url); $this->assertSession()->statusCodeEquals(403); diff --git a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php index 27c146ac81b..71b9e71f8ea 100644 --- a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php +++ b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php @@ -56,7 +56,7 @@ class FileTokenReplaceTest extends FileFieldTestBase { $tests['[file:path]'] = Html::escape($file->getFileUri()); $tests['[file:mime]'] = Html::escape($file->getMimeType()); $tests['[file:size]'] = format_size($file->getSize()); - $tests['[file:url]'] = Html::escape(file_create_url($file->getFileUri())); + $tests['[file:url]'] = Html::escape($file->createFileUrl(FALSE)); $tests['[file:created]'] = $date_formatter->format($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId()); $tests['[file:created:short]'] = $date_formatter->format($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId()); $tests['[file:changed]'] = $date_formatter->format($file->getChangedTime(), 'medium', '', NULL, $language_interface->getId()); diff --git a/core/modules/file/tests/src/Functional/Formatter/FileVideoFormatterTest.php b/core/modules/file/tests/src/Functional/Formatter/FileVideoFormatterTest.php index ec1ca79199f..4f4e27e1891 100644 --- a/core/modules/file/tests/src/Functional/Formatter/FileVideoFormatterTest.php +++ b/core/modules/file/tests/src/Functional/Formatter/FileVideoFormatterTest.php @@ -92,7 +92,7 @@ class FileVideoFormatterTest extends FileMediaFormatterTestBase { $this->drupalGet($entity->toUrl()); - $file_url = file_url_transform_relative(file_create_url($file->getFileUri())); + $file_url = \Drupal::service('file_url_generator')->generateString($file->getFileUri()); $assert_session = $this->assertSession(); $assert_session->elementExists('css', "video[autoplay='autoplay'] > source[src='$file_url'][type='video/mp4']"); diff --git a/core/modules/file/tests/src/Functional/Hal/FileHalJsonAnonTest.php b/core/modules/file/tests/src/Functional/Hal/FileHalJsonAnonTest.php index c40dd8a53a4..cd9dcac8f5c 100644 --- a/core/modules/file/tests/src/Functional/Hal/FileHalJsonAnonTest.php +++ b/core/modules/file/tests/src/Functional/Hal/FileHalJsonAnonTest.php @@ -42,7 +42,7 @@ class FileHalJsonAnonTest extends FileResourceTestBase { $normalization = $this->applyHalFieldNormalization($default_normalization); - $url = file_create_url($this->entity->getFileUri()); + $url = $this->entity->createFileUrl(FALSE); if ($this->config('hal.settings')->get('bc_file_uri_as_url_normalizer')) { $normalization['uri'][0]['value'] = $url; } diff --git a/core/modules/file/tests/src/Functional/Hal/FileUploadHalJsonTestBase.php b/core/modules/file/tests/src/Functional/Hal/FileUploadHalJsonTestBase.php index 8039ffd05a1..f7491213891 100644 --- a/core/modules/file/tests/src/Functional/Hal/FileUploadHalJsonTestBase.php +++ b/core/modules/file/tests/src/Functional/Hal/FileUploadHalJsonTestBase.php @@ -48,7 +48,7 @@ abstract class FileUploadHalJsonTestBase extends FileUploadResourceTestBase { // https://www.drupal.org/project/drupal/issues/2907402 is complete. // This link matches what is generated from File::url(), a resource // URL is currently not available. - 'href' => file_create_url($normalization['uri'][0]['value']), + 'href' => \Drupal::service('file_url_generator')->generateAbsoluteString($normalization['uri'][0]['value']), ], 'type' => [ 'href' => $this->baseUrl . '/rest/type/file/file', diff --git a/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php b/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php index 70500cb5f2b..9cde140d872 100644 --- a/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php +++ b/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php @@ -98,7 +98,7 @@ class PrivateFileOnTranslatedEntityTest extends FileFieldTestBase { \Drupal::entityTypeManager()->getStorage('node')->resetCache([$default_language_node->id()]); $node = Node::load($default_language_node->id()); $node_file = File::load($node->{$this->fieldName}->target_id); - $this->drupalGet(file_create_url($node_file->getFileUri())); + $this->drupalGet($node_file->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(200); // Translate the node into French. @@ -126,7 +126,7 @@ class PrivateFileOnTranslatedEntityTest extends FileFieldTestBase { // Ensure the file attached to the translated node can be downloaded. $french_node = $default_language_node->getTranslation('fr'); $node_file = File::load($french_node->{$this->fieldName}->target_id); - $this->drupalGet(file_create_url($node_file->getFileUri())); + $this->drupalGet($node_file->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(200); } diff --git a/core/modules/file/tests/src/Kernel/FileUrlTest.php b/core/modules/file/tests/src/Kernel/FileUrlTest.php index 8cc995c4185..a7ca1ef3668 100644 --- a/core/modules/file/tests/src/Kernel/FileUrlTest.php +++ b/core/modules/file/tests/src/Kernel/FileUrlTest.php @@ -21,7 +21,7 @@ class FileUrlTest extends FileManagedUnitTestBase { $directory_uri = 'public://' . dirname($filepath); \Drupal::service('file_system')->prepareDirectory($directory_uri, FileSystemInterface::CREATE_DIRECTORY); $file = $this->createFile($filepath, NULL, 'public'); - $url = file_create_url($file->getFileUri()); + $url = $file->createFileUrl(FALSE); $expected_url = $test_base_url . '/' . basename($filepath); $this->assertSame($url, $expected_url); } diff --git a/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php b/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php index dac6a316318..d48a58089a5 100644 --- a/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php +++ b/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\file\Kernel\Formatter; use Drupal\Core\Entity\Entity\EntityViewDisplay; -use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\KernelTests\KernelTestBase; @@ -26,12 +25,19 @@ class FileEntityFormatterTest extends KernelTestBase { */ protected $files; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); - + $this->fileUrlGenerator = $this->container->get('file_url_generator'); $this->installEntitySchema('file'); $this->files = []; @@ -80,7 +86,7 @@ class FileEntityFormatterTest extends KernelTestBase { $build = $entity_display->buildMultiple($this->files)[0]['filename'][0]; $this->assertEquals('file.png', $build['#title']); - $this->assertEquals(Url::fromUri(file_create_url('public://file.png')), $build['#url']); + $this->assertEquals($this->fileUrlGenerator->generate('public://file.png'), $build['#url']); } /** @@ -98,12 +104,12 @@ class FileEntityFormatterTest extends KernelTestBase { $entity_display->setComponent('uri', ['type' => 'file_uri', 'settings' => ['file_download_path' => TRUE]]); $build = $entity_display->buildMultiple($this->files)[0]['uri'][0]; - $this->assertEquals(file_create_url('public://file.png'), $build['#markup']); + $this->assertEquals($this->fileUrlGenerator->generateString('public://file.png'), $build['#markup']); $entity_display->setComponent('uri', ['type' => 'file_uri', 'settings' => ['file_download_path' => TRUE, 'link_to_file' => TRUE]]); $build = $entity_display->buildMultiple($this->files)[0]['uri'][0]; - $this->assertEquals(file_create_url('public://file.png'), $build['#title']); - $this->assertEquals(Url::fromUri(file_create_url('public://file.png')), $build['#url']); + $this->assertEquals($this->fileUrlGenerator->generateString('public://file.png'), $build['#title']); + $this->assertEquals($this->fileUrlGenerator->generate('public://file.png'), $build['#url']); } /** diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 70a603e8c5d..15aad887308 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -768,11 +768,14 @@ function _filter_html_image_secure_process($text) { $html_dom = Html::load($text); $images = $html_dom->getElementsByTagName('img'); + + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); foreach ($images as $image) { $src = $image->getAttribute('src'); // Transform absolute image URLs to relative image URLs: prevent problems on // multisite set-ups and prevent mixed content errors. - $image->setAttribute('src', file_url_transform_relative($src)); + $image->setAttribute('src', $file_url_generator->transformRelative($src)); // Verify that $src starts with $base_path. // This also ensures that external images cannot be referenced. diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index e43bd48765f..178e5464b17 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -17,6 +17,10 @@ use Drupal\Core\Render\Element; * - style: \Drupal\image\ImageStyleInterface image style being previewed. */ function template_preprocess_image_style_preview(&$variables) { + + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + // Style information. $style = $variables['style']; $variables['style_id'] = $style->id(); @@ -34,7 +38,7 @@ function template_preprocess_image_style_preview(&$variables) { $original_path = \Drupal::config('image.settings')->get('preview_image'); $original_image = $image_factory->get($original_path); $variables['original'] = [ - 'url' => file_url_transform_relative(file_create_url($original_path)), + 'url' => $file_url_generator->generateString($original_path), 'width' => $original_image->getWidth(), 'height' => $original_image->getHeight(), ]; @@ -55,7 +59,7 @@ function template_preprocess_image_style_preview(&$variables) { } $preview_image = $image_factory->get($preview_file); $variables['derivative'] = [ - 'url' => file_url_transform_relative(file_create_url($preview_file)), + 'url' => $file_url_generator->generateString($preview_file), 'width' => $preview_image->getWidth(), 'height' => $preview_image->getHeight(), ]; diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index 39e7263fcad..17d6f751a05 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -247,7 +247,9 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity return Url::fromUri('base:' . $directory_path . '/' . $stream_wrapper_manager::getTarget($uri), ['absolute' => TRUE, 'query' => $token_query])->toString(); } - $file_url = file_create_url($uri); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $file_url = $file_url_generator->generateAbsoluteString($uri); // Append the query string with the token, if necessary. if ($token_query) { $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query); diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index d3306e5d159..2339858e761 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -56,7 +56,7 @@ interface ImageStyleInterface extends ConfigEntityInterface { * in an tag. Requesting the URL will cause the image to be created. * * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver() - * @see file_url_transform_relative() + * @see \Drupal\Core\File\FileUrlGeneratorInterface::transformRelative() */ public function buildUrl($path, $clean_urls = NULL); diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index e350e53529c..515dd92553c 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -5,6 +5,7 @@ namespace Drupal\image\Plugin\Field\FieldFormatter; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Link; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; @@ -43,6 +44,13 @@ class ImageFormatter extends ImageFormatterBase { */ protected $imageStyleStorage; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Constructs an ImageFormatter object. * @@ -64,11 +72,18 @@ class ImageFormatter extends ImageFormatterBase { * The current user. * @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage * The image style storage. + * @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, AccountInterface $current_user, EntityStorageInterface $image_style_storage) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage, FileUrlGeneratorInterface $file_url_generator = NULL) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); $this->currentUser = $current_user; $this->imageStyleStorage = $image_style_storage; + if (!$file_url_generator) { + @trigger_error('Calling ImageFormatter::__construct() without the $file_url_generator argument is deprecated in drupal:9.3.0 and the $file_url_generator argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2940031', E_USER_DEPRECATED); + $file_url_generator = \Drupal::service('file_url_generator'); + } + $this->fileUrlGenerator = $file_url_generator; } /** @@ -84,7 +99,8 @@ class ImageFormatter extends ImageFormatterBase { $configuration['view_mode'], $configuration['third_party_settings'], $container->get('current_user'), - $container->get('entity_type.manager')->getStorage('image_style') + $container->get('entity_type.manager')->getStorage('image_style'), + $container->get('file_url_generator') ); } @@ -199,16 +215,9 @@ class ImageFormatter extends ImageFormatterBase { } foreach ($files as $delta => $file) { - $cache_contexts = []; if (isset($link_file)) { $image_uri = $file->getFileUri(); - // @todo Wrap in file_url_transform_relative(). This is currently - // impossible. As a work-around, we currently add the 'url.site' cache - // context to ensure different file URLs are generated for different - // sites in a multisite setup, including HTTP and HTTPS versions of the - // same site. Fix in https://www.drupal.org/node/2646744. - $url = Url::fromUri(file_create_url($image_uri)); - $cache_contexts[] = 'url.site'; + $url = $this->fileUrlGenerator->generate($image_uri); } $cache_tags = Cache::mergeTags($base_cache_tags, $file->getCacheTags()); @@ -226,7 +235,6 @@ class ImageFormatter extends ImageFormatterBase { '#url' => $url, '#cache' => [ 'tags' => $cache_tags, - 'contexts' => $cache_contexts, ], ]; } diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php index 7109627a43e..9d70cc64435 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php @@ -61,11 +61,12 @@ class ImageUrlFormatter extends ImageFormatter { /** @var \Drupal\image\ImageStyleInterface $image_style */ $image_style = $this->imageStyleStorage->load($this->getSetting('image_style')); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); /** @var \Drupal\file\FileInterface[] $images */ foreach ($images as $delta => $image) { $image_uri = $image->getFileUri(); - $url = $image_style ? $image_style->buildUrl($image_uri) : file_create_url($image_uri); - $url = file_url_transform_relative($url); + $url = $image_style ? $file_url_generator->transformRelative($image_style->buildUrl($image_uri)) : $file_url_generator->generateString($image_uri); // Add cacheability metadata from the image and image style. $cacheability = CacheableMetadata::createFromObject($image); diff --git a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php index 27f005ed02d..a8169aaedcd 100644 --- a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php +++ b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php @@ -2,13 +2,13 @@ namespace Drupal\Tests\image\Functional; -use Drupal\Core\Url; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Url; +use Drupal\file\Entity\File; use Drupal\image\Entity\ImageStyle; use Drupal\image\ImageStyleInterface; use Drupal\node\Entity\Node; -use Drupal\file\Entity\File; use Drupal\Tests\TestFileCreationTrait; /** @@ -343,8 +343,11 @@ class ImageAdminStylesTest extends ImageFieldTestBase { $original_uri = File::load($fid)->getFileUri(); // Test that image is displayed using newly created style. + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $this->drupalGet('node/' . $nid); - $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri))); + $this->assertSession()->responseContains($file_url_generator->transformRelative($style->buildUrl($original_uri))); // Rename the style and make sure the image field is updated. $new_style_name = strtolower($this->randomMachineName(10)); @@ -360,7 +363,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase { // Reload the image style using the new name. $style = ImageStyle::load($new_style_name); - $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri))); + $this->assertSession()->responseContains($file_url_generator->transformRelative($style->buildUrl($original_uri))); // Delete the style and choose a replacement style. $edit = [ @@ -373,7 +376,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase { $replacement_style = ImageStyle::load('thumbnail'); $this->drupalGet('node/' . $nid); - $this->assertRaw(file_url_transform_relative($replacement_style->buildUrl($original_uri))); + $this->assertSession()->responseContains($file_url_generator->transformRelative($replacement_style->buildUrl($original_uri))); } /** @@ -493,7 +496,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase { // Test that image is displayed using newly created style. $this->drupalGet('node/' . $nid); - $this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri))); + $this->assertRaw(\Drupal::service('file_url_generator')->transformRelative($style->buildUrl($original_uri))); // Copy config to sync, and delete the image style. $sync = $this->container->get('config.storage.sync'); diff --git a/core/modules/image/tests/src/Functional/ImageDimensionsTest.php b/core/modules/image/tests/src/Functional/ImageDimensionsTest.php index 9fa6affb151..4820e5d14d4 100644 --- a/core/modules/image/tests/src/Functional/ImageDimensionsTest.php +++ b/core/modules/image/tests/src/Functional/ImageDimensionsTest.php @@ -50,7 +50,9 @@ class ImageDimensionsTest extends BrowserTestBase { $style = ImageStyle::create(['name' => 'test', 'label' => 'Test']); $style->save(); $generated_uri = 'public://styles/test/public/' . $file_system->basename($original_uri); - $url = file_url_transform_relative($style->buildUrl($original_uri)); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $url = $file_url_generator->transformRelative($style->buildUrl($original_uri)); $variables = [ '#theme' => 'image_style', @@ -266,7 +268,7 @@ class ImageDimensionsTest extends BrowserTestBase { ]; // PNG original image. Should be resized to 100x100. $generated_uri = 'public://styles/test_uri/public/' . $file_system->basename($original_uri); - $url = file_url_transform_relative($style->buildUrl($original_uri)); + $url = \Drupal::service('file_url_generator')->transformRelative($style->buildUrl($original_uri)); $this->assertEquals('', $this->getImageTag($variables)); $this->assertFileDoesNotExist($generated_uri); $this->drupalGet($this->getAbsoluteUrl($url)); @@ -279,7 +281,7 @@ class ImageDimensionsTest extends BrowserTestBase { $file = $files[1]; $original_uri = $file_system->copy($file->uri, 'public://', FileSystemInterface::EXISTS_RENAME); $generated_uri = 'public://styles/test_uri/public/' . $file_system->basename($original_uri); - $url = file_url_transform_relative($style->buildUrl($original_uri)); + $url = $file_url_generator->transformRelative($style->buildUrl($original_uri)); $variables['#uri'] = $original_uri; $this->assertEquals('', $this->getImageTag($variables)); $this->assertFileDoesNotExist($generated_uri); diff --git a/core/modules/image/tests/src/Functional/ImageEffect/ConvertTest.php b/core/modules/image/tests/src/Functional/ImageEffect/ConvertTest.php index e404e79550b..168b5dda39a 100644 --- a/core/modules/image/tests/src/Functional/ImageEffect/ConvertTest.php +++ b/core/modules/image/tests/src/Functional/ImageEffect/ConvertTest.php @@ -49,7 +49,7 @@ class ConvertTest extends BrowserTestBase { // Execute the image style on the test image via a GET request. $derivative_uri = 'public://styles/image_effect_test/public/image-test-do.png.jpeg'; $this->assertFileDoesNotExist($derivative_uri); - $url = file_url_transform_relative($image_style->buildUrl($test_uri)); + $url = \Drupal::service('file_url_generator')->transformRelative($image_style->buildUrl($test_uri)); $this->drupalGet($this->getAbsoluteUrl($url)); $this->assertSession()->statusCodeEquals(200); $this->assertFileExists($derivative_uri); diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php index 00b41d2c9f5..8e44c55afd9 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php @@ -106,6 +106,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { $node = $node_storage->load($nid); // Test that the default formatter is being used. + /** @var \Drupal\file\FileInterface $file */ $file = $node->{$field_name}->entity; $image_uri = $file->getFileUri(); $image = [ @@ -135,7 +136,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { '#height' => 20, '#alt' => $alt, ]; - $default_output = '' . $renderer->renderRoot($image) . ''; + $default_output = '' . $renderer->renderRoot($image) . ''; $this->drupalGet('node/' . $nid); $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $file->getCacheTags()[0]); // @todo Remove in https://www.drupal.org/node/2646744. @@ -144,7 +145,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'image_style:'); $this->assertRaw($default_output); // Verify that the image can be downloaded. - $this->assertEquals(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), 'File was downloaded successfully.'); + $this->assertEquals(file_get_contents($test_image->uri), $this->drupalGet($file->createFileUrl(FALSE)), 'File was downloaded successfully.'); if ($scheme == 'private') { // Only verify HTTP headers when using private scheme and the headers are // sent by Drupal. @@ -153,7 +154,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { // Log out and ensure the file cannot be accessed. $this->drupalLogout(); - $this->drupalGet(file_create_url($image_uri)); + $this->drupalGet($file->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(403); // Log in again. @@ -178,7 +179,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { '//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->createFileUrl(), ':width' => $image['#width'], ':height' => $image['#height'], ':alt' => $alt, @@ -221,12 +222,12 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { 'type' => 'image_url', 'settings' => ['image_style' => ''], ]; - $expected_url = file_url_transform_relative(file_create_url($image_uri)); + $expected_url = $file->createFileUrl(); $this->assertEquals($expected_url, $node->{$field_name}->view($display_options)[0]['#markup']); // Test the image URL formatter with an image style. $display_options['settings']['image_style'] = 'thumbnail'; - $expected_url = file_url_transform_relative(ImageStyle::load('thumbnail')->buildUrl($image_uri)); + $expected_url = \Drupal::service('file_url_generator')->transformRelative(ImageStyle::load('thumbnail')->buildUrl($image_uri)); $this->assertEquals($expected_url, $node->{$field_name}->view($display_options)[0]['#markup']); } @@ -285,7 +286,8 @@ class ImageFieldDisplayTest extends ImageFieldTestBase { $node = $node_storage->load($nid); $file = $node->{$field_name}->entity; - $url = file_url_transform_relative(ImageStyle::load('medium')->buildUrl($file->getFileUri())); + $file_url_generator = \Drupal::service('file_url_generator'); + $url = $file_url_generator->transformRelative(ImageStyle::load('medium')->buildUrl($file->getFileUri())); $this->assertSession()->elementExists('css', 'img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'); // Add alt/title fields to the image and verify that they are displayed. diff --git a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php index 9ce52c6ca64..6d89a920d85 100644 --- a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php @@ -322,7 +322,7 @@ class ImageStylesPathAndUrlTest extends BrowserTestBase { // Check that requesting a nonexistent image does not create any new // directories in the file system. $directory = $scheme . '://styles/' . $this->style->id() . '/' . $scheme . '/' . $this->randomMachineName(); - $this->drupalGet(file_create_url($directory . '/' . $this->randomString())); + $this->drupalGet(\Drupal::service('file_url_generator')->generateAbsoluteString($directory . '/' . $this->randomString())); $this->assertDirectoryDoesNotExist($directory); } diff --git a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php index 1ef1942be38..1f9654e71a5 100644 --- a/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php +++ b/core/modules/image/tests/src/Kernel/ImageThemeFunctionTest.php @@ -93,7 +93,7 @@ class ImageThemeFunctionTest extends KernelTestBase { // Create a style. $style = ImageStyle::create(['name' => 'test', 'label' => 'Test']); $style->save(); - $url = file_url_transform_relative($style->buildUrl($original_uri)); + $url = \Drupal::service('file_url_generator')->transformRelative($style->buildUrl($original_uri)); // Create a test entity with the image field set. $entity = EntityTest::create(); @@ -155,7 +155,7 @@ class ImageThemeFunctionTest extends KernelTestBase { // Create a style. $style = ImageStyle::create(['name' => 'image_test', 'label' => 'Test']); $style->save(); - $url = file_url_transform_relative($style->buildUrl($original_uri)); + $url = \Drupal::service('file_url_generator')->transformRelative($style->buildUrl($original_uri)); // Create the base element that we'll use in the tests below. $base_element = [ diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php index a444a85185c..7a123e728ca 100644 --- a/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php +++ b/core/modules/jsonapi/tests/src/Functional/JsonApiFunctionalTestBase.php @@ -286,7 +286,7 @@ abstract class JsonApiFunctionalTestBase extends BrowserTestBase { } if ($article_has_image) { $file = File::create([ - 'uri' => 'vfs://' . $random->name() . '.png', + 'uri' => 'public://' . $random->name() . '.png', ]); $file->setPermanent(); $file->save(); diff --git a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php index c7102a33d8c..f1c0ce3af26 100644 --- a/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php +++ b/core/modules/media/tests/src/Functional/Hal/MediaHalJsonAnonTest.php @@ -178,7 +178,7 @@ class MediaHalJsonAnonTest extends MediaResourceTestBase { // https://www.drupal.org/project/drupal/issues/2907402 is complete. // This link matches what is generated from File::url(), a resource // URL is currently not available. - 'href' => file_create_url($normalization['uri'][0]['value']), + 'href' => \Drupal::service('file_url_generator')->generateAbsoluteString($normalization['uri'][0]['value']), ], 'type' => [ 'href' => $this->baseUrl . '/rest/type/file/file', diff --git a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php index f60e345b8d7..6736fb057a4 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php @@ -869,7 +869,9 @@ class CKEditorIntegrationTest extends WebDriverTestBase { if ($drupalimage_is_enabled) { // Add an image with a link wrapped around it. $uri = $this->media->field_media_image->entity->getFileUri(); - $src = file_url_transform_relative(file_create_url($uri)); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $src = $file_url_generator->generateString($uri); $this->host->body->value .= 'drupalimage test image

'; } $this->host->save(); diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaDisplayTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaDisplayTest.php index 3ca198c1349..95b4135e9b6 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/MediaDisplayTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaDisplayTest.php @@ -110,7 +110,9 @@ class MediaDisplayTest extends MediaJavascriptTestBase { // Assert that the image src uses the large image style, the label is // visually hidden, and there is no link to the image file. $media_image = $assert_session->elementExists('css', '.media--type-image img'); - $expected_image_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg')); $this->assertStringContainsString($expected_image_src, $media_image->getAttribute('src')); $field = $assert_session->elementExists('css', '.field--name-field-media-image'); $assert_session->elementExists('css', '.field__label.visually-hidden', $field); diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php index 5b2a929d777..e4a0f942c4e 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php @@ -72,7 +72,9 @@ class MediaSourceImageTest extends MediaSourceTestBase { // src attribute uses the large image style, the label is visually hidden, // and there is no link to the image file. $image_element = $assert_session->elementExists('css', '.field--name-field-media-image img'); - $expected_image_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg')); $this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src')); $field = $assert_session->elementExists('css', '.field--name-field-media-image'); $assert_session->elementExists('css', '.field__label.visually-hidden', $field); diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaStandardProfileTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaStandardProfileTest.php index f98e1b3e284..728a4444a80 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/MediaStandardProfileTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaStandardProfileTest.php @@ -153,7 +153,9 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // Assert the audio file is present inside the media element and that its // src attribute matches the audio file. $audio_element = $assert_session->elementExists('css', 'article.media--type-audio .field--name-field-media-audio-file audio > source'); - $expected_audio_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected_audio_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename)); $this->assertSame($expected_audio_src, $audio_element->getAttribute('src')); // Assert the media name is updated through the field mapping when changing @@ -180,7 +182,7 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // Assert the audio file is present inside the media element and that its // src attribute matches the updated audio file. $audio_element = $assert_session->elementExists('css', 'article.media--type-audio .field--name-field-media-audio-file audio > source'); - $expected_audio_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated))); + $expected_audio_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated)); $this->assertSame($expected_audio_src, $audio_element->getAttribute('src')); } @@ -240,7 +242,10 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // src attribute uses the large image style, the label is visually hidden, // and there is no link to the image file. $image_element = $assert_session->elementExists('css', 'article.media--type-image img'); - $expected_image_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name)); + $this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src')); $assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden'); $assert_session->elementNotExists('css', '.field--name-field-media-image a'); @@ -272,7 +277,7 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // src attribute uses the large image style, the label is visually hidden, // and there is no link to the image file. $image_element = $assert_session->elementExists('css', 'article.media--type-image img'); - $expected_image_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name_updated))); + $expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name_updated)); $this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src')); $assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden'); $assert_session->elementNotExists('css', '.field--name-field-media-image a'); @@ -524,7 +529,9 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // Assert the video element is present inside the media element and that its // src attribute matches the video file. $video_element = $assert_session->elementExists('css', 'article.media--type-video .field--name-field-media-video-file video > source'); - $expected_video_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename))); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected_video_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename)); $this->assertSame($expected_video_src, $video_element->getAttribute('src')); // Assert the media name is updated through the field mapping when changing @@ -551,7 +558,7 @@ class MediaStandardProfileTest extends MediaJavascriptTestBase { // Assert the video element is present inside the media element and that its // src attribute matches the updated video file. $video_element = $assert_session->elementExists('css', 'article.media--type-video .field--name-field-media-video-file video > source'); - $expected_video_src = file_url_transform_relative(file_create_url(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated))); + $expected_video_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated)); $this->assertSame($expected_video_src, $video_element->getAttribute('src')); } diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php index 3b84023d4f0..91d0cf9c083 100644 --- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php +++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php @@ -104,7 +104,7 @@ class MediaEmbedFilterTest extends MediaEmbedFilterTestBase { 'media:1', 'media_view', ]) - ->setCacheContexts(['url.site', 'user.permissions']) + ->setCacheContexts(['user.permissions']) ->setCacheMaxAge(Cache::PERMANENT), ], 'custom attributes are retained' => [ diff --git a/core/modules/node/tests/src/Functional/NodeRSSContentTest.php b/core/modules/node/tests/src/Functional/NodeRSSContentTest.php index c9a363f568b..45bea1374b0 100644 --- a/core/modules/node/tests/src/Functional/NodeRSSContentTest.php +++ b/core/modules/node/tests/src/Functional/NodeRSSContentTest.php @@ -87,20 +87,22 @@ class NodeRSSContentTest extends NodeTestBase { 'type' => 'article', 'promote' => 1, ]; + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); $this->drupalCreateNode($defaults + [ 'body' => [ - 'value' => '

Root-relative URL

', + 'value' => '

Root-relative URL

', 'format' => 'full_html', ], ]); - $protocol_relative_url = substr(file_create_url('public://protocol-relative'), strlen(\Drupal::request()->getScheme() . ':')); + $protocol_relative_url = substr($file_url_generator->generateAbsoluteString('public://protocol-relative'), strlen(\Drupal::request()->getScheme() . ':')); $this->drupalCreateNode($defaults + [ 'body' => [ 'value' => '

Protocol-relative URL

', 'format' => 'full_html', ], ]); - $absolute_url = file_create_url('public://absolute'); + $absolute_url = $file_url_generator->generateAbsoluteString('public://absolute'); $this->drupalCreateNode($defaults + [ 'body' => [ 'value' => '

Absolute URL

', @@ -110,7 +112,7 @@ class NodeRSSContentTest extends NodeTestBase { $this->drupalGet('rss.xml'); // Verify that root-relative URL is transformed to absolute. - $this->assertRaw(file_create_url('public://root-relative')); + $this->assertSession()->responseContains($file_url_generator->generateAbsoluteString('public://root-relative')); // Verify that protocol-relative URL is left untouched. $this->assertRaw($protocol_relative_url); // Verify that absolute URL is left untouched. diff --git a/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php b/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php index d8e1030d754..28ffb6b2615 100644 --- a/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php +++ b/core/modules/rdf/tests/src/Functional/FileFieldAttributesTest.php @@ -101,7 +101,7 @@ class FileFieldAttributesTest extends FileFieldTestBase { $html = \Drupal::service('renderer')->renderRoot($node_render_array); $node_uri = $this->node->toUrl('canonical', ['absolute' => TRUE])->toString(); - $file_uri = file_create_url($this->file->getFileUri()); + $file_uri = $this->file->createFileUrl(FALSE); // Node relation to attached file. $expected_value = [ diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 56673710424..4e79255ac46 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -495,11 +495,15 @@ function _responsive_image_image_style_url($style_name, $path) { // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever return 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; } + + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $entity = ImageStyle::load($style_name); if ($entity instanceof ImageStyle) { - return file_url_transform_relative($entity->buildUrl($path)); + return $file_url_generator->transformRelative($entity->buildUrl($path)); } - return file_url_transform_relative(file_create_url($path)); + return $file_url_generator->generateString($path); } /** diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php index 2aa5c15b0a2..e6656c19b6c 100644 --- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php @@ -35,6 +35,13 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { */ protected $responsiveImgStyle; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * Modules to enable. * @@ -52,6 +59,8 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { protected function setUp(): void { parent::setUp(); + $this->fileUrlGenerator = $this->container->get('file_url_generator'); + // Create user. $this->adminUser = $this->drupalCreateUser([ 'administer responsive images', @@ -259,9 +268,9 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { // No image style cache tag should be found. $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'image_style:'); - $this->assertSession()->responseMatches('/\s*assertSession()->responseMatches('/fileUrlGenerator->generateString($image_uri), '/') . '"(.*?)>\s*assertEquals(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), 'File was downloaded successfully.'); + $this->assertEquals(file_get_contents($test_image->uri), $this->drupalGet($this->fileUrlGenerator->generateAbsoluteString($image_uri)), 'File was downloaded successfully.'); if ($scheme == 'private') { // Only verify HTTP headers when using private scheme and the headers are // sent by Drupal. @@ -270,7 +279,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { // Log out and ensure the file cannot be accessed. $this->drupalLogout(); - $this->drupalGet(file_create_url($image_uri)); + $this->drupalGet($this->fileUrlGenerator->generateAbsoluteString($image_uri)); $this->assertSession()->statusCodeEquals(403); // Log in again. @@ -295,10 +304,10 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); $thumbnail_style = ImageStyle::load('thumbnail'); // Assert the output of the 'srcset' attribute (small multipliers first). - $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . file_url_transform_relative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x'); + $this->assertSession()->responseContains('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . $this->fileUrlGenerator->transformRelative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x'); $this->assertRaw('/styles/medium/'); // Assert the output of the original image. - $this->assertRaw(file_url_transform_relative(file_create_url($image_uri)) . ' 3x'); + $this->assertSession()->responseContains($this->fileUrlGenerator->generateString($image_uri) . ' 3x'); // Assert the output of the breakpoints. $this->assertRaw('media="(min-width: 0px)"'); $this->assertRaw('media="(min-width: 560px)"'); @@ -307,7 +316,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { $this->assertSession()->responseMatches('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/'); // Assert the output of the 'srcset' attribute (small images first). $medium_style = ImageStyle::load('medium'); - $this->assertRaw(file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 220w, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 360w'); + $this->assertSession()->responseContains($this->fileUrlGenerator->transformRelative($medium_style->buildUrl($image_uri)) . ' 220w, ' . $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image_uri)) . ' 360w'); $this->assertRaw('media="(min-width: 851px)"'); } $this->assertRaw('/styles/large/'); @@ -324,7 +333,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { $fallback_image = [ '#theme' => 'image', '#alt' => $alt, - '#uri' => file_url_transform_relative($large_style->buildUrl($image->getSource())), + '#uri' => $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image->getSource())), ]; // The image.html.twig template has a newline after the tag but // responsive-image.html.twig doesn't have one after the fallback image, so @@ -404,7 +413,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { $thumbnail_style = ImageStyle::load('thumbnail'); $node = $node_storage->load($nid); $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); - $this->assertSession()->responseMatches('/srcset="' . preg_quote(file_url_transform_relative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/'); + $this->assertSession()->responseMatches('/srcset="' . preg_quote($this->fileUrlGenerator->transformRelative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/'); } /** @@ -451,7 +460,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { $medium_style = ImageStyle::load('medium'); $node = $node_storage->load($nid); $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); - $this->assertRaw('assertSession()->responseContains('assertSession()->responseMatches('/\s*assertSession()->responseMatches('/fileUrlGenerator->generateString($image_uri), '/') . '"(.*?)>\s*getEntityTypeId() === 'file') { - $values['url'] = file_create_url($entity->getFileUri()); + elseif ($entity instanceof FileInterface) { + $values['url'] = $entity->createFileUrl(FALSE); } } diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestDisabledElementsForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestDisabledElementsForm.php index a756dc50b66..eef9efae5c8 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestDisabledElementsForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestDisabledElementsForm.php @@ -209,6 +209,7 @@ class FormTestDisabledElementsForm extends FormBase { $form['image_button'] = [ '#type' => 'image_button', '#value' => 'Image button', + '#src' => '', '#disabled' => TRUE, ]; $form['button'] = [ diff --git a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php index baf49a5b256..95fab4c5c8a 100644 --- a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php +++ b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php @@ -26,7 +26,7 @@ class RetrieveFileTest extends BrowserTestBase { $file_system->mkdir($sourcedir = 'public://' . $this->randomMachineName()); // cSpell:disable-next-line $filename = 'Файл для тестирования ' . $this->randomMachineName(); - $url = file_create_url($sourcedir . '/' . $filename); + $url = \Drupal::service('file_url_generator')->generateAbsoluteString($sourcedir . '/' . $filename); $retrieved_file = system_retrieve_file($url); $this->assertFalse($retrieved_file, 'Non-existent file not fetched.'); diff --git a/core/modules/system/tests/src/Functional/System/ThemeTest.php b/core/modules/system/tests/src/Functional/System/ThemeTest.php index b5173cbe26e..e46d7133a3f 100644 --- a/core/modules/system/tests/src/Functional/System/ThemeTest.php +++ b/core/modules/system/tests/src/Functional/System/ThemeTest.php @@ -75,21 +75,23 @@ class ThemeTest extends BrowserTestBase { $file_relative = strtr($file->uri, ['public:/' => PublicStream::basePath()]); $default_theme_path = 'core/themes/classy'; + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); $supported_paths = [ // Raw stream wrapper URI. $file->uri => [ 'form' => StreamWrapperManager::getTarget($file->uri), - 'src' => file_url_transform_relative(file_create_url($file->uri)), + 'src' => $file_url_generator->generateString($file->uri), ], // Relative path within the public filesystem. StreamWrapperManager::getTarget($file->uri) => [ 'form' => StreamWrapperManager::getTarget($file->uri), - 'src' => file_url_transform_relative(file_create_url($file->uri)), + 'src' => $file_url_generator->generateString($file->uri), ], // Relative path to a public file. $file_relative => [ 'form' => $file_relative, - 'src' => file_url_transform_relative(file_create_url($file->uri)), + 'src' => $file_url_generator->generateString($file->uri), ], // Relative path to an arbitrary file. 'core/misc/druplicon.png' => [ @@ -198,7 +200,7 @@ class ThemeTest extends BrowserTestBase { ':rel' => 'home', ] ); - $this->assertEquals(file_url_transform_relative(file_create_url($uploaded_filename)), $elements[0]->getAttribute('src')); + $this->assertEquals($file_url_generator->generateString($uploaded_filename), $elements[0]->getAttribute('src')); $this->container->get('theme_installer')->install(['bartik']); diff --git a/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php b/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php index 470c28aab55..fbe9819474b 100644 --- a/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php +++ b/core/modules/system/tests/src/Functional/Theme/EngineTwigTest.php @@ -133,7 +133,9 @@ class EngineTwigTest extends BrowserTestBase { */ public function testTwigFileUrls() { $this->drupalGet('/twig-theme-test/file-url'); - $filepath = file_url_transform_relative(file_create_url('core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js')); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $filepath = $file_url_generator->generateString('core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js'); $this->assertRaw('
file_url: ' . $filepath . '
'); } diff --git a/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php b/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php index adfa53364cc..38f5be61cf6 100644 --- a/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php +++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyImageTest.php @@ -106,13 +106,13 @@ class TaxonomyImageTest extends TaxonomyTestBase { // Ensure a user that should be able to access the file can access it. $this->drupalLogin($access_user); - $this->drupalGet(file_create_url($image->getFileUri())); + $this->drupalGet($image->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(200); // Ensure a user that should not be able to access the file cannot access // it. $this->drupalLogin($no_access_user); - $this->drupalGet(file_create_url($image->getFileUri())); + $this->drupalGet($image->createFileUrl(FALSE)); $this->assertSession()->statusCodeEquals(403); } diff --git a/core/modules/user/tests/src/Functional/UserPictureTest.php b/core/modules/user/tests/src/Functional/UserPictureTest.php index b20c9b4c7eb..e83d2a214df 100644 --- a/core/modules/user/tests/src/Functional/UserPictureTest.php +++ b/core/modules/user/tests/src/Functional/UserPictureTest.php @@ -116,7 +116,7 @@ class UserPictureTest extends BrowserTestBase { $image_style_id = $this->config('core.entity_view_display.user.user.compact')->get('content.user_picture.settings.image_style'); $style = ImageStyle::load($image_style_id); - $image_url = file_url_transform_relative($style->buildUrl($file->getfileUri())); + $image_url = \Drupal::service('file_url_generator')->transformRelative($style->buildUrl($file->getfileUri())); $alt_text = 'Profile picture for user ' . $this->webUser->getAccountName(); // Verify that the image is displayed on the node page. diff --git a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php index 1c5a92affdf..221de328ca7 100644 --- a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php @@ -35,6 +35,13 @@ class AttachedAssetsTest extends KernelTestBase { */ protected $renderer; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * {@inheritdoc} */ @@ -48,6 +55,7 @@ class AttachedAssetsTest extends KernelTestBase { $this->assetResolver = $this->container->get('asset.resolver'); $this->renderer = $this->container->get('renderer'); + $this->fileUrlGenerator = $this->container->get('file_url_generator'); } /** @@ -88,8 +96,8 @@ class AttachedAssetsTest extends KernelTestBase { $rendered_css = $this->renderer->renderPlain($css_render_array); $rendered_js = $this->renderer->renderPlain($js_render_array); $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; - $this->assertStringContainsString('', $rendered_css, 'Rendering an external CSS file.'); - $this->assertStringContainsString('', $rendered_js, 'Rendering an external JavaScript file.'); + $this->assertStringContainsString('', $rendered_css, 'Rendering an external CSS file.'); + $this->assertStringContainsString('', $rendered_js, 'Rendering an external JavaScript file.'); } /** @@ -142,7 +150,7 @@ class AttachedAssetsTest extends KernelTestBase { $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js); $rendered_js = $this->renderer->renderPlain($js_render_array); $expected_1 = ''; - $expected_2 = ''; + $expected_2 = ''; $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.'); $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered internal JavaScript with correct defer and random attributes.'); } @@ -158,7 +166,7 @@ class AttachedAssetsTest extends KernelTestBase { $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js); $rendered_js = $this->renderer->renderPlain($js_render_array); $expected_1 = ''; - $expected_2 = ''; + $expected_2 = ''; $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered external JavaScript with correct defer and random attributes.'); $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered internal JavaScript with correct defer and random attributes.'); } @@ -232,9 +240,9 @@ class AttachedAssetsTest extends KernelTestBase { $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js); $rendered_js = $this->renderer->renderPlain($js_render_array); $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; - $this->assertStringContainsString('', $rendered_js, 'The JS asset in common_test/js-header appears in the header.'); - $this->assertStringContainsString('', $rendered_js, 'The JS asset in common_test/js-header appears in the header.'); + $this->assertStringContainsString('' . "\n"; + $expected_1 = ""; + $expected_2 = "\n" . '' . "\n"; $this->assertStringContainsString($expected_1, $rendered_js, 'Rendered JavaScript within downlevel-hidden conditional comments.'); $this->assertStringContainsString($expected_2, $rendered_js, 'Rendered JavaScript within downlevel-revealed conditional comments.'); @@ -475,8 +483,8 @@ class AttachedAssetsTest extends KernelTestBase { $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js); $rendered_js = $this->renderer->renderPlain($js_render_array); $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; - $this->assertStringContainsString('', $rendered_css, 'CSS file with query string gets version query string correctly appended..'); - $this->assertStringContainsString('', $rendered_js, 'JavaScript file with query string gets version query string correctly appended.'); + $this->assertStringContainsString('', $rendered_css, 'CSS file with query string gets version query string correctly appended..'); + $this->assertStringContainsString('', $rendered_js, 'JavaScript file with query string gets version query string correctly appended.'); } } diff --git a/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php new file mode 100644 index 00000000000..c383b0ab592 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php @@ -0,0 +1,26 @@ +expectDeprecation('file_create_url() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use the appropriate method on \Drupal\Core\File\FileUrlGeneratorInterface instead. See https://www.drupal.org/node/2940031'); + $this->expectDeprecation('file_url_transform_relative() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\File\FileUrlGenerator::transformRelative() instead. See https://www.drupal.org/node/2940031'); + $filepath = 'core/assets/vendor/jquery/jquery.min.js'; + $url = file_url_transform_relative(file_create_url($filepath)); + $this->assertNotEmpty($url); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php new file mode 100644 index 00000000000..af8e25e9b52 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/File/FileUrlGeneratorTest.php @@ -0,0 +1,257 @@ +fileUrlGenerator = $this->container->get('file_url_generator'); + } + + /** + * Tests missing stream handler. + * + * @covers ::generate + */ + public function testGenerateMissingStreamWrapper() { + $this->expectException(InvalidStreamWrapperException::class); + $result = $this->fileUrlGenerator->generate("foo://bar"); + } + + /** + * Tests missing stream handler. + * + * @covers ::generateString + */ + public function testGenerateStringMissingStreamWrapper() { + $this->expectException(InvalidStreamWrapperException::class); + $result = $this->fileUrlGenerator->generateString("foo://bar"); + } + + /** + * Tests missing stream handler. + * + * @covers ::generateAbsoluteString + */ + public function testGenerateAbsoluteStringMissingStreamWrapper() { + $this->expectException(InvalidStreamWrapperException::class); + $result = $this->fileUrlGenerator->generateAbsoluteString("foo://bar"); + } + + /** + * Tests the rewriting of shipped file URLs by hook_file_url_alter(). + * + * @covers ::generateAbsoluteString + */ + public function testShippedFileURL() { + // Test generating a URL to a shipped file (i.e. a file that is part of + // Drupal core, a module or a theme, for example a JavaScript file). + + // Test alteration of file URLs to use a CDN. + \Drupal::state()->set('file_test.hook_file_url_alter', 'cdn'); + $filepath = 'core/assets/vendor/jquery/jquery.min.js'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.'); + $filepath = 'core/misc/favicon.ico'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.'); + + // Test alteration of file URLs to use root-relative URLs. + \Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative'); + $filepath = 'core/assets/vendor/jquery/jquery.min.js'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.'); + $filepath = 'core/misc/favicon.ico'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.'); + + // Test alteration of file URLs to use protocol-relative URLs. + \Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative'); + $filepath = 'core/assets/vendor/jquery/jquery.min.js'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.'); + $filepath = 'core/misc/favicon.ico'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertEquals('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.'); + + // Test alteration of file URLs with query strings and/or fragment. + \Drupal::state()->delete('file_test.hook_file_url_alter'); + $filepath = 'core/misc/favicon.ico'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath . '?foo'); + $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=', $url, 'Correctly generated URL. The query string is present.'); + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath . '?foo=bar'); + $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar', $url, 'Correctly generated URL. The query string is present.'); + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath . '#v1.2'); + $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '#v1.2', $url, 'Correctly generated URL. The fragment is present.'); + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath . '?foo=bar#v1.2'); + $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar#v1.2', $url, 'Correctly generated URL. The query string amd fragment is present.'); + } + + /** + * Tests the rewriting of public managed file URLs by hook_file_url_alter(). + * + * @covers ::generateAbsoluteString + */ + public function testPublicManagedFileURL() { + // Test generating a URL to a managed file. + + // Test alteration of file URLs to use a CDN. + \Drupal::state()->set('file_test.hook_file_url_alter', 'cdn'); + $uri = $this->createUri(); + $url = $this->fileUrlGenerator->generateAbsoluteString($uri); + $public_directory_path = \Drupal::service('stream_wrapper_manager') + ->getViaScheme('public') + ->getDirectoryPath(); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $this->assertEquals(FILE_URL_TEST_CDN_2 . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a CDN URL for a created file.'); + + // Test alteration of file URLs to use root-relative URLs. + \Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative'); + $uri = $this->createUri(); + $url = $this->fileUrlGenerator->generateAbsoluteString($uri); + $this->assertEquals(base_path() . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a root-relative URL for a created file.'); + + // Test alteration of file URLs to use a protocol-relative URLs. + \Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative'); + $uri = $this->createUri(); + $url = $this->fileUrlGenerator->generateAbsoluteString($uri); + $this->assertEquals('/' . base_path() . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a protocol-relative URL for a created file.'); + } + + /** + * Tests generate absolute string with relative URL. + * + * @covers ::generateAbsoluteString + */ + public function testRelativeFileURL() { + // Disable file_test.module's hook_file_url_alter() implementation. + \Drupal::state()->set('file_test.hook_file_url_alter', NULL); + + // Create a mock Request for transformRelative(). + $request = Request::create($GLOBALS['base_url']); + $this->container->get('request_stack')->push($request); + \Drupal::setContainer($this->container); + + // Shipped file. + $filepath = 'core/assets/vendor/jquery/jquery.min.js'; + $url = $this->fileUrlGenerator->generateAbsoluteString($filepath); + $this->assertSame(base_path() . $filepath, $this->fileUrlGenerator->transformRelative($url)); + + // Managed file. + $uri = $this->createUri(); + $url = $this->fileUrlGenerator->generateAbsoluteString($uri); + $public_directory_path = \Drupal::service('stream_wrapper_manager') + ->getViaScheme('public') + ->getDirectoryPath(); + $this->assertSame(base_path() . $public_directory_path . '/' . rawurlencode(\Drupal::service('file_system') + ->basename($uri)), $this->fileUrlGenerator->transformRelative($url)); + } + + /** + * @covers ::generate + * + * @dataProvider providerGenerateURI + */ + public function testGenerateURI($filepath, $expected) { + // Disable file_test.module's hook_file_url_alter() implementation. + \Drupal::state()->set('file_test.hook_file_url_alter', NULL); + + // Create a mock Request for transformRelative(). + $request = Request::create($GLOBALS['base_url']); + $this->container->get('request_stack')->push($request); + \Drupal::setContainer($this->container); + + // No schema file. + $url = $this->fileUrlGenerator->generate($filepath); + $this->assertEquals($expected, $url->getUri()); + } + + /** + * @covers ::generate + */ + public function testGenerateURIWithSchema() { + // Disable file_test.module's hook_file_url_alter() implementation. + \Drupal::state()->set('file_test.hook_file_url_alter', NULL); + + // Create a mock Request for transformRelative(). + $request = Request::create($GLOBALS['base_url']); + $this->container->get('request_stack')->push($request); + \Drupal::setContainer($this->container); + + $public_directory_path = \Drupal::service('stream_wrapper_manager') + ->getViaScheme('public') + ->getDirectoryPath(); + + $url = $this->fileUrlGenerator->generate('public://path/to/file.png'); + $this->assertEquals('base:/' . $public_directory_path . '/path/to/file.png', $url->getUri()); + } + + /** + * Data provider. + */ + public function providerGenerateURI() { + return [ + 'schemaless' => + [ + '//core/assets/vendor/jquery/jquery.min.js', + '//core/assets/vendor/jquery/jquery.min.js', + ], + 'query string' => + [ + '//core/assets/vendor/jquery/jquery.min.js?foo', + '//core/assets/vendor/jquery/jquery.min.js?foo', + ], + 'query string and hashes' => + [ + '//core/assets/vendor/jquery/jquery.min.js?foo=bar#whizz', + '//core/assets/vendor/jquery/jquery.min.js?foo=bar#whizz', + ], + 'hashes' => + [ + '//core/assets/vendor/jquery/jquery.min.js#whizz', + '//core/assets/vendor/jquery/jquery.min.js#whizz', + ], + 'root-relative' => + [ + '/core/assets/vendor/jquery/jquery.min.js', + 'base:/core/assets/vendor/jquery/jquery.min.js', + ], + 'relative' => + [ + 'core/assets/vendor/jquery/jquery.min.js', + 'base:core/assets/vendor/jquery/jquery.min.js', + ], + 'external' => + [ + 'https://www.example.com/core/assets/vendor/jquery/jquery.min.js', + 'https://www.example.com/core/assets/vendor/jquery/jquery.min.js', + ], + ]; + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php b/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php index 845cebef9f3..6576c72c060 100644 --- a/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/StreamWrapperTest.php @@ -4,6 +4,7 @@ namespace Drupal\KernelTests\Core\File; use Drupal\Core\DrupalKernel; use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\PublicStream; use Symfony\Component\HttpFoundation\Request; @@ -98,12 +99,14 @@ class StreamWrapperTest extends FileTestBase { $config->set('default_scheme', 'private')->save(); $this->assertEquals('private://foo/bar.txt', file_build_uri('foo/bar.txt'), 'Got a valid URI from foo/bar.txt.'); - // Test file_create_url() + // Test FileUrlGeneratorInterface::generateString() // TemporaryStream::getExternalUrl() uses Url::fromRoute(), which needs // route information to work. - $this->assertStringContainsString('system/temporary?file=test.txt', file_create_url('temporary://test.txt'), 'Temporary external URL correctly built.'); - $this->assertStringContainsString(Settings::get('file_public_path') . '/test.txt', file_create_url('public://test.txt'), 'Public external URL correctly built.'); - $this->assertStringContainsString('system/files/test.txt', file_create_url('private://test.txt'), 'Private external URL correctly built.'); + $file_url_generator = $this->container->get('file_url_generator'); + assert($file_url_generator instanceof FileUrlGeneratorInterface); + $this->assertStringContainsString('system/temporary?file=test.txt', $file_url_generator->generateString('temporary://test.txt'), 'Temporary external URL correctly built.'); + $this->assertStringContainsString(Settings::get('file_public_path') . '/test.txt', $file_url_generator->generateString('public://test.txt'), 'Public external URL correctly built.'); + $this->assertStringContainsString('system/files/test.txt', $file_url_generator->generateString('private://test.txt'), 'Private external URL correctly built.'); } /** diff --git a/core/tests/Drupal/KernelTests/Core/File/UrlRewritingTest.php b/core/tests/Drupal/KernelTests/Core/File/UrlRewritingTest.php deleted file mode 100644 index fdabb1a3f83..00000000000 --- a/core/tests/Drupal/KernelTests/Core/File/UrlRewritingTest.php +++ /dev/null @@ -1,120 +0,0 @@ -set('file_test.hook_file_url_alter', 'cdn'); - $filepath = 'core/assets/vendor/jquery/jquery.min.js'; - $url = file_create_url($filepath); - $this->assertEquals(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.'); - $filepath = 'core/misc/favicon.ico'; - $url = file_create_url($filepath); - $this->assertEquals(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.'); - - // Test alteration of file URLs to use root-relative URLs. - \Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative'); - $filepath = 'core/assets/vendor/jquery/jquery.min.js'; - $url = file_create_url($filepath); - $this->assertEquals(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.'); - $filepath = 'core/misc/favicon.ico'; - $url = file_create_url($filepath); - $this->assertEquals(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.'); - - // Test alteration of file URLs to use protocol-relative URLs. - \Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative'); - $filepath = 'core/assets/vendor/jquery/jquery.min.js'; - $url = file_create_url($filepath); - $this->assertEquals('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.'); - $filepath = 'core/misc/favicon.ico'; - $url = file_create_url($filepath); - $this->assertEquals('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.'); - - // Test alteration of file URLs with query strings and/or fragment. - \Drupal::state()->delete('file_test.hook_file_url_alter'); - $filepath = 'core/misc/favicon.ico'; - $url = file_create_url($filepath . '?foo'); - $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=', $url, 'Correctly generated URL. The query string is present.'); - $url = file_create_url($filepath . '?foo=bar'); - $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar', $url, 'Correctly generated URL. The query string is present.'); - $url = file_create_url($filepath . '#v1.2'); - $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '#v1.2', $url, 'Correctly generated URL. The fragment is present.'); - $url = file_create_url($filepath . '?foo=bar#v1.2'); - $this->assertEquals($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar#v1.2', $url, 'Correctly generated URL. The query string amd fragment is present.'); - } - - /** - * Tests the rewriting of public managed file URLs by hook_file_url_alter(). - */ - public function testPublicManagedFileURL() { - // Test generating a URL to a managed file. - - // Test alteration of file URLs to use a CDN. - \Drupal::state()->set('file_test.hook_file_url_alter', 'cdn'); - $uri = $this->createUri(); - $url = file_create_url($uri); - $public_directory_path = \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath(); - /** @var \Drupal\Core\File\FileSystemInterface $file_system */ - $file_system = \Drupal::service('file_system'); - $this->assertEquals(FILE_URL_TEST_CDN_2 . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a CDN URL for a created file.'); - - // Test alteration of file URLs to use root-relative URLs. - \Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative'); - $uri = $this->createUri(); - $url = file_create_url($uri); - $this->assertEquals(base_path() . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a root-relative URL for a created file.'); - - // Test alteration of file URLs to use a protocol-relative URLs. - \Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative'); - $uri = $this->createUri(); - $url = file_create_url($uri); - $this->assertEquals('/' . base_path() . '/' . $public_directory_path . '/' . $file_system->basename($uri), $url, 'Correctly generated a protocol-relative URL for a created file.'); - } - - /** - * Tests file_url_transform_relative(). - */ - public function testRelativeFileURL() { - // Disable file_test.module's hook_file_url_alter() implementation. - \Drupal::state()->set('file_test.hook_file_url_alter', NULL); - - // Create a mock Request for file_url_transform_relative(). - $request = Request::create($GLOBALS['base_url']); - $this->container->get('request_stack')->push($request); - \Drupal::setContainer($this->container); - - // Shipped file. - $filepath = 'core/assets/vendor/jquery/jquery.min.js'; - $url = file_create_url($filepath); - $this->assertSame(base_path() . $filepath, file_url_transform_relative($url)); - - // Managed file. - $uri = $this->createUri(); - $url = file_create_url($uri); - $public_directory_path = \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath(); - $this->assertSame(base_path() . $public_directory_path . '/' . rawurlencode(\Drupal::service('file_system')->basename($uri)), file_url_transform_relative($url)); - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/File/UrlTransformRelativeTest.php b/core/tests/Drupal/KernelTests/Core/File/UrlTransformRelativeTest.php index 1b2153b1da1..d6f2d00dc18 100644 --- a/core/tests/Drupal/KernelTests/Core/File/UrlTransformRelativeTest.php +++ b/core/tests/Drupal/KernelTests/Core/File/UrlTransformRelativeTest.php @@ -15,7 +15,7 @@ class UrlTransformRelativeTest extends KernelTestBase { protected static $modules = ['file_test']; /** - * Tests file_url_transform_relative function. + * Tests transformRelative() function. * * @dataProvider providerFileUrlTransformRelative */ @@ -37,7 +37,7 @@ class UrlTransformRelativeTest extends KernelTestBase { $request = Request::createFromGlobals(); \Drupal::requestStack()->push($request); - $this->assertSame($expected, file_url_transform_relative($url)); + $this->assertSame($expected, \Drupal::service('file_url_generator')->transformRelative($url)); } public function providerFileUrlTransformRelative() { diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php index 6e69db87331..ecbf860228a 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/ImageTest.php @@ -19,6 +19,13 @@ class ImageTest extends KernelTestBase { */ protected static $modules = ['system']; + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + /** * The images to test with. * @@ -29,13 +36,15 @@ class ImageTest extends KernelTestBase { protected function setUp(): void { parent::setUp(); - // The code under test uses file_url_transform_relative(), which relies on + // The code under test uses transformRelative(), which relies on // the Request containing the correct hostname. KernelTestBase doesn't set // it, so push another request onto the stack to ensure it's correct. $request = Request::create('/', 'GET', [], [], [], $_SERVER); $this->container = \Drupal::service('kernel')->getContainer(); $this->container->get('request_stack')->push($request); + $this->fileUrlGenerator = $this->container->get('file_url_generator'); + $this->testImages = [ 'core/misc/druplicon.png', 'core/misc/loading.gif', @@ -79,7 +88,9 @@ class ImageTest extends KernelTestBase { $this->render($image); // Make sure the src attribute has the correct value. - $this->assertRaw(file_url_transform_relative(file_create_url($image['#uri'])), 'Correct output for an image with the src attribute.'); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $this->fileUrlGenerator */ + $this->fileUrlGenerator = $this->fileUrlGenerator; + $this->assertRaw($this->fileUrlGenerator->generateString($image['#uri']), 'Correct output for an image with the src attribute.'); } /** @@ -107,7 +118,7 @@ class ImageTest extends KernelTestBase { $this->render($image); // Make sure the srcset attribute has the correct value. - $this->assertRaw(file_url_transform_relative(file_create_url($this->testImages[0])) . ' 1x, ' . file_url_transform_relative(file_create_url($this->testImages[1])) . ' 2x', 'Correct output for image with srcset attribute and multipliers.'); + $this->assertRaw($this->fileUrlGenerator->transformRelative($this->fileUrlGenerator->generateString($this->testImages[0])) . ' 1x, ' . $this->fileUrlGenerator->transformRelative($this->fileUrlGenerator->generateString($this->testImages[1])) . ' 2x', 'Correct output for image with srcset attribute and multipliers.'); } /** @@ -139,7 +150,7 @@ class ImageTest extends KernelTestBase { $this->render($image); // Make sure the srcset attribute has the correct value. - $this->assertRaw(file_url_transform_relative(file_create_url($this->testImages[0])) . ' ' . $widths[0] . ', ' . file_url_transform_relative(file_create_url($this->testImages[1])) . ' ' . $widths[1], 'Correct output for image with srcset attribute and width descriptors.'); + $this->assertRaw($this->fileUrlGenerator->generateString($this->testImages[0]) . ' ' . $widths[0] . ', ' . $this->fileUrlGenerator->transformRelative($this->fileUrlGenerator->generateString($this->testImages[1])) . ' ' . $widths[1], 'Correct output for image with srcset attribute and width descriptors.'); } } diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeSettingsTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeSettingsTest.php index 924005304d3..f5ce5cfec56 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeSettingsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeSettingsTest.php @@ -85,7 +85,9 @@ class ThemeSettingsTest extends KernelTestBase { theme_settings_convert_to_config($values, $config)->save(); // Tests logo path with scheme. - $expected = file_url_transform_relative(file_create_url('public://logo_with_scheme.png')); + /** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */ + $file_url_generator = \Drupal::service('file_url_generator'); + $expected = $file_url_generator->generateString('public://logo_with_scheme.png'); $this->assertEquals($expected, theme_get_setting('logo.url', 'stark')); $values = [ diff --git a/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php b/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php index b95164fc17a..4288598a592 100644 --- a/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php +++ b/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php @@ -123,8 +123,8 @@ trait BrowserHtmlDebugTrait { $html_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.html'; file_put_contents($this->htmlOutputDirectory . '/' . $html_output_filename, $message); file_put_contents($this->htmlOutputCounterStorage, $this->htmlOutputCounter++); - // Do not use file_create_url() as the module_handler service might not be - // available. + // Do not use the file_url_generator service as the module_handler service + // might not be available. $uri = $this->htmlOutputBaseUrl . '/sites/simpletest/browser_output/' . $html_output_filename; file_put_contents($this->htmlOutputFile, $uri . "\n", FILE_APPEND); } diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php index 39d65bfa4e9..cd1d6687962 100644 --- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\Core\Asset; use Drupal\Core\Asset\CssCollectionRenderer; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Tests\UnitTestCase; use Drupal\Core\State\StateInterface; @@ -33,8 +34,15 @@ class CssCollectionRendererUnitTest extends UnitTestCase { protected function setUp(): void { parent::setUp(); $state = $this->prophesize(StateInterface::class); + $file_url_generator = $this->createMock(FileUrlGeneratorInterface::class); + $file_url_generator->expects($this->any()) + ->method('generateString') + ->with($this->isType('string')) + ->willReturnCallback(function ($uri) { + return 'generated-relative-url:' . $uri; + }); $state->get('system.css_js_query_string', '0')->shouldBeCalledOnce()->willReturn(NULL); - $this->renderer = new CssCollectionRenderer($state->reveal()); + $this->renderer = new CssCollectionRenderer($state->reveal(), $file_url_generator); $this->fileCssGroup = [ 'group' => -100, 'type' => 'file', @@ -110,7 +118,7 @@ class CssCollectionRendererUnitTest extends UnitTestCase { 0 => ['group' => 0, 'type' => 'file', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'public://css/file-all', 'browsers' => []], ], [ - 0 => $create_link_element(file_url_transform_relative(file_create_url('public://css/file-all')) . '?', 'all'), + 0 => $create_link_element('generated-relative-url:public://css/file-all' . '?', 'all'), ], ], // Single file CSS asset with custom attributes. @@ -119,7 +127,7 @@ class CssCollectionRendererUnitTest extends UnitTestCase { 0 => ['group' => 0, 'type' => 'file', 'media' => 'all', 'preprocess' => TRUE, 'data' => 'public://css/file-all', 'browsers' => [], 'attributes' => $custom_attributes], ], [ - 0 => $create_link_element(file_url_transform_relative(file_create_url('public://css/file-all')) . '?', 'all', [], $custom_attributes), + 0 => $create_link_element('generated-relative-url:public://css/file-all' . '?', 'all', [], $custom_attributes), ], ], // 31 file CSS assets: expect 31 link elements. @@ -158,37 +166,37 @@ class CssCollectionRendererUnitTest extends UnitTestCase { 30 => $create_file_css_asset('public://css/31.css'), ], [ - 0 => $create_link_element(file_url_transform_relative(file_create_url('public://css/1.css')) . '?'), - 1 => $create_link_element(file_url_transform_relative(file_create_url('public://css/2.css')) . '?'), - 2 => $create_link_element(file_url_transform_relative(file_create_url('public://css/3.css')) . '?'), - 3 => $create_link_element(file_url_transform_relative(file_create_url('public://css/4.css')) . '?'), - 4 => $create_link_element(file_url_transform_relative(file_create_url('public://css/5.css')) . '?'), - 5 => $create_link_element(file_url_transform_relative(file_create_url('public://css/6.css')) . '?'), - 6 => $create_link_element(file_url_transform_relative(file_create_url('public://css/7.css')) . '?'), - 7 => $create_link_element(file_url_transform_relative(file_create_url('public://css/8.css')) . '?'), - 8 => $create_link_element(file_url_transform_relative(file_create_url('public://css/9.css')) . '?'), - 9 => $create_link_element(file_url_transform_relative(file_create_url('public://css/10.css')) . '?'), - 10 => $create_link_element(file_url_transform_relative(file_create_url('public://css/11.css')) . '?'), - 11 => $create_link_element(file_url_transform_relative(file_create_url('public://css/12.css')) . '?'), - 12 => $create_link_element(file_url_transform_relative(file_create_url('public://css/13.css')) . '?'), - 13 => $create_link_element(file_url_transform_relative(file_create_url('public://css/14.css')) . '?'), - 14 => $create_link_element(file_url_transform_relative(file_create_url('public://css/15.css')) . '?'), - 15 => $create_link_element(file_url_transform_relative(file_create_url('public://css/16.css')) . '?'), - 16 => $create_link_element(file_url_transform_relative(file_create_url('public://css/17.css')) . '?'), - 17 => $create_link_element(file_url_transform_relative(file_create_url('public://css/18.css')) . '?'), - 18 => $create_link_element(file_url_transform_relative(file_create_url('public://css/19.css')) . '?'), - 19 => $create_link_element(file_url_transform_relative(file_create_url('public://css/20.css')) . '?'), - 20 => $create_link_element(file_url_transform_relative(file_create_url('public://css/21.css')) . '?'), - 21 => $create_link_element(file_url_transform_relative(file_create_url('public://css/22.css')) . '?'), - 22 => $create_link_element(file_url_transform_relative(file_create_url('public://css/23.css')) . '?'), - 23 => $create_link_element(file_url_transform_relative(file_create_url('public://css/24.css')) . '?'), - 24 => $create_link_element(file_url_transform_relative(file_create_url('public://css/25.css')) . '?'), - 25 => $create_link_element(file_url_transform_relative(file_create_url('public://css/26.css')) . '?'), - 26 => $create_link_element(file_url_transform_relative(file_create_url('public://css/27.css')) . '?'), - 27 => $create_link_element(file_url_transform_relative(file_create_url('public://css/28.css')) . '?'), - 28 => $create_link_element(file_url_transform_relative(file_create_url('public://css/29.css')) . '?'), - 29 => $create_link_element(file_url_transform_relative(file_create_url('public://css/30.css')) . '?'), - 30 => $create_link_element(file_url_transform_relative(file_create_url('public://css/31.css')) . '?'), + 0 => $create_link_element('generated-relative-url:public://css/1.css' . '?'), + 1 => $create_link_element('generated-relative-url:public://css/2.css' . '?'), + 2 => $create_link_element('generated-relative-url:public://css/3.css' . '?'), + 3 => $create_link_element('generated-relative-url:public://css/4.css' . '?'), + 4 => $create_link_element('generated-relative-url:public://css/5.css' . '?'), + 5 => $create_link_element('generated-relative-url:public://css/6.css' . '?'), + 6 => $create_link_element('generated-relative-url:public://css/7.css' . '?'), + 7 => $create_link_element('generated-relative-url:public://css/8.css' . '?'), + 8 => $create_link_element('generated-relative-url:public://css/9.css' . '?'), + 9 => $create_link_element('generated-relative-url:public://css/10.css' . '?'), + 10 => $create_link_element('generated-relative-url:public://css/11.css' . '?'), + 11 => $create_link_element('generated-relative-url:public://css/12.css' . '?'), + 12 => $create_link_element('generated-relative-url:public://css/13.css' . '?'), + 13 => $create_link_element('generated-relative-url:public://css/14.css' . '?'), + 14 => $create_link_element('generated-relative-url:public://css/15.css' . '?'), + 15 => $create_link_element('generated-relative-url:public://css/16.css' . '?'), + 16 => $create_link_element('generated-relative-url:public://css/17.css' . '?'), + 17 => $create_link_element('generated-relative-url:public://css/18.css' . '?'), + 18 => $create_link_element('generated-relative-url:public://css/19.css' . '?'), + 19 => $create_link_element('generated-relative-url:public://css/20.css' . '?'), + 20 => $create_link_element('generated-relative-url:public://css/21.css' . '?'), + 21 => $create_link_element('generated-relative-url:public://css/22.css' . '?'), + 22 => $create_link_element('generated-relative-url:public://css/23.css' . '?'), + 23 => $create_link_element('generated-relative-url:public://css/24.css' . '?'), + 24 => $create_link_element('generated-relative-url:public://css/25.css' . '?'), + 25 => $create_link_element('generated-relative-url:public://css/26.css' . '?'), + 26 => $create_link_element('generated-relative-url:public://css/27.css' . '?'), + 27 => $create_link_element('generated-relative-url:public://css/28.css' . '?'), + 28 => $create_link_element('generated-relative-url:public://css/29.css' . '?'), + 29 => $create_link_element('generated-relative-url:public://css/30.css' . '?'), + 30 => $create_link_element('generated-relative-url:public://css/31.css' . '?'), ], ], // 32 file CSS assets with the same properties, except for the 10th and @@ -229,38 +237,38 @@ class CssCollectionRendererUnitTest extends UnitTestCase { 31 => $create_file_css_asset('public://css/32.css'), ], [ - 0 => $create_link_element(file_url_transform_relative(file_create_url('public://css/1.css')) . '?'), - 1 => $create_link_element(file_url_transform_relative(file_create_url('public://css/2.css')) . '?'), - 2 => $create_link_element(file_url_transform_relative(file_create_url('public://css/3.css')) . '?'), - 3 => $create_link_element(file_url_transform_relative(file_create_url('public://css/4.css')) . '?'), - 4 => $create_link_element(file_url_transform_relative(file_create_url('public://css/5.css')) . '?'), - 5 => $create_link_element(file_url_transform_relative(file_create_url('public://css/6.css')) . '?'), - 6 => $create_link_element(file_url_transform_relative(file_create_url('public://css/7.css')) . '?'), - 7 => $create_link_element(file_url_transform_relative(file_create_url('public://css/8.css')) . '?'), - 8 => $create_link_element(file_url_transform_relative(file_create_url('public://css/9.css')) . '?'), - 9 => $create_link_element(file_url_transform_relative(file_create_url('public://css/10.css')) . '?', 'screen'), - 10 => $create_link_element(file_url_transform_relative(file_create_url('public://css/11.css')) . '?'), - 11 => $create_link_element(file_url_transform_relative(file_create_url('public://css/12.css')) . '?'), - 12 => $create_link_element(file_url_transform_relative(file_create_url('public://css/13.css')) . '?'), - 13 => $create_link_element(file_url_transform_relative(file_create_url('public://css/14.css')) . '?'), - 14 => $create_link_element(file_url_transform_relative(file_create_url('public://css/15.css')) . '?'), - 15 => $create_link_element(file_url_transform_relative(file_create_url('public://css/16.css')) . '?'), - 16 => $create_link_element(file_url_transform_relative(file_create_url('public://css/17.css')) . '?'), - 17 => $create_link_element(file_url_transform_relative(file_create_url('public://css/18.css')) . '?'), - 18 => $create_link_element(file_url_transform_relative(file_create_url('public://css/19.css')) . '?'), - 19 => $create_link_element(file_url_transform_relative(file_create_url('public://css/20.css')) . '?', 'print'), - 20 => $create_link_element(file_url_transform_relative(file_create_url('public://css/21.css')) . '?'), - 21 => $create_link_element(file_url_transform_relative(file_create_url('public://css/22.css')) . '?'), - 22 => $create_link_element(file_url_transform_relative(file_create_url('public://css/23.css')) . '?'), - 23 => $create_link_element(file_url_transform_relative(file_create_url('public://css/24.css')) . '?'), - 24 => $create_link_element(file_url_transform_relative(file_create_url('public://css/25.css')) . '?'), - 25 => $create_link_element(file_url_transform_relative(file_create_url('public://css/26.css')) . '?'), - 26 => $create_link_element(file_url_transform_relative(file_create_url('public://css/27.css')) . '?'), - 27 => $create_link_element(file_url_transform_relative(file_create_url('public://css/28.css')) . '?'), - 28 => $create_link_element(file_url_transform_relative(file_create_url('public://css/29.css')) . '?'), - 29 => $create_link_element(file_url_transform_relative(file_create_url('public://css/30.css')) . '?'), - 30 => $create_link_element(file_url_transform_relative(file_create_url('public://css/31.css')) . '?'), - 31 => $create_link_element(file_url_transform_relative(file_create_url('public://css/32.css')) . '?'), + 0 => $create_link_element('generated-relative-url:public://css/1.css' . '?'), + 1 => $create_link_element('generated-relative-url:public://css/2.css' . '?'), + 2 => $create_link_element('generated-relative-url:public://css/3.css' . '?'), + 3 => $create_link_element('generated-relative-url:public://css/4.css' . '?'), + 4 => $create_link_element('generated-relative-url:public://css/5.css' . '?'), + 5 => $create_link_element('generated-relative-url:public://css/6.css' . '?'), + 6 => $create_link_element('generated-relative-url:public://css/7.css' . '?'), + 7 => $create_link_element('generated-relative-url:public://css/8.css' . '?'), + 8 => $create_link_element('generated-relative-url:public://css/9.css' . '?'), + 9 => $create_link_element('generated-relative-url:public://css/10.css' . '?', 'screen'), + 10 => $create_link_element('generated-relative-url:public://css/11.css' . '?'), + 11 => $create_link_element('generated-relative-url:public://css/12.css' . '?'), + 12 => $create_link_element('generated-relative-url:public://css/13.css' . '?'), + 13 => $create_link_element('generated-relative-url:public://css/14.css' . '?'), + 14 => $create_link_element('generated-relative-url:public://css/15.css' . '?'), + 15 => $create_link_element('generated-relative-url:public://css/16.css' . '?'), + 16 => $create_link_element('generated-relative-url:public://css/17.css' . '?'), + 17 => $create_link_element('generated-relative-url:public://css/18.css' . '?'), + 18 => $create_link_element('generated-relative-url:public://css/19.css' . '?'), + 19 => $create_link_element('generated-relative-url:public://css/20.css' . '?', 'print'), + 20 => $create_link_element('generated-relative-url:public://css/21.css' . '?'), + 21 => $create_link_element('generated-relative-url:public://css/22.css' . '?'), + 22 => $create_link_element('generated-relative-url:public://css/23.css' . '?'), + 23 => $create_link_element('generated-relative-url:public://css/24.css' . '?'), + 24 => $create_link_element('generated-relative-url:public://css/25.css' . '?'), + 25 => $create_link_element('generated-relative-url:public://css/26.css' . '?'), + 26 => $create_link_element('generated-relative-url:public://css/27.css' . '?'), + 27 => $create_link_element('generated-relative-url:public://css/28.css' . '?'), + 28 => $create_link_element('generated-relative-url:public://css/29.css' . '?'), + 29 => $create_link_element('generated-relative-url:public://css/30.css' . '?'), + 30 => $create_link_element('generated-relative-url:public://css/31.css' . '?'), + 31 => $create_link_element('generated-relative-url:public://css/32.css' . '?'), ], ], ]; @@ -294,57 +302,3 @@ class CssCollectionRendererUnitTest extends UnitTestCase { } } - -/** - * Temporary mock for file_create_url(), until that is moved into - * Component/Utility. - */ -if (!function_exists('Drupal\Tests\Core\Asset\file_create_url')) { - - function file_create_url($uri) { - return 'file_create_url:' . $uri; - } - -} - -/** - * Temporary mock of file_url_transform_relative, until that is moved into - * Component/Utility. - */ -if (!function_exists('Drupal\Tests\Core\Asset\file_url_transform_relative')) { - - function file_url_transform_relative($uri) { - return 'file_url_transform_relative:' . $uri; - } - -} - -/** - * CssCollectionRenderer uses file_create_url() & file_url_transform_relative(), - * which *are* available when using the Simpletest test runner, but not when - * using the PHPUnit test runner; hence this hack. - */ -namespace Drupal\Core\Asset; - -if (!function_exists('Drupal\Core\Asset\file_create_url')) { - - /** - * Temporary mock for file_create_url(), until that is moved into - * Component/Utility. - */ - function file_create_url($uri) { - return \Drupal\Tests\Core\Asset\file_create_url($uri); - } - -} -if (!function_exists('Drupal\Core\Asset\file_url_transform_relative')) { - - /** - * Temporary mock of file_url_transform_relative, until that is moved into - * Component/Utility. - */ - function file_url_transform_relative($uri) { - return \Drupal\Tests\Core\Asset\file_url_transform_relative($uri); - } - -} diff --git a/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php index dba9fc2ad2b..782f3cd3839 100644 --- a/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\Core\Asset; use Drupal\Core\Asset\CssOptimizer; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Tests\UnitTestCase; /** @@ -24,10 +25,23 @@ class CssOptimizerUnitTest extends UnitTestCase { */ protected $optimizer; + /** + * The file URL generator mock. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileUrlGenerator; + protected function setUp(): void { parent::setUp(); - - $this->optimizer = new CssOptimizer(); + $this->fileUrlGenerator = $this->createMock(FileUrlGeneratorInterface::class); + $this->fileUrlGenerator->expects($this->any()) + ->method('generateString') + ->with($this->isType('string')) + ->willReturnCallback(function ($uri) { + return 'generated-relative-url:' . $uri; + }); + $this->optimizer = new CssOptimizer($this->fileUrlGenerator); } /** @@ -58,7 +72,8 @@ class CssOptimizerUnitTest extends UnitTestCase { // - Proper URLs in imported files. (https://www.drupal.org/node/265719) // - A background image with relative paths, which must be rewritten. // - The rewritten background image path must also be passed through - // file_create_url(). (https://www.drupal.org/node/1961340) + // FileUrlGeneratorInterface::generate(). + // (https://www.drupal.org/node/1961340) // - Imported files that are external (protocol-relative URL or not) // should not be expanded. (https://www.drupal.org/node/2014851) [ @@ -72,7 +87,7 @@ class CssOptimizerUnitTest extends UnitTestCase { 'browsers' => ['IE' => TRUE, '!IE' => TRUE], 'basename' => 'css_input_with_import.css', ], - str_replace('url(images/icon.png)', 'url(' . file_url_transform_relative(file_create_url($path . 'images/icon.png')) . ')', file_get_contents($absolute_path . 'css_input_with_import.css.optimized.css')), + str_replace('url(images/icon.png)', 'url(generated-relative-url:' . $path . 'images/icon.png)', file_get_contents($absolute_path . 'css_input_with_import.css.optimized.css')), ], // File. Tests: // - Retain comment hacks. @@ -104,7 +119,7 @@ class CssOptimizerUnitTest extends UnitTestCase { 'browsers' => ['IE' => TRUE, '!IE' => TRUE], 'basename' => 'css_input_with_import.css', ], - str_replace('url(../images/icon.png)', 'url(' . file_url_transform_relative(file_create_url($path . 'images/icon.png')) . ')', file_get_contents($absolute_path . 'css_subfolder/css_input_with_import.css.optimized.css')), + str_replace('url(../images/icon.png)', 'url(generated-relative-url:' . $path . 'images/icon.png)', file_get_contents($absolute_path . 'css_subfolder/css_input_with_import.css.optimized.css')), ], // File. Tests: // - Any @charset declaration at the beginning of a file should be @@ -267,58 +282,10 @@ class CssOptimizerUnitTest extends UnitTestCase { } /** - * Temporary mock for file_create_url(), until that is moved into - * Component/Utility. - */ -if (!function_exists('Drupal\Tests\Core\Asset\file_create_url')) { - - function file_create_url($uri) { - return 'file_create_url:' . $uri; - } - -} - -/** - * Temporary mock of file_url_transform_relative, until that is moved into - * Component/Utility. - */ -if (!function_exists('Drupal\Tests\Core\Asset\file_url_transform_relative')) { - - function file_url_transform_relative($uri) { - return 'file_url_transform_relative:' . $uri; - } - -} - -/** - * CssCollectionRenderer uses file_create_url() & file_url_transform_relative(), - * which *are* available when using the Simpletest test runner, but not when - * using the PHPUnit test runner; hence this hack. + * CssCollectionRenderer uses file_uri_scheme() which need to be mocked. */ namespace Drupal\Core\Asset; -if (!function_exists('Drupal\Core\Asset\file_create_url')) { - - /** - * Temporary mock for file_create_url(), until that is moved into - * Component/Utility. - */ - function file_create_url($uri) { - return \Drupal\Tests\Core\Asset\file_create_url($uri); - } - -} -if (!function_exists('Drupal\Core\Asset\file_url_transform_relative')) { - - /** - * Temporary mock of file_url_transform_relative, until that is moved into - * Component/Utility. - */ - function file_url_transform_relative($uri) { - return \Drupal\Tests\Core\Asset\file_url_transform_relative($uri); - } - -} if (!function_exists('Drupal\Core\Asset\file_uri_scheme')) { function file_uri_scheme($uri) { diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php index cd367d128b7..a4c2c5b68b5 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\Core\Template; +use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\GeneratedLink; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -60,6 +61,13 @@ class TwigExtensionTest extends UnitTestCase { */ protected $systemUnderTest; + /** + * The file URL generator mock. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileUrlGenerator; + /** * {@inheritdoc} */ @@ -70,8 +78,9 @@ class TwigExtensionTest extends UnitTestCase { $this->urlGenerator = $this->createMock('\Drupal\Core\Routing\UrlGeneratorInterface'); $this->themeManager = $this->createMock('\Drupal\Core\Theme\ThemeManagerInterface'); $this->dateFormatter = $this->createMock('\Drupal\Core\Datetime\DateFormatterInterface'); + $this->fileUrlGenerator = $this->createMock(FileUrlGeneratorInterface::class); - $this->systemUnderTest = new TwigExtension($this->renderer, $this->urlGenerator, $this->themeManager, $this->dateFormatter); + $this->systemUnderTest = new TwigExtension($this->renderer, $this->urlGenerator, $this->themeManager, $this->dateFormatter, $this->fileUrlGenerator); } /** @@ -167,6 +176,22 @@ class TwigExtensionTest extends UnitTestCase { $this->assertEquals('1978-11-19', $result); } + /** + * Tests the file_url filter. + */ + public function testFileUrl() { + $this->fileUrlGenerator->expects($this->once()) + ->method('generateString') + ->with('public://picture.jpg') + ->willReturn('sites/default/files/picture.jpg'); + + $loader = new StringLoader(); + $twig = new Environment($loader); + $twig->addExtension($this->systemUnderTest); + $result = $twig->render('{{ file_url(file) }}', ['file' => 'public://picture.jpg']); + $this->assertEquals('sites/default/files/picture.jpg', $result); + } + /** * Tests the active_theme_path function. */