src/Frontend/Core/Engine/Url.php line 151

Open in your IDE?
  1. <?php
  2. namespace Frontend\Core\Engine;
  3. use Common\Exception\RedirectException;
  4. use ForkCMS\App\KernelLoader;
  5. use Frontend\Core\Language\Language;
  6. use SpoonFilter;
  7. use Symfony\Component\HttpFoundation\RedirectResponse;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Symfony\Component\HttpKernel\KernelInterface;
  10. /**
  11.  * This class will handle the incoming URL.
  12.  */
  13. class Url extends KernelLoader
  14. {
  15.     /**
  16.      * The pages
  17.      *
  18.      * @var array
  19.      */
  20.     private $pages = [];
  21.     /**
  22.      * The parameters
  23.      *
  24.      * @var array
  25.      */
  26.     private $parameters = [];
  27.     public function __construct(KernelInterface $kernel)
  28.     {
  29.         parent::__construct($kernel);
  30.         // add ourself to the reference so other classes can retrieve us
  31.         $this->getContainer()->set('url'$this);
  32.         // if there is a trailing slash we permanent redirect to the page without slash
  33.         if (mb_strlen(Model::getRequest()->getRequestUri()) !== &&
  34.             mb_substr(Model::getRequest()->getRequestUri(), -1) === '/'
  35.         ) {
  36.             throw new RedirectException(
  37.                 'Redirect',
  38.                 new RedirectResponse(
  39.                     mb_substr(Model::getRequest()->getRequestUri(), 0, -1),
  40.                     Response::HTTP_FOUND
  41.                 )
  42.             );
  43.         }
  44.         // set query-string and parameters for later use
  45.         $this->parameters Model::getRequest()->query->all();
  46.         // process URL
  47.         $this->processQueryString();
  48.     }
  49.     /**
  50.      * Get the domain
  51.      *
  52.      * @return string The current domain (without www.)
  53.      */
  54.     public function getDomain(): string
  55.     {
  56.         // replace
  57.         return str_replace('www.'''Model::getRequest()->getHttpHost());
  58.     }
  59.     /**
  60.      * Get a page specified by the given index
  61.      *
  62.      * @param int $index The index (0-based).
  63.      *
  64.      * @return string|null
  65.      */
  66.     public function getPage(int $index): ?string
  67.     {
  68.         return $this->pages[$index] ?? null;
  69.     }
  70.     public function getPages(): array
  71.     {
  72.         return $this->pages;
  73.     }
  74.     /**
  75.      * Get a parameter specified by the given index
  76.      * The function will return null if the key is not available
  77.      * By default we will cast the return value into a string, if you want
  78.      * something else specify it by passing the wanted type.
  79.      *
  80.      * @param mixed $index The index of the parameter.
  81.      * @param string $type The return type, possible values are:
  82.      *                             bool, boolean, int, integer, float, double, string, array.
  83.      * @param mixed $defaultValue The value that should be returned if the key is not available.
  84.      *
  85.      * @return mixed
  86.      */
  87.     public function getParameter($indexstring $type 'string'$defaultValue null)
  88.     {
  89.         // does the index exists and isn't this parameter empty
  90.         if ($this->hasParameter($index)) {
  91.             return SpoonFilter::getValue(
  92.                 $this->parameters[$index],
  93.                 null,
  94.                 null,
  95.                 $type
  96.             );
  97.         }
  98.         // fallback
  99.         return $defaultValue;
  100.     }
  101.     /**
  102.      * Return all the parameters
  103.      *
  104.      * @param bool $includeGet Should the GET-parameters be included?
  105.      *
  106.      * @return array
  107.      */
  108.     public function getParameters(bool $includeGet true): array
  109.     {
  110.         return $includeGet $this->parameters array_diff_assoc($this->parametersModel::getRequest()->query->all());
  111.     }
  112.     public function getQueryString(): string
  113.     {
  114.         return rtrim(Model::getRequest()->getRequestUri(), '/');
  115.     }
  116.     /**
  117.      * Check if a certain ($_GET) parameter exists
  118.      *
  119.      * @param mixed $index The index of the parameter.
  120.      *
  121.      * @return bool
  122.      */
  123.     public function hasParameter($index): bool
  124.     {
  125.         return isset($this->parameters[$index]) && $this->parameters[$index] !== '';
  126.     }
  127.     private function processQueryString(): void
  128.     {
  129.         // store the query string local, so we don't alter it.
  130.         $queryString trim(Model::getRequest()->getPathInfo(), '/');
  131.         /* @var $pageAliasUrl PageUrlAliasInterface */
  132.         $pageAliasUrl $this->get('fork.frontend.page_url_alias');
  133.         $hasMultiLanguages $this->getContainer()->getParameter('site.multilanguage');
  134.         $language $this->determineLanguage($queryString);
  135.         // define the language
  136.         defined('FRONTEND_LANGUAGE') || define('FRONTEND_LANGUAGE'$language);
  137.         defined('LANGUAGE') || define('LANGUAGE'$language);
  138.         // sets the locale file
  139.         Language::setLocale($language);
  140.         // remove language from query string
  141.         if ($hasMultiLanguages) {
  142.             $queryString trim(mb_substr($queryStringmb_strlen($language)), '/');
  143.         }
  144.         $aliases $pageAliasUrl->getAliases($language);
  145.         $url $this->determineUrl($queryString$language$aliases);
  146.         // currently not in the homepage
  147.         if ($url !== '') {
  148.             $this->setPages(explode('/'$url));
  149.         }
  150.         $parameters $this->extractParametersFromQueryString($queryString$url);
  151.         $pageId $pageAliasUrl->getPageId(implode('/'$this->getPages()), $language);
  152.         $pageInfo Navigation::getPageInfo($pageId);
  153.         // invalid page, or parameters but no extra
  154.         if ($pageInfo === false || (!empty($parameters) && !$pageInfo['has_extra'])) {
  155.             // get 404 URL
  156.             $url Navigation::getUrl(Model::ERROR_PAGE_ID);
  157.             // remove language
  158.             if ($hasMultiLanguages) {
  159.                 $url str_replace('/' $language''$url);
  160.             }
  161.             // remove the first slash
  162.             $url trim($url'/');
  163.             // currently not in the homepage
  164.             if ($url !== '') {
  165.                 $this->setPages(explode('/'$url));
  166.             }
  167.         }
  168.         if ($pageInfo !== false) {
  169.             $this->handleRedirects($pageInfo);
  170.         }
  171.     }
  172.     private function extractParametersFromQueryString(string $queryStringstring $url): array
  173.     {
  174.         // set parameters
  175.         $parameters trim(mb_substr($queryStringmb_strlen($url)), '/');
  176.         if (empty($parameters)) {
  177.             return [];
  178.         }
  179.         // parameters will be separated by /
  180.         $parameters explode('/'$parameters);
  181.         // set parameters
  182.         $this->setParameters($parameters);
  183.         return $parameters;
  184.     }
  185.     private function determineLanguage(string $queryString): string
  186.     {
  187.         if (!$this->getContainer()->getParameter('site.multilanguage')) {
  188.             return $this->get('fork.settings')->get('Core''default_language'SITE_DEFAULT_LANGUAGE);
  189.         }
  190.         // get possible languages
  191.         $possibleLanguages = (array) Language::getActiveLanguages();
  192.         $redirectLanguages = (array) Language::getRedirectLanguages();
  193.         // split into chunks
  194.         $chunks = (array) explode('/'$queryString);
  195.         // the language is present in the URL
  196.         if (isset($chunks[0]) && in_array($chunks[0], $possibleLanguages)) {
  197.             // define language
  198.             $language = (string) $chunks[0];
  199.             $this->setLanguageCookie($language);
  200.             Model::getSession()->set('frontend_language'$language);
  201.             return $language;
  202.         }
  203.         $cookie $this->getContainer()->get('fork.cookie');
  204.         if ($cookie->has('frontend_language')
  205.             && in_array($cookie->get('frontend_language'), $redirectLanguagestrue)
  206.         ) {
  207.             $this->redirectToLanguage($cookie->get('frontend_language'));
  208.         }
  209.         // default browser language
  210.         $language Language::getBrowserLanguage();
  211.         $this->setLanguageCookie($language);
  212.         $this->redirectToLanguage($language);
  213.     }
  214.     private function setLanguageCookie(string $language): void
  215.     {
  216.         try {
  217.             self::getContainer()->get('fork.cookie')->set('frontend_language'$language);
  218.         } catch (\RuntimeException $e) {
  219.             // settings cookies isn't allowed, because this isn't a real problem we ignore the exception
  220.         }
  221.     }
  222.     private function redirectToLanguage(string $language): void
  223.     {
  224.         // trim the first / from the query string to prevent double slashes
  225.         $url rtrim('/' $language '/' trim($this->getQueryString(), '/'), '/');
  226.         // when we are just adding the language to the domain, it's a temporary redirect because
  227.         // Safari keeps the 301 in cache, so the cookie to switch language doesn't work any more
  228.         $redirectCode = ($url === '/' $language) ? Response::HTTP_FOUND Response::HTTP_MOVED_PERMANENTLY;
  229.         // set header & redirect
  230.         throw new RedirectException(
  231.             'Redirect',
  232.             new RedirectResponse($url$redirectCode)
  233.         );
  234.     }
  235.     private function handleRedirects(array $pageInfo): void
  236.     {
  237.         // is this an internal redirect?
  238.         if (isset($pageInfo['redirect_page_id']) && $pageInfo['redirect_page_id'] !== '') {
  239.             // get url for item
  240.             $newPageUrl Navigation::getUrl((int) $pageInfo['redirect_page_id']);
  241.             $errorURL Navigation::getUrl(Model::ERROR_PAGE_ID);
  242.             // not an error?
  243.             if ($newPageUrl !== $errorURL) {
  244.                 // redirect
  245.                 throw new RedirectException(
  246.                     'Redirect',
  247.                     new RedirectResponse(
  248.                         $newPageUrl,
  249.                         $pageInfo['redirect_code']
  250.                     )
  251.                 );
  252.             }
  253.         }
  254.         // is this an external redirect?
  255.         if (isset($pageInfo['redirect_url']) && $pageInfo['redirect_url'] !== '') {
  256.             // redirect
  257.             throw new RedirectException(
  258.                 'Redirect',
  259.                 new RedirectResponse(
  260.                     $pageInfo['redirect_url'],
  261.                     $pageInfo['redirect_code']
  262.                 )
  263.             );
  264.         }
  265.     }
  266.     private function setPages(array $pages = []): void
  267.     {
  268.         $this->pages $pages;
  269.     }
  270.     private function setParameters(array $parameters = []): void
  271.     {
  272.         foreach ($parameters as $key => $value) {
  273.             $this->parameters[$key] = $value;
  274.         }
  275.     }
  276.     private function determineUrl(string $queryStringstring $language, array $aliases): string
  277.     {
  278.         // list of pageIds & their full URL
  279.         $keys Navigation::getKeys();
  280.         $chunks = (array) explode('/'$queryString);
  281.         // rebuild our URL, but without the language parameter. (it's tripped earlier)
  282.         $url implode('/'$chunks);
  283.         // loop until we find the URL in the list of pages
  284.         while (!in_array($url$keys) && !array_key_exists($url$aliases)) {
  285.             // remove the last chunk
  286.             array_pop($chunks);
  287.             // redefine the URL
  288.             $url implode('/'$chunks);
  289.         }
  290.         // if it's the homepage AND parameters were given (not allowed!)
  291.         if ($url === '' && $queryString !== '') {
  292.             // get 404 URL
  293.             $url Navigation::getUrl(Model::ERROR_PAGE_ID);
  294.             // remove language
  295.             if ($this->getContainer()->getParameter('site.multilanguage')) {
  296.                 $url str_replace('/' $language''$url);
  297.             }
  298.         }
  299.         // set pages
  300.         $url trim($url'/');
  301.         return $url;
  302.     }
  303. }