123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- <?php
- /*
- * This file is part of the Symfony CMF package.
- *
- * (c) 2011-2015 Symfony CMF
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Cmf\Component\Routing;
- use Doctrine\Common\Collections\Collection;
- use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
- use Symfony\Component\Routing\Route as SymfonyRoute;
- use Symfony\Component\Routing\Exception\RouteNotFoundException;
- use Symfony\Component\Routing\RouteCollection;
- /**
- * A generator that tries to generate routes from object, route names or
- * content objects or names.
- *
- * @author Philippo de Santis
- * @author David Buchmann
- * @author Uwe Jäger
- */
- class ContentAwareGenerator extends ProviderBasedGenerator
- {
- /**
- * The locale to use when neither the parameters nor the request context
- * indicate the locale to use.
- *
- * @var string
- */
- protected $defaultLocale = null;
- /**
- * The content repository used to find content by it's id
- * This can be used to specify a parameter content_id when generating urls.
- *
- * This is optional and might not be initialized.
- *
- * @var ContentRepositoryInterface
- */
- protected $contentRepository;
- /**
- * Set an optional content repository to find content by ids.
- *
- * @param ContentRepositoryInterface $contentRepository
- */
- public function setContentRepository(ContentRepositoryInterface $contentRepository)
- {
- $this->contentRepository = $contentRepository;
- }
- /**
- * {@inheritdoc}
- *
- * @param string $name ignored.
- * @param array $parameters must either contain the field 'route' with a
- * RouteObjectInterface or the field 'content_id'
- * with the id of a document implementing
- * RouteReferrersReadInterface.
- *
- * @throws RouteNotFoundException If there is no such route in the database
- */
- public function generate($name, $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
- {
- if ($name instanceof SymfonyRoute) {
- $route = $this->getBestLocaleRoute($name, $parameters);
- } elseif (is_string($name) && $name) {
- $route = $this->getRouteByName($name, $parameters);
- } else {
- $route = $this->getRouteByContent($name, $parameters);
- }
- if (!$route instanceof SymfonyRoute) {
- $hint = is_object($route) ? get_class($route) : gettype($route);
- throw new RouteNotFoundException('Route of this document is not an instance of Symfony\Component\Routing\Route but: '.$hint);
- }
- $this->unsetLocaleIfNotNeeded($route, $parameters);
- return parent::generate($route, $parameters, $absolute);
- }
- /**
- * Get the route by a string name.
- *
- * @param string $route
- * @param array $parameters
- *
- * @return SymfonyRoute
- *
- * @throws RouteNotFoundException if there is no route found for the provided name
- */
- protected function getRouteByName($name, array $parameters)
- {
- $route = $this->provider->getRouteByName($name);
- if (empty($route)) {
- throw new RouteNotFoundException('No route found for name: '.$name);
- }
- return $this->getBestLocaleRoute($route, $parameters);
- }
- /**
- * Determine if there is a route with matching locale associated with the
- * given route via associated content.
- *
- * @param SymfonyRoute $route
- * @param array $parameters
- *
- * @return SymfonyRoute either the passed route or an alternative with better locale
- */
- protected function getBestLocaleRoute(SymfonyRoute $route, $parameters)
- {
- if (!$route instanceof RouteObjectInterface) {
- // this route has no content, we can't get the alternatives
- return $route;
- }
- $locale = $this->getLocale($parameters);
- if (!$this->checkLocaleRequirement($route, $locale)) {
- $content = $route->getContent();
- if ($content instanceof RouteReferrersReadInterface) {
- $routes = $content->getRoutes();
- $contentRoute = $this->getRouteByLocale($routes, $locale);
- if ($contentRoute) {
- return $contentRoute;
- }
- }
- }
- return $route;
- }
- /**
- * Get the route based on the $name that is an object implementing
- * RouteReferrersReadInterface or a content found in the content repository
- * with the content_id specified in parameters that is an instance of
- * RouteReferrersReadInterface.
- *
- * Called in generate when there is no route given in the parameters.
- *
- * If there is more than one route for the content, tries to find the
- * first one that matches the _locale (provided in $parameters or otherwise
- * defaulting to the request locale).
- *
- * If no route with matching locale is found, falls back to just return the
- * first route.
- *
- * @param mixed $name
- * @param array $parameters which should contain a content field containing
- * a RouteReferrersReadInterface object
- *
- * @return SymfonyRoute the route instance
- *
- * @throws RouteNotFoundException if no route can be determined
- */
- protected function getRouteByContent($name, &$parameters)
- {
- if ($name instanceof RouteReferrersReadInterface) {
- $content = $name;
- } elseif (isset($parameters['content_id'])
- && null !== $this->contentRepository
- ) {
- $content = $this->contentRepository->findById($parameters['content_id']);
- if (empty($content)) {
- throw new RouteNotFoundException('The content repository found nothing at id '.$parameters['content_id']);
- }
- if (!$content instanceof RouteReferrersReadInterface) {
- throw new RouteNotFoundException('Content repository did not return a RouteReferrersReadInterface instance for id '.$parameters['content_id']);
- }
- } else {
- $hint = is_object($name) ? get_class($name) : gettype($name);
- throw new RouteNotFoundException("The route name argument '$hint' is not RouteReferrersReadInterface instance and there is no 'content_id' parameter");
- }
- $routes = $content->getRoutes();
- if (empty($routes)) {
- $hint = ($this->contentRepository && $this->contentRepository->getContentId($content))
- ? $this->contentRepository->getContentId($content)
- : get_class($content);
- throw new RouteNotFoundException('Content document has no route: '.$hint);
- }
- unset($parameters['content_id']);
- $route = $this->getRouteByLocale($routes, $this->getLocale($parameters));
- if ($route) {
- return $route;
- }
- // if none matched, randomly return the first one
- if ($routes instanceof Collection) {
- return $routes->first();
- }
- return reset($routes);
- }
- /**
- * @param RouteCollection $routes
- * @param string $locale
- *
- * @return bool|SymfonyRoute false if no route requirement matches the provided locale
- */
- protected function getRouteByLocale($routes, $locale)
- {
- foreach ($routes as $route) {
- if (!$route instanceof SymfonyRoute) {
- continue;
- }
- if ($this->checkLocaleRequirement($route, $locale)) {
- return $route;
- }
- }
- return false;
- }
- /**
- * @param SymfonyRoute $route
- * @param string $locale
- *
- * @return bool true if there is either no $locale, no _locale requirement
- * on the route or if the requirement and the passed $locale
- * match.
- */
- private function checkLocaleRequirement(SymfonyRoute $route, $locale)
- {
- return empty($locale)
- || !$route->getRequirement('_locale')
- || preg_match('/'.$route->getRequirement('_locale').'/', $locale)
- ;
- }
- /**
- * Determine the locale to be used with this request.
- *
- * @param array $parameters the parameters determined by the route
- *
- * @return string the locale following of the parameters or any other
- * information the router has available. defaultLocale if no
- * other locale can be determined.
- */
- protected function getLocale($parameters)
- {
- if (isset($parameters['_locale'])) {
- return $parameters['_locale'];
- }
- if ($this->getContext()->hasParameter('_locale')) {
- return $this->getContext()->getParameter('_locale');
- }
- return $this->defaultLocale;
- }
- /**
- * Overwrite the locale to be used by default if there is neither one in
- * the parameters when building the route nor a request available (i.e. CLI).
- *
- * @param string $locale
- */
- public function setDefaultLocale($locale)
- {
- $this->defaultLocale = $locale;
- }
- /**
- * We additionally support empty name and data in parameters and RouteAware content.
- */
- public function supports($name)
- {
- return !$name || parent::supports($name) || $name instanceof RouteReferrersReadInterface;
- }
- /**
- * {@inheritdoc}
- */
- public function getRouteDebugMessage($name, array $parameters = array())
- {
- if (empty($name) && isset($parameters['content_id'])) {
- return 'Content id '.$parameters['content_id'];
- }
- if ($name instanceof RouteReferrersReadInterface) {
- return 'Route aware content '.parent::getRouteDebugMessage($name, $parameters);
- }
- return parent::getRouteDebugMessage($name, $parameters);
- }
- /**
- * If the _locale parameter is allowed by the requirements of the route
- * and it is the default locale, remove it from the parameters so that we
- * do not get an unneeded ?_locale= query string.
- *
- * @param SymfonyRoute $route The route being generated.
- * @param array $parameters The parameters used, will be modified to
- * remove the _locale field if needed.
- */
- protected function unsetLocaleIfNotNeeded(SymfonyRoute $route, array &$parameters)
- {
- $locale = $this->getLocale($parameters);
- if (null !== $locale) {
- if (preg_match('/'.$route->getRequirement('_locale').'/', $locale)
- && $locale == $route->getDefault('_locale')
- ) {
- $compiledRoute = $route->compile();
- if (!in_array('_locale', $compiledRoute->getVariables())) {
- unset($parameters['_locale']);
- }
- }
- }
- }
- }
|