Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / Routing / RequestFormatRouteFilter.php
1 <?php
2
3 namespace Drupal\Core\Routing;
4
5 use Drupal\Core\Url;
6 use Symfony\Component\HttpFoundation\Request;
7 use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
8 use Symfony\Component\Routing\Route;
9 use Symfony\Component\Routing\RouteCollection;
10
11 /**
12  * Provides a route filter, which filters by the request format.
13  */
14 class RequestFormatRouteFilter implements FilterInterface {
15
16   /**
17    * {@inheritdoc}
18    */
19   public function filter(RouteCollection $collection, Request $request) {
20     // Determine the request format.
21     $default_format = static::getDefaultFormat($collection);
22     // If the request does not specify a format then use the default.
23     if (is_null($request->getRequestFormat(NULL))) {
24       $format = $default_format;
25       $request->setRequestFormat($default_format);
26     }
27     else {
28       $format = $request->getRequestFormat($default_format);
29     }
30
31     $routes_with_requirement = [];
32     $routes_without_requirement = [];
33     $result_collection = new RouteCollection();
34     /** @var \Symfony\Component\Routing\Route $route */
35     foreach ($collection as $name => $route) {
36       if (!$route->hasRequirement('_format')) {
37         $routes_without_requirement[$name] = $route;
38         continue;
39       }
40       else {
41         $routes_with_requirement[$name] = $route;
42       }
43     }
44
45     foreach ($routes_with_requirement as $name => $route) {
46       // If the route has no _format specification, we move it to the end. If it
47       // does, then no match means the route is removed entirely.
48       if (($supported_formats = array_filter(explode('|', $route->getRequirement('_format')))) && in_array($format, $supported_formats, TRUE)) {
49         $result_collection->add($name, $route);
50       }
51     }
52
53     foreach ($routes_without_requirement as $name => $route) {
54       $result_collection->add($name, $route);
55     }
56
57     if (count($result_collection)) {
58       return $result_collection;
59     }
60
61     // We do not throw a
62     // \Symfony\Component\Routing\Exception\ResourceNotFoundException here
63     // because we don't want to return a 404 status code, but rather a 406.
64     $available_formats = static::getAvailableFormats($collection);
65     $not_acceptable = new NotAcceptableHttpException("No route found for the specified format $format. Supported formats: " . implode(', ', $available_formats) . '.');
66     if ($available_formats) {
67       $links = [];
68       foreach ($available_formats as $available_format) {
69         $url = Url::fromUri($request->getUri(), ['query' => ['_format' => $available_format]])->toString(TRUE)->getGeneratedUrl();
70         $content_type = $request->getMimeType($available_format);
71         $links[] = "<$url>; rel=\"alternate\"; type=\"$content_type\"";
72       }
73       $not_acceptable->setHeaders(['Link' => implode(', ', $links)]);
74     }
75     throw $not_acceptable;
76   }
77
78   /**
79    * Determines the default request format.
80    *
81    * By default, use 'html' as the default format. But when there's only a
82    * single route match, and that route specifies a '_format' requirement
83    * listing a single format, then use that as the default format. Also, if
84    * there are multiple routes which all require the same single format then
85    * use it.
86    *
87    * @param \Symfony\Component\Routing\RouteCollection $collection
88    *   The route collection to filter.
89    *
90    * @return string
91    *   The default format.
92    */
93   protected static function getDefaultFormat(RouteCollection $collection) {
94     $formats = static::getAvailableFormats($collection);
95
96     // The default format is 'html' unless ALL routes require the same format.
97     return count($formats) === 1
98       ? reset($formats)
99       : 'html';
100   }
101
102   /**
103    * Gets the set of formats across all routes in the collection.
104    *
105    * @param \Symfony\Component\Routing\RouteCollection $collection
106    *   The route collection to filter.
107    *
108    * @return string[]
109    *   All available formats.
110    */
111   protected static function getAvailableFormats(RouteCollection $collection) {
112     $all_formats = array_reduce($collection->all(), function (array $carry, Route $route) {
113       // Routes without a '_format' requirement are assumed to require HTML.
114       $route_formats = !$route->hasRequirement('_format')
115         ? ['html']
116         : explode('|', $route->getRequirement('_format'));
117       return array_merge($carry, $route_formats);
118     }, []);
119     return array_unique(array_filter($all_formats));
120   }
121
122 }