UrlGenerator.php 13.3 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
Crell's avatar
Crell committed
5
 * Contains Drupal\Core\Routing\UrlGenerator.
6 7 8 9
 */

namespace Drupal\Core\Routing;

10
use Symfony\Component\HttpFoundation\Request;
11 12
use Symfony\Component\HttpKernel\Log\LoggerInterface;

13 14 15
use Symfony\Component\Routing\Route as SymfonyRoute;
use Symfony\Component\Routing\Exception\RouteNotFoundException;

16 17
use Symfony\Cmf\Component\Routing\ProviderBasedGenerator;

18
use Drupal\Component\Utility\Settings;
19
use Drupal\Component\Utility\Url;
20 21
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
22 23

/**
24
 * A Generator creates URL strings based on a specified route.
25
 */
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
class UrlGenerator extends ProviderBasedGenerator implements PathBasedGeneratorInterface {

  /**
   * A request object.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $request;

  /**
   * The path processor to convert the system path to one suitable for urls.
   *
   * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface
   */
  protected $pathProcessor;

  /**
   * The base path to use for urls.
   *
   * @var string
   */
  protected $basePath;

  /**
   * The base url to use for urls.
   *
   * @var string
   */
  protected $baseUrl;

  /**
   * The script path to use for urls.
   *
   * @var string
   */
  protected $scriptPath;
62 63

  /**
64
   * Whether both secure and insecure session cookies can be used simultaneously.
65
   *
66
   * @var bool
67
   */
68
  protected $mixedModeSessions;
69

70 71 72
  /**
   *  Constructs a new generator object.
   *
73
   * @param \Drupal\Core\Routing\RouteProviderInterface $provider
74 75 76 77 78 79
   *   The route provider to be searched for routes.
   * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
   *   The alias manager responsible for path aliasing.
   * @param \Symfony\Component\HttpKernel\Log\LoggerInterface $logger
   *   An optional logger for recording errors.
   */
80
  public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) {
81 82
    parent::__construct($provider, $logger);

83 84 85
    $this->pathProcessor = $path_processor;
    $this->mixedModeSessions = $settings->get('mixed_mode_sessions', FALSE);
    $allowed_protocols = $config->get('system.filter')->get('protocols') ?: array('http', 'https');
86
    Url::setAllowedProtocols($allowed_protocols);
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
  }

  /**
   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setRequest().
   */
  public function setRequest(Request $request) {
    $this->request = $request;
    // Set some properties, based on the request, that are used during path-based
    // url generation.
    $this->basePath = $request->getBasePath() . '/';
    $this->baseUrl = $request->getSchemeAndHttpHost() . $this->basePath;
    $this->scriptPath = '';
    $base_path_with_script = $request->getBaseUrl();
    $script_name = $request->getScriptName();
    if (!empty($base_path_with_script) && strpos($base_path_with_script, $script_name) !== FALSE) {
      $length = strlen($this->basePath);
      $this->scriptPath = ltrim(substr($script_name, $length), '/') . '/';
    }
105 106
  }

107
  /**
108
   * Implements Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate().
109
   */
