Chris@0: contentRepository = $contentRepository; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @param string $name ignored. Chris@0: * @param array $parameters must either contain the field 'route' with a Chris@0: * RouteObjectInterface or the field 'content_id' Chris@0: * with the id of a document implementing Chris@0: * RouteReferrersReadInterface. Chris@0: * Chris@0: * @throws RouteNotFoundException If there is no such route in the database Chris@0: */ Chris@0: public function generate($name, $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH) Chris@0: { Chris@0: if ($name instanceof SymfonyRoute) { Chris@0: $route = $this->getBestLocaleRoute($name, $parameters); Chris@0: } elseif (is_string($name) && $name) { Chris@0: $route = $this->getRouteByName($name, $parameters); Chris@0: } else { Chris@0: $route = $this->getRouteByContent($name, $parameters); Chris@0: } Chris@0: Chris@0: if (!$route instanceof SymfonyRoute) { Chris@0: $hint = is_object($route) ? get_class($route) : gettype($route); Chris@0: throw new RouteNotFoundException('Route of this document is not an instance of Symfony\Component\Routing\Route but: '.$hint); Chris@0: } Chris@0: Chris@0: $this->unsetLocaleIfNotNeeded($route, $parameters); Chris@0: Chris@0: return parent::generate($route, $parameters, $absolute); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the route by a string name. Chris@0: * Chris@0: * @param string $route Chris@0: * @param array $parameters Chris@0: * Chris@0: * @return SymfonyRoute Chris@0: * Chris@0: * @throws RouteNotFoundException if there is no route found for the provided name Chris@0: */ Chris@0: protected function getRouteByName($name, array $parameters) Chris@0: { Chris@0: $route = $this->provider->getRouteByName($name); Chris@0: if (empty($route)) { Chris@0: throw new RouteNotFoundException('No route found for name: '.$name); Chris@0: } Chris@0: Chris@0: return $this->getBestLocaleRoute($route, $parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determine if there is a route with matching locale associated with the Chris@0: * given route via associated content. Chris@0: * Chris@0: * @param SymfonyRoute $route Chris@0: * @param array $parameters Chris@0: * Chris@0: * @return SymfonyRoute either the passed route or an alternative with better locale Chris@0: */ Chris@0: protected function getBestLocaleRoute(SymfonyRoute $route, $parameters) Chris@0: { Chris@0: if (!$route instanceof RouteObjectInterface) { Chris@0: // this route has no content, we can't get the alternatives Chris@0: return $route; Chris@0: } Chris@0: $locale = $this->getLocale($parameters); Chris@0: if (!$this->checkLocaleRequirement($route, $locale)) { Chris@0: $content = $route->getContent(); Chris@0: if ($content instanceof RouteReferrersReadInterface) { Chris@0: $routes = $content->getRoutes(); Chris@0: $contentRoute = $this->getRouteByLocale($routes, $locale); Chris@0: if ($contentRoute) { Chris@0: return $contentRoute; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $route; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get the route based on the $name that is an object implementing Chris@0: * RouteReferrersReadInterface or a content found in the content repository Chris@0: * with the content_id specified in parameters that is an instance of Chris@0: * RouteReferrersReadInterface. Chris@0: * Chris@0: * Called in generate when there is no route given in the parameters. Chris@0: * Chris@0: * If there is more than one route for the content, tries to find the Chris@0: * first one that matches the _locale (provided in $parameters or otherwise Chris@0: * defaulting to the request locale). Chris@0: * Chris@0: * If no route with matching locale is found, falls back to just return the Chris@0: * first route. Chris@0: * Chris@0: * @param mixed $name Chris@0: * @param array $parameters which should contain a content field containing Chris@0: * a RouteReferrersReadInterface object Chris@0: * Chris@0: * @return SymfonyRoute the route instance Chris@0: * Chris@0: * @throws RouteNotFoundException if no route can be determined Chris@0: */ Chris@0: protected function getRouteByContent($name, &$parameters) Chris@0: { Chris@0: if ($name instanceof RouteReferrersReadInterface) { Chris@0: $content = $name; Chris@0: } elseif (isset($parameters['content_id']) Chris@0: && null !== $this->contentRepository Chris@0: ) { Chris@0: $content = $this->contentRepository->findById($parameters['content_id']); Chris@0: if (empty($content)) { Chris@0: throw new RouteNotFoundException('The content repository found nothing at id '.$parameters['content_id']); Chris@0: } Chris@0: if (!$content instanceof RouteReferrersReadInterface) { Chris@0: throw new RouteNotFoundException('Content repository did not return a RouteReferrersReadInterface instance for id '.$parameters['content_id']); Chris@0: } Chris@0: } else { Chris@0: $hint = is_object($name) ? get_class($name) : gettype($name); Chris@0: throw new RouteNotFoundException("The route name argument '$hint' is not RouteReferrersReadInterface instance and there is no 'content_id' parameter"); Chris@0: } Chris@0: Chris@0: $routes = $content->getRoutes(); Chris@0: if (empty($routes)) { Chris@0: $hint = ($this->contentRepository && $this->contentRepository->getContentId($content)) Chris@0: ? $this->contentRepository->getContentId($content) Chris@0: : get_class($content); Chris@0: throw new RouteNotFoundException('Content document has no route: '.$hint); Chris@0: } Chris@0: Chris@0: unset($parameters['content_id']); Chris@0: Chris@0: $route = $this->getRouteByLocale($routes, $this->getLocale($parameters)); Chris@0: if ($route) { Chris@0: return $route; Chris@0: } Chris@0: Chris@0: // if none matched, randomly return the first one Chris@0: if ($routes instanceof Collection) { Chris@0: return $routes->first(); Chris@0: } Chris@0: Chris@0: return reset($routes); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param RouteCollection $routes Chris@0: * @param string $locale Chris@0: * Chris@0: * @return bool|SymfonyRoute false if no route requirement matches the provided locale Chris@0: */ Chris@0: protected function getRouteByLocale($routes, $locale) Chris@0: { Chris@0: foreach ($routes as $route) { Chris@0: if (!$route instanceof SymfonyRoute) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($this->checkLocaleRequirement($route, $locale)) { Chris@0: return $route; Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param SymfonyRoute $route Chris@0: * @param string $locale Chris@0: * Chris@0: * @return bool true if there is either no $locale, no _locale requirement Chris@0: * on the route or if the requirement and the passed $locale Chris@0: * match. Chris@0: */ Chris@0: private function checkLocaleRequirement(SymfonyRoute $route, $locale) Chris@0: { Chris@0: return empty($locale) Chris@0: || !$route->getRequirement('_locale') Chris@0: || preg_match('/'.$route->getRequirement('_locale').'/', $locale) Chris@0: ; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determine the locale to be used with this request. Chris@0: * Chris@0: * @param array $parameters the parameters determined by the route Chris@0: * Chris@0: * @return string the locale following of the parameters or any other Chris@0: * information the router has available. defaultLocale if no Chris@0: * other locale can be determined. Chris@0: */ Chris@0: protected function getLocale($parameters) Chris@0: { Chris@0: if (isset($parameters['_locale'])) { Chris@0: return $parameters['_locale']; Chris@0: } Chris@0: Chris@0: if ($this->getContext()->hasParameter('_locale')) { Chris@0: return $this->getContext()->getParameter('_locale'); Chris@0: } Chris@0: Chris@0: return $this->defaultLocale; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Overwrite the locale to be used by default if there is neither one in Chris@0: * the parameters when building the route nor a request available (i.e. CLI). Chris@0: * Chris@0: * @param string $locale Chris@0: */ Chris@0: public function setDefaultLocale($locale) Chris@0: { Chris@0: $this->defaultLocale = $locale; Chris@0: } Chris@0: Chris@0: /** Chris@0: * We additionally support empty name and data in parameters and RouteAware content. Chris@0: */ Chris@0: public function supports($name) Chris@0: { Chris@0: return !$name || parent::supports($name) || $name instanceof RouteReferrersReadInterface; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getRouteDebugMessage($name, array $parameters = array()) Chris@0: { Chris@0: if (empty($name) && isset($parameters['content_id'])) { Chris@0: return 'Content id '.$parameters['content_id']; Chris@0: } Chris@0: Chris@0: if ($name instanceof RouteReferrersReadInterface) { Chris@0: return 'Route aware content '.parent::getRouteDebugMessage($name, $parameters); Chris@0: } Chris@0: Chris@0: return parent::getRouteDebugMessage($name, $parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * If the _locale parameter is allowed by the requirements of the route Chris@0: * and it is the default locale, remove it from the parameters so that we Chris@0: * do not get an unneeded ?_locale= query string. Chris@0: * Chris@0: * @param SymfonyRoute $route The route being generated. Chris@0: * @param array $parameters The parameters used, will be modified to Chris@0: * remove the _locale field if needed. Chris@0: */ Chris@0: protected function unsetLocaleIfNotNeeded(SymfonyRoute $route, array &$parameters) Chris@0: { Chris@0: $locale = $this->getLocale($parameters); Chris@0: if (null !== $locale) { Chris@0: if (preg_match('/'.$route->getRequirement('_locale').'/', $locale) Chris@0: && $locale == $route->getDefault('_locale') Chris@0: ) { Chris@0: $compiledRoute = $route->compile(); Chris@0: if (!in_array('_locale', $compiledRoute->getVariables())) { Chris@0: unset($parameters['_locale']); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: }