class FileUrlGenerator

Same name in other branches
  1. 9 core/lib/Drupal/Core/File/FileUrlGenerator.php \Drupal\Core\File\FileUrlGenerator
  2. 10 core/lib/Drupal/Core/File/FileUrlGenerator.php \Drupal\Core\File\FileUrlGenerator

Default implementation for the file URL generator service.

Hierarchy

Expanded class hierarchy of FileUrlGenerator

File

core/lib/Drupal/Core/File/FileUrlGenerator.php, line 16

Namespace

Drupal\Core\File
View source
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);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
FileUrlGenerator::$moduleHandler protected property The module handler.
FileUrlGenerator::$requestStack protected property The request stack.
FileUrlGenerator::$streamWrapperManager protected property The stream wrapper manager.
FileUrlGenerator::doGenerateString protected function Creates an absolute web-accessible URL string.
FileUrlGenerator::generate public function Overrides FileUrlGeneratorInterface::generate
FileUrlGenerator::generateAbsoluteString public function Overrides FileUrlGeneratorInterface::generateAbsoluteString
FileUrlGenerator::generatePath protected function Generate a URL path.
FileUrlGenerator::generateString public function Overrides FileUrlGeneratorInterface::generateString
FileUrlGenerator::transformRelative public function Overrides FileUrlGeneratorInterface::transformRelative
FileUrlGenerator::__construct public function Constructs a new file URL generator object.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.