AcceptHeaderMatcher.php 2.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
<?php

/**
 * @file
 * Contains Drupal\Core\Routing\AcceptHeaderMatcher.
 */

namespace Drupal\Core\Routing;

use Drupal\Component\Utility\String;
use Drupal\Core\ContentNegotiation;
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\RouteCollection;

/**
 * Filters routes based on the media type specified in the HTTP Accept headers.
 */
class AcceptHeaderMatcher implements RouteFilterInterface {

  /**
   * The content negotiation library.
   *
   * @var \Drupal\Core\ContentNegotiation
   */
  protected $contentNegotiation;

  /**
   * Constructs a new AcceptHeaderMatcher.
   *
   * @param \Drupal\Core\ContentNegotiation $cotent_negotiation
   *   The content negotiation library.
   */
  public function __construct(ContentNegotiation $content_negotiation) {
    $this->contentNegotiation = $content_negotiation;
  }

  /**
   * {@inheritdoc}
   */
  public function filter(RouteCollection $collection, Request $request) {
    // Generates a list of Symfony formats matching the acceptable MIME types.
    // @todo replace by proper content negotiation library.
    $acceptable_mime_types = $request->getAcceptableContentTypes();
    $acceptable_formats = array_filter(array_map(array($request, 'getFormat'), $acceptable_mime_types));
    $primary_format = $this->contentNegotiation->getContentType($request);

    foreach ($collection as $name => $route) {
      // _format could be a |-delimited list of supported formats.
      $supported_formats = array_filter(explode('|', $route->getRequirement('_format')));

      if (empty($supported_formats)) {
        // No format restriction on the route, so it always matches. Move it to
        // the end of the collection by re-adding it.
        $collection->add($name, $route);
      }
      elseif (in_array($primary_format, $supported_formats)) {
        // Perfect match, which will get a higher priority by leaving the route
        // on top of the list.
      }
      // The route partially matches if it doesn't care about format, if it
      // explicitly allows any format, or if one of its allowed formats is
      // in the request's list of acceptable formats.
      elseif (in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) {
        // Move it to the end of the list.
        $collection->add($name, $route);
      }
      else {
        // Remove the route if it does not match at all.
        $collection->remove($name);
      }
    }

    if (count($collection)) {
      return $collection;
    }

    // We do not throw a
    // \Symfony\Component\Routing\Exception\ResourceNotFoundException here
    // because we don't want to return a 404 status code, but rather a 406.
    throw new NotAcceptableHttpException(String::format('No route found for the specified formats @formats.', array('@formats' => implode(' ', $acceptable_mime_types))));
  }

}