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(' 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 .= '