110 111
  public function generate($name, $parameters = array(), $absolute = FALSE) {

112 113 114
    if ($name instanceof SymfonyRoute) {
      $route = $name;
    }
115
    elseif (NULL === $route = $this->provider->getRouteByName($name, $parameters)) {
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
      throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
    }

    // The Route has a cache of its own and is not recompiled as long as it does
    // not get modified.
    $compiledRoute = $route->compile();
    $hostTokens = $compiledRoute->getHostTokens();

    $route_requirements = $route->getRequirements();
    // We need to bypass the doGenerate() method's handling of absolute URLs as
    // we handle that ourselves after processing the path.
    if (isset($route_requirements['_scheme'])) {
      $scheme_req = $route_requirements['_scheme'];
      unset($route_requirements['_scheme']);
    }
    $path = $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route_requirements, $compiledRoute->getTokens(), $parameters, $name, FALSE, $hostTokens);

    // The URL returned from doGenerate() will include the base path if there is
    // one (i.e., if running in a subdirectory) so we need to strip that off
    // before processing the path.
    $base_url = $this->context->getBaseUrl();
    if (!empty($base_url) && strpos($path, $base_url) === 0) {
      $path = substr($path, strlen($base_url));
    }

    $path = $this->processPath($path);
    if (!$absolute || !$host = $this->context->getHost()) {
      return $base_url . $path;
    }

    // Prepare an absolute URL by getting the correct scheme, host and port from
    // the request context.
    $scheme = $this->context->getScheme();
    if (isset($scheme_req) && ($req = strtolower($scheme_req)) && $scheme !== $req) {
      $scheme = $req;
    }
    $port = '';
    if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
      $port = ':' . $this->context->getHttpPort();
    } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
      $port = ':' . $this->context->getHttpsPort();
    }
    return $scheme . '://' . $host . $port . $base_url . $path;
  }

  /**
   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::generateFromPath().
   *
   * @param $path
   *   (optional) The internal path or external URL being linked to, such as
   *   "node/34" or "http://example.com/foo". The default value is equivalent to
   *   passing in '<front>'. A few notes:
   *   - If you provide a full URL, it will be considered an external URL.
   *   - If you provide only the path (e.g. "node/34"), it will be
   *     considered an internal link. In this case, it should be a system URL,
   *     and it will be replaced with the alias, if one exists. Additional query
   *     arguments for internal paths must be supplied in $options['query'], not
   *     included in $path.
   *   - If you provide an internal path and $options['alias'] is set to TRUE, the
   *     path is assumed already to be the correct path alias, and the alias is
   *     not looked up.
   *   - The special string '<front>' generates a link to the site's base URL.
   *   - If your external URL contains a query (e.g. http://example.com/foo?a=b),
   *     then you can either URL encode the query keys and values yourself and
   *     include them in $path, or use $options['query'] to let this method
   *     URL encode them.
   *
   * @param $options
   *   (optional) An associative array of additional options, with the following
   *   elements:
   *   - 'query': An array of query key/value-pairs (without any URL-encoding) to
   *     append to the URL.
   *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
   *     Do not include the leading '#' character.
   *   - 'absolute': Defaults to FALSE. Whether to force the output to be an
   *     absolute link (beginning with http:). Useful for links that will be
   *     displayed outside the site, such as in an RSS feed.
   *   - 'alias': Defaults to FALSE. Whether the given path is a URL alias
   *     already.
   *   - 'external': Whether the given path is an external URL.
   *   - 'language': An optional language object. If the path being linked to is
   *     internal to the site, $options['language'] is used to look up the alias
   *     for the URL. If $options['language'] is omitted, the language will be
   *     obtained from language(Language::TYPE_URL).
   *   - 'https': Whether this URL should point to a secure location. If not
   *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
   *     respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
   *     only be enforced when the variable 'https' is set to TRUE.
   *   - 'base_url': Only used internally, to modify the base URL when a language
   *     dependent URL requires so.
   *   - 'prefix': Only used internally, to modify the path when a language
   *     dependent URL requires so.
   *   - 'script': Added to the URL between the base path and the path prefix.
   *     Defaults to empty string when clean URLs are in effect, and to
   *     'index.php/' when they are not.
   *   - 'entity_type': The entity type of the object that called url(). Only
   *     set if url() is invoked by Drupal\Core\Entity\Entity::uri().
   *   - 'entity': The entity object (such as a node) for which the URL is being
   *     generated. Only set if url() is invoked by Drupal\Core\Entity\Entity::uri().
   *
   * @return
   *   A string containing a URL to the given path.
   *
   * @throws \Drupal\Core\Routing\GeneratorNotInitializedException.
   */
  public function generateFromPath($path = NULL, $options = array()) {

    if (!$this->initialized()) {
      throw new GeneratorNotInitializedException();
    }

    // Merge in defaults.
    $options += array(
      'fragment' => '',
      'query' => array(),
      'absolute' => FALSE,
      'prefix' => '',
    );

    if (!isset($options['external'])) {
      // Return an external link if $path contains an allowed absolute URL. Only
      // call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
      // before any / ? or #. Note: we could use url_is_external($path) here, but
      // that would require another function call, and performance inside url() is
      // critical.
      $colonpos = strpos($path, ':');
242
      $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && Url::stripDangerousProtocols($path) == $path);
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    }

    if (isset($options['fragment']) && $options['fragment'] !== '') {
      $options['fragment'] = '#' . $options['fragment'];
    }

    if ($options['external']) {
      // Split off the fragment.
      if (strpos($path, '#') !== FALSE) {
        list($path, $old_fragment) = explode('#', $path, 2);
        // If $options contains no fragment, take it over from the path.
        if (isset($old_fragment) && !$options['fragment']) {
          $options['fragment'] = '#' . $old_fragment;
        }
      }
      // Append the query.
      if ($options['query']) {
260
        $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . Url::buildQuery($options['query']);
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
      }
      if (isset($options['https']) && $this->mixedModeSessions) {
        if ($options['https'] === TRUE) {
          $path = str_replace('http://', 'https://', $path);
        }
        elseif ($options['https'] === FALSE) {
          $path = str_replace('https://', 'http://', $path);
        }
      }
      // Reassemble.
      return $path . $options['fragment'];
    }
    else {
      $path = ltrim($this->processPath($path, $options), '/');
    }

    if (!isset($options['script'])) {
      $options['script'] = $this->scriptPath;
    }
    // The base_url might be rewritten from the language rewrite in domain mode.
    if (!isset($options['base_url'])) {
      if (isset($options['https']) && $this->mixedModeSessions) {
        if ($options['https'] === TRUE) {
          $options['base_url'] = str_replace('http://', 'https://', $this->baseUrl);
          $options['absolute'] = TRUE;
        }
        elseif ($options['https'] === FALSE) {
          $options['base_url'] = str_replace('https://', 'http://', $this->baseUrl);
          $options['absolute'] = TRUE;
        }
      }
      else {
        $options['base_url'] = $this->baseUrl;
      }
    }
    elseif (rtrim($options['base_url'], '/') == $options['base_url']) {
      $options['base_url'] .= '/';
    }
    $base = $options['absolute'] ? $options['base_url'] : $this->basePath;
    $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];

    $path = str_replace('%2F', '/', rawurlencode($prefix . $path));
