FileUrlGenerator.php
Same filename in other branches
Namespace
Drupal\Core\FileFile
-
core/
lib/ Drupal/ Core/ File/ FileUrlGenerator.php
View source
<?php
namespace Drupal\Core\File;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\Exception\InvalidStreamWrapperException;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Default implementation for the file URL generator service.
*/
class FileUrlGenerator implements FileUrlGeneratorInterface {
/**
* The stream wrapper manager.
*
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
*/
protected $streamWrapperManager;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new file URL generator object.
*
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
* The stream wrapper manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
$this->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)) {
$external_url = $wrapper->getExternalUrl();
$options = UrlHelper::parse($external_url);
// @todo Switch to dependency injected request_context service after
// https://www.drupal.org/project/drupal/issues/3256884 is fixed.
if (UrlHelper::externalIsLocal($external_url, \Drupal::service('router.request_context')->getCompleteBaseUrl())) {
// Attempt to return an external URL using the appropriate wrapper.
return Url::fromUri('base:' . $this->transformRelative(urldecode($options['path']), FALSE), $options);
}
else {
return Url::fromUri(urldecode($options['path']), $options);
}
}
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();
$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 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.
$base_path = !$root_relative ? $request->getBasePath() : '';
$host = preg_quote($host, '@');
$port = preg_quote($port, '@');
$base_path = preg_quote($base_path, '@');
return preg_replace("@^https?://{$host}(:{$port})?{$base_path}(\$|/)@", '/', $file_url);
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
FileUrlGenerator | Default implementation for the file URL generator service. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.