src/Frontend/Core/Engine/Page.php line 107

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\Engine\Block\ModuleExtraInterface;
  6. use Frontend\Core\Header\Header;
  7. use Frontend\Core\Language\Language;
  8. use Symfony\Component\HttpFoundation\RedirectResponse;
  9. use Symfony\Component\HttpFoundation\Response;
  10. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  11. use Symfony\Component\HttpKernel\KernelInterface;
  12. use Frontend\Core\Engine\Block\ExtraInterface as FrontendBlockExtra;
  13. use Frontend\Core\Engine\Block\Widget as FrontendBlockWidget;
  14. use Backend\Core\Engine\Model as BackendModel;
  15. use Frontend\Modules\Profiles\Engine\Authentication as FrontendAuthenticationModel;
  16. use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
  17. /**
  18.  * Frontend page class, this class will handle everything on a page
  19.  */
  20. class Page extends KernelLoader
  21. {
  22.     /**
  23.      * Breadcrumb instance
  24.      *
  25.      * @var Breadcrumb
  26.      */
  27.     protected $breadcrumb;
  28.     /**
  29.      * Array of extras linked to this page
  30.      *
  31.      * @var array
  32.      */
  33.     protected $extras = [];
  34.     /**
  35.      * Footer instance
  36.      *
  37.      * @var Footer
  38.      */
  39.     protected $footer;
  40.     /**
  41.      * Header instance
  42.      *
  43.      * @var Header
  44.      */
  45.     protected $header;
  46.     /**
  47.      * Header instance
  48.      *
  49.      * @var Modals
  50.      */
  51.     protected $modals;
  52.     /**
  53.      * The current pageId
  54.      *
  55.      * @var int
  56.      */
  57.     protected $pageId;
  58.     /**
  59.      * Content of the page
  60.      *
  61.      * @var array
  62.      */
  63.     protected $record = [];
  64.     /**
  65.      * The path of the template to show
  66.      *
  67.      * @var string
  68.      */
  69.     protected $templatePath;
  70.     /**
  71.      * The statuscode
  72.      *
  73.      * @var int
  74.      */
  75.     protected $statusCode Response::HTTP_OK;
  76.     /**
  77.      * TwigTemplate instance
  78.      *
  79.      * @var TwigTemplate
  80.      */
  81.     protected $template;
  82.     /**
  83.      * URL instance
  84.      *
  85.      * @var Url
  86.      */
  87.     protected $url;
  88.     public function __construct(KernelInterface $kernel)
  89.     {
  90.         parent::__construct($kernel);
  91.         $this->getContainer()->set('page'$this);
  92.         $this->template $this->getContainer()->get('templating');
  93.         $this->url $this->getContainer()->get('url');
  94.     }
  95.     /**
  96.      * Loads the actual components on the page
  97.      */
  98.     public function load(): void
  99.     {
  100.         // set tracking cookie
  101.         Model::getVisitorId();
  102.         // create header instance
  103.         $this->header = new Header($this->getKernel());
  104.         /* @var $pageAliasUrl PageUrlAliasInterface */
  105.         $pageAliasUrl $this->get('fork.frontend.page_url_alias');
  106.         try {
  107.             $this->handlePage($pageAliasUrl->getPageId(implode('/'$this->url->getPages()), FRONTEND_LANGUAGE));
  108.         } catch (NotFoundHttpException $notFoundHttpException) {
  109.             $this->handlePage(Response::HTTP_NOT_FOUND);
  110.         } catch (InsufficientAuthenticationException $insufficientAuthenticationException) {
  111.             $this->redirectToLogin();
  112.         }
  113.     }
  114.     private function handlePage(int $pageId)
  115.     {
  116.         // get page content from pageId of the requested URL
  117.         $this->record $this->getPageContent($pageId);
  118.         /* @var $pageAliasUrl PageUrlAliasInterface */
  119.         $pageAliasUrl $this->get('fork.frontend.page_url_alias');
  120.         //redirect when url not alias and alias url is present
  121.         if(!empty($this->record) && !$pageAliasUrl->isAlias(implode('/'$this->url->getPages()), FRONTEND_LANGUAGE) && $pageAliasUrl->hasPageIdAlias($this->record['id'], FRONTEND_LANGUAGE)) {
  122.             throw new RedirectException(
  123.                 'Redirect',
  124.                 new RedirectResponse(
  125.                     Navigation::getURL($this->record['id']),
  126.                     301
  127.                 )
  128.             );
  129.         }
  130.         if (empty($this->record)) {
  131.             throw new NotFoundHttpException('No page was found for the page id:' $pageId);
  132.         }
  133.         $this->checkAuthentication();
  134.         // we need to set the correct id
  135.         $this->pageId = (int) $this->record['id'];
  136.         if ($this->pageId === Response::HTTP_NOT_FOUND) {
  137.             $this->statusCode Response::HTTP_NOT_FOUND;
  138.             if (extension_loaded('newrelic')) {
  139.                 newrelic_name_transaction('404');
  140.             }
  141.         }
  142.         $this->breadcrumb = new Breadcrumb($this->getKernel());
  143.         $this->footer = new Footer($this->getKernel());
  144.         $this->modals = new Modals($this->getKernel(), $this->pageId);
  145.         $this->processPage();
  146.         // execute all extras linked to the page
  147.         array_map([$this'processExtra'], $this->extras);
  148.     }
  149.     private function checkAuthentication(): void
  150.     {
  151.         // no authentication needed
  152.         if (!isset($this->record['data']['auth_required'])
  153.             || !$this->record['data']['auth_required']
  154.             || !BackendModel::isModuleInstalled('Profiles')
  155.         ) {
  156.             return;
  157.         }
  158.         if (!FrontendAuthenticationModel::isLoggedIn()) {
  159.             throw new InsufficientAuthenticationException('You must log in to see this page');
  160.         }
  161.         // specific groups for auth?
  162.         if (empty($this->record['data']['auth_groups'])) {
  163.             // no further checks needed
  164.             return;
  165.         }
  166.         foreach ($this->record['data']['auth_groups'] as $group) {
  167.             if (FrontendAuthenticationModel::getProfile()->isInGroup($group)) {
  168.                 // profile is in a group that is allowed to see the page
  169.                 return;
  170.             }
  171.         }
  172.         throw new NotFoundHttpException('The current page is not available to the logged in profile');
  173.     }
  174.     public function display(): Response
  175.     {
  176.         try {
  177.             $this->template->assignGlobal('homePage'$this->pageId == 1);
  178.             // assign the id so we can use it as an option
  179.             $this->template->assignGlobal('isPage' $this->pageIdtrue);
  180.             $this->template->assignGlobal('isChildOfPage' $this->record['parent_id'], true);
  181.             // hide the cookiebar from within the code to prevent flickering
  182.             $this->template->assignGlobal(
  183.                 'cookieBarHide',
  184.                 !$this->get('fork.settings')->get('Core''show_cookie_bar'false)
  185.                 || $this->getContainer()->get('fork.cookie')->hasHiddenCookieBar()
  186.             );
  187.             // assign globals for GtmCookieBar module
  188.             if (BackendModel::isModuleInstalled('GtmCookieBar'))
  189.             {
  190.                 $this->template->assignGlobal('gtmCookieSettings', \Frontend\Modules\GtmCookieBar\Engine\Model::getGtmCookieSettings());
  191.             }
  192.             // assign globals for PageExtend module
  193.             if (BackendModel::isModuleInstalled('PagesExtension'))
  194.             {
  195.                 $this->template->assignGlobal('pageClass', \Frontend\Modules\PagesExtension\Engine\Model::getPageClass($this->pageIdFRONTEND_LANGUAGE) ?: null);
  196.             }
  197.             $this->parsePositions();
  198.             // assign empty positions
  199.             $unusedPositions array_diff(
  200.                 $this->record['template_data']['names'],
  201.                 array_keys($this->record['positions'])
  202.             );
  203.             foreach ($unusedPositions as $position) {
  204.                 $this->template->assign('position' . \SpoonFilter::ucfirst($position), []);
  205.             }
  206.             $this->header->parse();
  207.             $this->breadcrumb->parse();
  208.             $this->parseLanguages();
  209.             $this->footer->parse();
  210.             $this->modals->parse();
  211.             return new Response(
  212.                 $this->template->getContent($this->templatePath),
  213.                 $this->statusCode
  214.             );
  215.         } catch (NotFoundHttpException $notFoundHttpException) {
  216.             $this->handlePage(Response::HTTP_NOT_FOUND);
  217.             return $this->display();
  218.         } catch (InsufficientAuthenticationException $insufficientAuthenticationException) {
  219.             $this->redirectToLogin();
  220.         }
  221.     }
  222.     /**
  223.      * Redirects to the login page in a way that the login page will redirect back to the current page after logging in
  224.      */
  225.     private function redirectToLogin()
  226.     {
  227.         $this->redirect(
  228.             Navigation::getUrlForBlock('Profiles''Login') . '?queryString=' Model::getRequest()->getRequestUri(),
  229.             Response::HTTP_TEMPORARY_REDIRECT
  230.         );
  231.     }
  232.     public function getExtras(): array
  233.     {
  234.         return $this->extras;
  235.     }
  236.     public function getId(): int
  237.     {
  238.         return $this->pageId;
  239.     }
  240.     private function getPageRecord(int $pageId): array
  241.     {
  242.         if ($this->url->getParameter('page_revision''int') === null) {
  243.             return Model::getPage($pageId);
  244.         }
  245.         // add no-index to meta-custom, so the draft won't get accidentally indexed
  246.         $this->header->addMetaData(['name' => 'robots''content' => 'noindex, nofollow'], true);
  247.         return Model::getPageRevision($this->url->getParameter('page_revision''int'));
  248.     }
  249.     protected function getPageContent(int $pageId): array
  250.     {
  251.         $record $this->getPageRecord($pageId);
  252.         if (empty($record)) {
  253.             return [];
  254.         }
  255.         // redirect to the first child if the page is empty
  256.         if ($this->allPositionsAreEmpty($record['positions'])) {
  257.             $firstChildId Navigation::getFirstChildId($record['id']);
  258.             // check if we actually have a first child
  259.             if ($firstChildId !== 0) {
  260.                 $this->redirect(Navigation::getUrl($firstChildId));
  261.             }
  262.         }
  263.         return $record;
  264.     }
  265.     private function allPositionsAreEmpty(array $positions): bool
  266.     {
  267.         // loop positions to check if they are empty
  268.         foreach ($positions as $blocks) {
  269.             // loop blocks in position
  270.             foreach ($blocks as $block) {
  271.                 // It isn't empty if HTML is provided, a decent extra is provided or a widget is provided
  272.                 if ($block['extra_type'] === 'block'
  273.                     || $block['extra_type'] === 'widget'
  274.                     || trim($block['html']) !== ''
  275.                 ) {
  276.                     return false;
  277.                 }
  278.             }
  279.         }
  280.         return true;
  281.     }
  282.     public function getRecord(): array
  283.     {
  284.         return $this->record;
  285.     }
  286.     public function getStatusCode(): int
  287.     {
  288.         return $this->statusCode;
  289.     }
  290.     protected function parseLanguages(): void
  291.     {
  292.         // just execute if the site is multi-language
  293.         if (!$this->getContainer()->getParameter('site.multilanguage') || count(Language::getActiveLanguages()) === 1) {
  294.             return;
  295.         }
  296.         $this->template->assignGlobal(
  297.             'languages',
  298.             array_map(
  299.                 function (string $language) {
  300.                     return [
  301.                         'url' => '/' $language,
  302.                         'label' => $language,
  303.                         'name' => Language::msg(mb_strtoupper($language)),
  304.                         'current' => $language === LANGUAGE,
  305.                     ];
  306.                 },
  307.                 Language::getActiveLanguages()
  308.             )
  309.         );
  310.     }
  311.     protected function parsePositions(): void
  312.     {
  313.         // init array to store parsed positions data
  314.         $positions = [];
  315.         // fetch variables from main template
  316.         $mainVariables $this->template->getAssignedVariables();
  317.         // loop all positions
  318.         foreach ($this->record['positions'] as $position => $blocks) {
  319.             // loop all blocks in this position
  320.             foreach ($blocks as $i => $block) {
  321.                 $positions[$position][$i] = $this->parseBlock($block$mainVariables);
  322.             }
  323.             // assign position to template
  324.             $this->template->assign('position' . \SpoonFilter::ucfirst($position), $positions[$position]);
  325.         }
  326.         $this->template->assign('positions'$positions);
  327.     }
  328.     private function parseBlock(array $block, array $mainVariables): array
  329.     {
  330.         if (!isset($block['extra'])) {
  331.             $parsedBlock $block;
  332.             if (array_key_exists('blockContent'$block)) {
  333.                 $parsedBlock['html'] = $block['blockContent'];
  334.             }
  335.             return $parsedBlock;
  336.         }
  337.         $block['extra']->execute();
  338.         $extraVariables $block['extra']->getTemplate()->getAssignedVariables();
  339.         $block['extra']->getTemplate()->assignArray($mainVariables);
  340.         $block['extra']->getTemplate()->assignArray($extraVariables);
  341.         return [
  342.             'variables' => $block['extra']->getTemplate()->getAssignedVariables(),
  343.             'blockIsEditor' => false,
  344.             'html' => $block['extra']->getContent(),
  345.         ];
  346.     }
  347.     protected function processExtra(ModuleExtraInterface $extra): void
  348.     {
  349.         $this->getContainer()->get('logger.public')->info(
  350.             'Executing ' get_class($extra) . " '{$extra->getAction()}' for module '{$extra->getModule()}'."
  351.         );
  352.         // overwrite the template
  353.         if (is_callable([$extra'getOverwrite']) && $extra->getOverwrite()) {
  354.             $this->templatePath $extra->getTemplatePath();
  355.         }
  356.     }
  357.     private function addAlternateLinks(): void
  358.     {
  359.         // no need for alternate links if there is only one language
  360.         if (!$this->getContainer()->getParameter('site.multilanguage')) {
  361.             return;
  362.         }
  363.         array_map([$this'addAlternateLinkForLanguage'], Language::getActiveLanguages());
  364.     }
  365.     private function addAlternateLinkForLanguage(string $language): void
  366.     {
  367.         if ($language === LANGUAGE) {
  368.             return;
  369.         }
  370.         // Get page data
  371.         $pageInfo Model::getPage($this->pageId);
  372.         // Check if hreflang is set for language
  373.         if (isset($pageInfo['data']['hreflang_' $language])) {
  374.             $url Navigation::getUrl($pageInfo['data']['hreflang_' $language], $language);
  375.         } else {
  376.             $url Navigation::getUrl($this->pageId$language);
  377.         }
  378.         // remove last /
  379.         $url rtrim($url'/\\');
  380.         // Ignore 404 links
  381.         if ($this->pageId !== Response::HTTP_NOT_FOUND
  382.             && $url === Navigation::getUrl(Response::HTTP_NOT_FOUND$language)) {
  383.             return;
  384.         }
  385.         // Convert relative to absolute url
  386.         if (strpos($url'/') === 0) {
  387.             $url SITE_URL $url;
  388.         }
  389.         $this->header->addLink(['rel' => 'alternate''hreflang' => $language'href' => $url]);
  390.     }
  391.     private function assignPageMeta(): void
  392.     {
  393.         // set pageTitle
  394.         $this->header->setPageTitle(
  395.             $this->record['meta_title'],
  396.             $this->record['meta_title_overwrite']
  397.         );
  398.         // set meta-data
  399.         $this->header->addMetaDescription(
  400.             $this->record['meta_description'],
  401.             $this->record['meta_description_overwrite']
  402.         );
  403.         $this->header->addMetaKeywords(
  404.             $this->record['meta_keywords'],
  405.             $this->record['meta_keywords_overwrite']
  406.         );
  407.         $this->header->setMetaCustom($this->record['meta_custom']);
  408.         // advanced SEO-attributes
  409.         if (isset($this->record['meta_seo_index'])) {
  410.             $this->header->addMetaData(
  411.                 ['name' => 'robots''content' => $this->record['meta_seo_index']]
  412.             );
  413.         }
  414.         if (isset($this->record['meta_seo_follow'])) {
  415.             $this->header->addMetaData(
  416.                 ['name' => 'robots''content' => $this->record['meta_seo_follow']]
  417.             );
  418.         }
  419.     }
  420.     protected function processPage(): void
  421.     {
  422.         $this->assignPageMeta();
  423.         new Navigation($this->getKernel());
  424.         $this->addAlternateLinks();
  425.         // assign content
  426.         $pageInfo Navigation::getPageInfo($this->record['id']);
  427.         $this->record['has_children'] = $pageInfo['has_children'];
  428.         $this->template->assignGlobal('page'$this->record);
  429.         // set template path
  430.         $this->templatePath $this->record['template_path'];
  431.         // loop blocks
  432.         foreach ($this->record['positions'] as $position => &$blocks) {
  433.             // position not known in template = skip it
  434.             if (!in_array($position$this->record['template_data']['names'])) {
  435.                 continue;
  436.             }
  437.             $blocks array_map(
  438.                 function (array $block) {
  439.                     if ($block['extra_id'] === null) {
  440.                         return [
  441.                             'blockIsEditor' => true,
  442.                             'blockContent' => $block['html'],
  443.                         ];
  444.                     }
  445.                     $block = ['extra' => $this->getExtraForBlock($block)];
  446.                     // add to list of extras to parse
  447.                     $this->extras[] = $block['extra'];
  448.                     return $block;
  449.                 },
  450.                 $blocks
  451.             );
  452.         }
  453.     }
  454.     private function getExtraForBlock(array $block): ModuleExtraInterface
  455.     {
  456.         // block
  457.         if ($block['extra_type'] === 'block') {
  458.             if (extension_loaded('newrelic')) {
  459.                 newrelic_name_transaction($block['extra_module'] . '::' $block['extra_action']);
  460.             }
  461.             return new FrontendBlockExtra(
  462.                 $this->getKernel(),
  463.                 $block['extra_module'],
  464.                 $block['extra_action'],
  465.                 $block['extra_data']
  466.             );
  467.         }
  468.         return new FrontendBlockWidget(
  469.             $this->getKernel(),
  470.             $block['extra_module'],
  471.             $block['extra_action'],
  472.             $block['extra_data']
  473.         );
  474.     }
  475.     private function redirect(string $urlint $code RedirectResponse::HTTP_FOUND): void
  476.     {
  477.         throw new RedirectException('Redirect', new RedirectResponse($url$code));
  478.     }
  479. }