303
    $query = $options['query'] ? ('?' . Url::buildQuery($options['query'])) : '';
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    return $base . $options['script'] . $path . $query . $options['fragment'];
  }

  /**
   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setBaseUrl().
   */
  public function setBaseUrl($url) {
    $this->baseUrl = $url;
  }

  /**
   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setBasePath().
   */
  public function setBasePath($path) {
    $this->basePath = $path;
  }

  /**
   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setScriptPath().
   */
  public function setScriptPath($path) {
    $this->scriptPath = $path;
  }

  /**
   * Passes the path to a processor manager to allow alterations.
   */
  protected function processPath($path, &$options = array()) {
    // Router-based paths may have a querystring on them.
    if ($query_pos = strpos($path, '?')) {
      // We don't need to do a strict check here because position 0 would mean we
      // have no actual path to work with.
      $actual_path = substr($path, 0, $query_pos);
      $query_string = substr($path, $query_pos);
    }
    else {
      $actual_path = $path;
      $query_string = '';
    }
    $path = '/' . $this->pathProcessor->processOutbound(trim($actual_path, '/'), $options, $this->request);
    $path .= $query_string;
345 346 347
    return $path;
  }

348 349 350 351 352 353 354 355 356 357 358
  /**
   * Returns whether or not the url generator has been initialized.
   *
   * @return bool
   *   Returns TRUE if the basePath, baseUrl and scriptPath properties have been
   *   set, FALSE otherwise.
   */
  protected function initialized() {
    return isset($this->basePath) && isset($this->baseUrl) && isset($this->scriptPath);
  }

359
}