vendor/contao/core-bundle/src/Resources/contao/classes/Frontend.php line 301

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\CoreBundle\Exception\LegacyRoutingException;
  11. use Contao\CoreBundle\Exception\NoRootPageFoundException;
  12. use Contao\CoreBundle\Search\Document;
  13. use Contao\CoreBundle\Util\LocaleUtil;
  14. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
  18. use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
  19. /**
  20.  * Provide methods to manage front end controllers.
  21.  *
  22.  * @author Leo Feyer <https://github.com/leofeyer>
  23.  */
  24. abstract class Frontend extends Controller
  25. {
  26.     /**
  27.      * Meta array
  28.      * @var array
  29.      */
  30.     protected $arrMeta = array();
  31.     /**
  32.      * Aux array
  33.      * @var array
  34.      */
  35.     protected $arrAux = array();
  36.     /**
  37.      * Processed files array
  38.      * @var array
  39.      */
  40.     protected $arrProcessed = array();
  41.     /**
  42.      * Load the database object
  43.      *
  44.      * Make the constructor public, so pages can be instantiated (see #6182)
  45.      */
  46.     public function __construct()
  47.     {
  48.         parent::__construct();
  49.         $this->import(Database::class, 'Database');
  50.     }
  51.     /**
  52.      * Split the current request into fragments, strip the URL suffix, recreate the $_GET array and return the page ID
  53.      *
  54.      * @return mixed
  55.      *
  56.      * @deprecated Deprecated since Contao 4.7, to be removed in Contao 5.0.
  57.      *             Use the Symfony routing instead.
  58.      */
  59.     public static function getPageIdFromUrl()
  60.     {
  61.         trigger_deprecation('contao/core-bundle''4.7''Using "Contao\Frontend::getPageIdFromUrl()" has been deprecated and will no longer work in Contao 5.0. Use the Symfony routing instead.');
  62.         if (!System::getContainer()->getParameter('contao.legacy_routing'))
  63.         {
  64.             throw new LegacyRoutingException('Frontend::getPageIdFromUrl() requires legacy routing. Configure "prepend_locale" or "url_suffix" in your app configuration (e.g. config.yml).');
  65.         }
  66.         $strRequest Environment::get('relativeRequest');
  67.         if (!$strRequest)
  68.         {
  69.             return null;
  70.         }
  71.         // Get the request without the query string
  72.         list($strRequest) = explode('?'$strRequest2);
  73.         // URL decode here (see #6232)
  74.         $strRequest rawurldecode($strRequest);
  75.         // The request string must not contain "auto_item" (see #4012)
  76.         if (strpos($strRequest'/auto_item/') !== false)
  77.         {
  78.             return false;
  79.         }
  80.         // Extract the language
  81.         if (Config::get('addLanguageToUrl'))
  82.         {
  83.             $arrMatches = array();
  84.             // Use the matches instead of substr() (thanks to Mario Müller)
  85.             if (preg_match('@^([a-z]{2}(-[A-Z]{2})?)/(.*)$@'$strRequest$arrMatches))
  86.             {
  87.                 Input::setGet('language'$arrMatches[1]);
  88.                 // Trigger the root page if only the language was given
  89.                 if (!$arrMatches[3])
  90.                 {
  91.                     return null;
  92.                 }
  93.                 $strRequest $arrMatches[3];
  94.             }
  95.             else
  96.             {
  97.                 return false// Language not provided
  98.             }
  99.         }
  100.         // Remove the URL suffix if not just a language root (e.g. en/) is requested
  101.         if ($strRequest && (!Config::get('addLanguageToUrl') || !preg_match('@^[a-z]{2}(-[A-Z]{2})?/$@'$strRequest)))
  102.         {
  103.             $intSuffixLength = \strlen(Config::get('urlSuffix'));
  104.             // Return false if the URL suffix does not match (see #2864)
  105.             if ($intSuffixLength 0)
  106.             {
  107.                 if (substr($strRequest, -$intSuffixLength) != Config::get('urlSuffix'))
  108.                 {
  109.                     return false;
  110.                 }
  111.                 $strRequest substr($strRequest0, -$intSuffixLength);
  112.             }
  113.         }
  114.         $arrFragments null;
  115.         // Use folder-style URLs
  116.         if (strpos($strRequest'/') !== false)
  117.         {
  118.             $strAlias $strRequest;
  119.             $arrOptions = array($strAlias);
  120.             // Compile all possible aliases by applying dirname() to the request (e.g. news/archive/item, news/archive, news)
  121.             while ($strAlias != '/' && strpos($strAlias'/') !== false)
  122.             {
  123.                 $strAlias = \dirname($strAlias);
  124.                 $arrOptions[] = $strAlias;
  125.             }
  126.             /** @var PageModel $objPageModel */
  127.             $objPageModel System::getContainer()->get('contao.framework')->getAdapter(PageModel::class);
  128.             // Check if there are pages with a matching alias
  129.             $objPages $objPageModel->findByAliases($arrOptions);
  130.             if ($objPages !== null)
  131.             {
  132.                 $arrPages = array();
  133.                 // Order by domain and language
  134.                 while ($objPages->next())
  135.                 {
  136.                     /** @var PageModel $objModel */
  137.                     $objModel $objPages->current();
  138.                     $objPage  $objModel->loadDetails();
  139.                     $domain $objPage->domain ?: '*';
  140.                     $arrPages[$domain][$objPage->rootLanguage][] = $objPage;
  141.                     // Also store the fallback language
  142.                     if ($objPage->rootIsFallback)
  143.                     {
  144.                         $arrPages[$domain]['*'][] = $objPage;
  145.                     }
  146.                 }
  147.                 $arrAliases = array();
  148.                 $strHost Environment::get('host');
  149.                 // Look for a root page whose domain name matches the host name
  150.                 $arrLangs $arrPages[$strHost] ?? $arrPages['*'] ?? array();
  151.                 // Use the first result (see #4872)
  152.                 if (!Config::get('addLanguageToUrl'))
  153.                 {
  154.                     $arrAliases current($arrLangs);
  155.                 }
  156.                 // Try to find a page matching the language parameter
  157.                 elseif (($lang Input::get('language')) && isset($arrLangs[$lang]))
  158.                 {
  159.                     $arrAliases $arrLangs[$lang];
  160.                 }
  161.                 // Return if there are no matches
  162.                 if (empty($arrAliases))
  163.                 {
  164.                     return false;
  165.                 }
  166.                 $objPage $arrAliases[0];
  167.                 // The request consists of the alias only
  168.                 if ($strRequest == $objPage->alias)
  169.                 {
  170.                     $arrFragments = array($strRequest);
  171.                 }
  172.                 // Remove the alias from the request string, explode it and then re-insert the alias at the beginning
  173.                 else
  174.                 {
  175.                     $arrFragments explode('/'substr($strRequest, \strlen($objPage->alias) + 1));
  176.                     array_unshift($arrFragments$objPage->alias);
  177.                 }
  178.             }
  179.         }
  180.         // If folderUrl is deactivated or did not find a matching page
  181.         if ($arrFragments === null)
  182.         {
  183.             if ($strRequest == '/')
  184.             {
  185.                 return false;
  186.             }
  187.             $arrFragments explode('/'$strRequest);
  188.         }
  189.         // Add the second fragment as auto_item if the number of fragments is even
  190.         if (\count($arrFragments) % == 0)
  191.         {
  192.             if (!Config::get('useAutoItem'))
  193.             {
  194.                 return false// see #264
  195.             }
  196.             ArrayUtil::arrayInsert($arrFragments1, array('auto_item'));
  197.         }
  198.         // HOOK: add custom logic
  199.         if (isset($GLOBALS['TL_HOOKS']['getPageIdFromUrl']) && \is_array($GLOBALS['TL_HOOKS']['getPageIdFromUrl']))
  200.         {
  201.             foreach ($GLOBALS['TL_HOOKS']['getPageIdFromUrl'] as $callback)
  202.             {
  203.                 $arrFragments = static::importStatic($callback[0])->{$callback[1]}($arrFragments);
  204.             }
  205.         }
  206.         // Return if the alias is empty (see #4702 and #4972)
  207.         if (!$arrFragments[0] && \count($arrFragments) > 1)
  208.         {
  209.             return false;
  210.         }
  211.         // Add the fragments to the $_GET array
  212.         for ($i=1$c=\count($arrFragments); $i<$c$i+=2)
  213.         {
  214.             // Return false if the key is empty (see #4702 and #263)
  215.             if (!$arrFragments[$i])
  216.             {
  217.                 return false;
  218.             }
  219.             // Return false if there is a duplicate parameter (duplicate content) (see #4277)
  220.             if (isset($_GET[$arrFragments[$i]]))
  221.             {
  222.                 return false;
  223.             }
  224.             // Return false if the request contains an auto_item keyword (duplicate content) (see #4012)
  225.             if (Config::get('useAutoItem') && \in_array($arrFragments[$i], $GLOBALS['TL_AUTO_ITEM']))
  226.             {
  227.                 return false;
  228.             }
  229.             Input::setGet($arrFragments[$i], $arrFragments[$i+1], true);
  230.         }
  231.         return $arrFragments[0] ?: null;
  232.     }
  233.     /**
  234.      * Return the root page ID
  235.      *
  236.      * @return integer
  237.      *
  238.      * @deprecated Deprecated since Contao 4.0, to be removed in Contao 5.0.
  239.      *             Use Frontend::getRootPageFromUrl()->id instead.
  240.      */
  241.     public static function getRootIdFromUrl()
  242.     {
  243.         trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Frontend::getRootIdFromUrl()" has been deprecated and will no longer work in Contao 5.0. Use "Contao\Frontend::getRootPageFromUrl()->id" instead.');
  244.         return static::getRootPageFromUrl()->id;
  245.     }
  246.     /**
  247.      * Try to find a root page based on language and URL
  248.      *
  249.      * @return PageModel
  250.      */
  251.     public static function getRootPageFromUrl()
  252.     {
  253.         if (!System::getContainer()->getParameter('contao.legacy_routing'))
  254.         {
  255.             $objRequest System::getContainer()->get('request_stack')->getCurrentRequest();
  256.             if ($objRequest instanceof Request)
  257.             {
  258.                 $objPage $objRequest->attributes->get('pageModel');
  259.                 if ($objPage instanceof PageModel)
  260.                 {
  261.                     $objPage->loadDetails();
  262.                     return PageModel::findByPk($objPage->rootId);
  263.                 }
  264.             }
  265.             throw new NoRootPageFoundException('No root page found');
  266.         }
  267.         $accept_language Environment::get('httpAcceptLanguage');
  268.         $blnAddLanguageToUrl System::getContainer()->getParameter('contao.prepend_locale');
  269.         // Get the language from the URL if it is not set (see #456)
  270.         if (!isset($_GET['language']) && $blnAddLanguageToUrl)
  271.         {
  272.             $arrMatches = array();
  273.             // Get the request without the query string
  274.             list($strRequest) = explode('?'Environment::get('relativeRequest'), 2);
  275.             if (preg_match('@^([a-z]{2}(-[A-Z]{2})?)/@'$strRequest$arrMatches))
  276.             {
  277.                 Input::setGet('language'$arrMatches[1]);
  278.             }
  279.         }
  280.         // The language is set in the URL
  281.         if (!empty($_GET['language']) && $blnAddLanguageToUrl)
  282.         {
  283.             $strUri Environment::get('url') . '/' Input::get('language') . '/';
  284.         }
  285.         // No language given
  286.         else
  287.         {
  288.             $strUri Environment::get('url') . '/';
  289.         }
  290.         $objRequest Request::create($strUri);
  291.         $objRequest->headers->set('Accept-Language'$accept_language);
  292.         try
  293.         {
  294.             $arrParameters System::getContainer()->get('contao.routing.nested_matcher')->matchRequest($objRequest);
  295.         }
  296.         catch (RoutingExceptionInterface $exception)
  297.         {
  298.             try
  299.             {
  300.                 $arrParameters System::getContainer()->get('contao.routing.nested_404_matcher')->matchRequest($objRequest);
  301.             }
  302.             catch (RoutingExceptionInterface $exception)
  303.             {
  304.                 throw new NoRootPageFoundException('No root page found'0$exception);
  305.             }
  306.         }
  307.         $objRootPage $arrParameters['pageModel'] ?? null;
  308.         if (!$objRootPage instanceof PageModel)
  309.         {
  310.             throw new MissingMandatoryParametersException('Every Contao route must have a "pageModel" parameter');
  311.         }
  312.         // Redirect to the website root or language root (e.g. en/)
  313.         if (!Environment::get('relativeRequest'))
  314.         {
  315.             if ($blnAddLanguageToUrl)
  316.             {
  317.                 $arrParams = array('_locale' => LocaleUtil::formatAsLocale($objRootPage->language));
  318.                 $strUrl System::getContainer()->get('router')->generate('contao_index'$arrParams);
  319.                 $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  320.                 static::redirect($strUrl);
  321.             }
  322.             // Redirect if the page alias is not "index" or "/" (see #8498, #8560 and #1210)
  323.             elseif ($objRootPage->type !== 'root' && !\in_array($objRootPage->alias, array('index''/')))
  324.             {
  325.                 static::redirect($objRootPage->getAbsoluteUrl());
  326.             }
  327.         }
  328.         if ($objRootPage->type != 'root')
  329.         {
  330.             return PageModel::findByPk($objRootPage->rootId);
  331.         }
  332.         return $objRootPage;
  333.     }
  334.     /**
  335.      * Overwrite the parent method as front end URLs are handled differently
  336.      *
  337.      * @param string  $strRequest
  338.      * @param boolean $blnIgnoreParams
  339.      * @param array   $arrUnset
  340.      *
  341.      * @return string
  342.      */
  343.     public static function addToUrl($strRequest$blnIgnoreParams=false$arrUnset=array())
  344.     {
  345.         /** @var PageModel $objPage */
  346.         global $objPage;
  347.         $arrGet $blnIgnoreParams ? array() : $_GET;
  348.         // Clean the $_GET values (thanks to thyon)
  349.         foreach (array_keys($arrGet) as $key)
  350.         {
  351.             $arrGet[$key] = Input::get($keytruetrue);
  352.         }
  353.         $arrFragments preg_split('/&(amp;)?/i'$strRequest);
  354.         // Merge the new request string
  355.         foreach ($arrFragments as $strFragment)
  356.         {
  357.             list($key$value) = explode('='$strFragment);
  358.             if (!$value)
  359.             {
  360.                 unset($arrGet[$key]);
  361.             }
  362.             else
  363.             {
  364.                 $arrGet[$key] = $value;
  365.             }
  366.         }
  367.         // Unset the language parameter
  368.         if ($objPage->urlPrefix)
  369.         {
  370.             unset($arrGet['language']);
  371.         }
  372.         $strParams    '';
  373.         $strConnector '/';
  374.         $strSeparator '/';
  375.         // Compile the parameters string
  376.         foreach ($arrGet as $k=>$v)
  377.         {
  378.             // Omit the key if it is an auto_item key (see #5037)
  379.             if ($objPage->useAutoItem && ($k == 'auto_item' || \in_array($k$GLOBALS['TL_AUTO_ITEM'])))
  380.             {
  381.                 $strParams $strConnector urlencode($v) . $strParams;
  382.             }
  383.             else
  384.             {
  385.                 $strParams .= $strConnector urlencode($k) . $strSeparator urlencode($v);
  386.             }
  387.         }
  388.         $strUrl System::getContainer()->get('router')->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, array(RouteObjectInterface::CONTENT_OBJECT => $objPage'parameters' => $strParams));
  389.         $strUrl substr($strUrl, \strlen(Environment::get('path')) + 1);
  390.         return $strUrl;
  391.     }
  392.     /**
  393.      * Redirect to a jumpTo page or reload the current page
  394.      *
  395.      * @param integer|array $intId
  396.      * @param string        $strParams
  397.      * @param string        $strForceLang
  398.      */
  399.     protected function jumpToOrReload($intId$strParams=null$strForceLang=null)
  400.     {
  401.         if ($strForceLang !== null)
  402.         {
  403.             trigger_deprecation('contao/core-bundle''4.0''Using "Contao\Frontend::jumpToOrReload()" with $strForceLang has been deprecated and will no longer work in Contao 5.0.');
  404.         }
  405.         /** @var PageModel $objPage */
  406.         global $objPage;
  407.         // Always redirect if there are additional arguments (see #5734)
  408.         $blnForceRedirect = ($strParams !== null || $strForceLang !== null);
  409.         if (\is_array($intId))
  410.         {
  411.             $intId $intId['id'] ?? 0;
  412.         }
  413.         if ($intId && ($intId != $objPage->id || $blnForceRedirect) && ($objNextPage PageModel::findPublishedById($intId)) !== null)
  414.         {
  415.             $this->redirect($objNextPage->getFrontendUrl($strParams$strForceLang));
  416.         }
  417.         $this->reload();
  418.     }
  419.     /**
  420.      * Check whether a back end or front end user is logged in
  421.      *
  422.      * @param string $strCookie
  423.      *
  424.      * @return boolean
  425.      *
  426.      * @deprecated Deprecated since Contao 4.5, to be removed in Contao 5.0.
  427.      *             Use Symfony security instead.
  428.      */
  429.     protected function getLoginStatus($strCookie)
  430.     {
  431.         trigger_deprecation('contao/core-bundle''4.5''Using "Contao\Frontend::getLoginStatus()" has been deprecated and will no longer work in Contao 5.0. Use Symfony security instead.');
  432.         $objTokenChecker System::getContainer()->get('contao.security.token_checker');
  433.         if ($strCookie == 'BE_USER_AUTH' && $objTokenChecker->hasBackendUser())
  434.         {
  435.             // Always return false if we are not in preview mode (show hidden elements)
  436.             if (TL_MODE == 'FE' && !$objTokenChecker->isPreviewMode())
  437.             {
  438.                 return false;
  439.             }
  440.             return true;
  441.         }
  442.         if ($strCookie == 'FE_USER_AUTH' && $objTokenChecker->hasFrontendUser())
  443.         {
  444.             return true;
  445.         }
  446.         return false;
  447.     }
  448.     /**
  449.      * Get the metadata from a serialized string
  450.      *
  451.      * @param string $strData
  452.      * @param string $strLanguage
  453.      *
  454.      * @return array
  455.      */
  456.     public static function getMetaData($strData$strLanguage)
  457.     {
  458.         if (empty($strLanguage))
  459.         {
  460.             return array();
  461.         }
  462.         $arrData StringUtil::deserialize($strData);
  463.         // Convert the language to a locale (see #5678)
  464.         $strLanguage LocaleUtil::formatAsLocale($strLanguage);
  465.         if (!\is_array($arrData) || !isset($arrData[$strLanguage]))
  466.         {
  467.             return array();
  468.         }
  469.         return $arrData[$strLanguage];
  470.     }
  471.     /**
  472.      * Prepare a text to be used in the meta description tag
  473.      *
  474.      * @param string $strText
  475.      *
  476.      * @return string
  477.      *
  478.      * @deprecated Deprecated since Contao 4.12, to be removed in Contao 5.0.
  479.      *             Use StringUtil::htmlToPlainText() instead.
  480.      */
  481.     protected function prepareMetaDescription($strText)
  482.     {
  483.         trigger_deprecation('contao/core-bundle''4.12''Using "Contao\Frontend::prepareMetaDescription()" has been deprecated and will no longer work Contao 5.0. Use Contao\StringUtil::htmlToPlainText() instead.');
  484.         $strText $this->replaceInsertTags($strTextfalse);
  485.         $strText strip_tags($strText);
  486.         $strText str_replace("\n"' '$strText);
  487.         $strText StringUtil::substr($strText320);
  488.         return trim($strText);
  489.     }
  490.     /**
  491.      * Return the cron timeout in seconds
  492.      *
  493.      * @return integer
  494.      */
  495.     public static function getCronTimeout()
  496.     {
  497.         if (!empty($GLOBALS['TL_CRON']['minutely']))
  498.         {
  499.             return 60;
  500.         }
  501.         if (!empty($GLOBALS['TL_CRON']['hourly']))
  502.         {
  503.             return 3600;
  504.         }
  505.         return 86400// daily
  506.     }
  507.     /**
  508.      * Index a page if applicable
  509.      *
  510.      * @param Response $response
  511.      *
  512.      * @deprecated Deprecated since Contao 4.9, to be removed in Contao 5.0.
  513.      *             Use the "contao.search.indexer" service instead.
  514.      */
  515.     public static function indexPageIfApplicable(Response $response)
  516.     {
  517.         trigger_deprecation('contao/core-bundle''4.9''Using "Contao\Frontend::indexPageIfApplicable()" has been deprecated and will no longer work in Contao 5.0. Use the "contao.search.indexer" service instead.');
  518.         $searchIndexer System::getContainer()->get('contao.search.indexer');
  519.         // The search indexer is disabled
  520.         if ($searchIndexer === null)
  521.         {
  522.             return;
  523.         }
  524.         $request System::getContainer()->get('request_stack')->getCurrentRequest();
  525.         if ($request === null)
  526.         {
  527.             throw new \RuntimeException('The request stack did not contain a request');
  528.         }
  529.         $document Document::createFromRequestResponse($request$response);
  530.         $searchIndexer->index($document);
  531.     }
  532.     /**
  533.      * Check whether there is a cached version of the page and return a response object
  534.      *
  535.      * @return Response|null
  536.      *
  537.      * @deprecated Deprecated since Contao 4.3, to be removed in Contao 5.0.
  538.      *             Use proper response caching headers instead.
  539.      */
  540.     public static function getResponseFromCache()
  541.     {
  542.         trigger_deprecation('contao/core-bundle''4.3''Using "Contao\Frontend::getResponseFromCache()" has been deprecated and will no longer work in Contao 5.0. Use proper response caching headers instead.');
  543.         return null;
  544.     }
  545. }
  546. class_alias(Frontend::class, 'Frontend');