vendor/pimcore/pimcore/lib/Navigation/Builder.php line 82

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Navigation;
  15. use Pimcore\Cache as CacheManager;
  16. use Pimcore\Http\RequestHelper;
  17. use Pimcore\Logger;
  18. use Pimcore\Model\Document;
  19. use Pimcore\Model\Site;
  20. use Pimcore\Navigation\Page\Document as DocumentPage;
  21. use Pimcore\Navigation\Page\Url;
  22. /**
  23.  * @internal
  24.  */
  25. class Builder
  26. {
  27.     /**
  28.      * @var RequestHelper
  29.      */
  30.     private $requestHelper;
  31.     /**
  32.      * @var string
  33.      */
  34.     protected $htmlMenuIdPrefix;
  35.     /**
  36.      * @var string
  37.      */
  38.     protected $pageClass DocumentPage::class;
  39.     /**
  40.      * @var int
  41.      */
  42.     private $currentLevel 0;
  43.     /**
  44.      * @var array
  45.      */
  46.     private $navCacheTags = [];
  47.     /**
  48.      * @param RequestHelper $requestHelper
  49.      * @param string|null $pageClass
  50.      */
  51.     public function __construct(RequestHelper $requestHelperstring $pageClass null)
  52.     {
  53.         $this->requestHelper $requestHelper;
  54.         if (null !== $pageClass) {
  55.             $this->pageClass $pageClass;
  56.         }
  57.     }
  58.     /**
  59.      * @param Document|null $activeDocument
  60.      * @param Document|null $navigationRootDocument
  61.      * @param string|null $htmlMenuIdPrefix
  62.      * @param \Closure|null $pageCallback
  63.      * @param bool|string $cache
  64.      * @param int|null $maxDepth
  65.      * @param int|null $cacheLifetime
  66.      *
  67.      * @return mixed|\Pimcore\Navigation\Container
  68.      *
  69.      * @throws \Exception
  70.      */
  71.     public function getNavigation($activeDocument null$navigationRootDocument null$htmlMenuIdPrefix null$pageCallback null$cache true, ?int $maxDepth null, ?int $cacheLifetime null)
  72.     {
  73.         $cacheEnabled $cache !== false;
  74.         $this->htmlMenuIdPrefix $htmlMenuIdPrefix;
  75.         if (!$navigationRootDocument) {
  76.             $navigationRootDocument Document::getById(1);
  77.         }
  78.         // the cache key consists out of the ID and the class name (eg. for hardlinks) of the root document and the optional html prefix
  79.         $cacheKeys = ['root_id__' $navigationRootDocument->getId(), $htmlMenuIdPrefixget_class($navigationRootDocument)];
  80.         if (Site::isSiteRequest()) {
  81.             $site Site::getCurrentSite();
  82.             $cacheKeys[] = 'site__' $site->getId();
  83.         }
  84.         if (is_string($cache)) {
  85.             $cacheKeys[] = 'custom__' $cache;
  86.         }
  87.         if ($pageCallback instanceof \Closure) {
  88.             $cacheKeys[] = 'pageCallback_' closureHash($pageCallback);
  89.         }
  90.         if ($maxDepth) {
  91.             $cacheKeys[] = 'maxDepth_' $maxDepth;
  92.         }
  93.         $cacheKey 'nav_' md5(serialize($cacheKeys));
  94.         $navigation CacheManager::load($cacheKey);
  95.         if (!$navigation || !$cacheEnabled) {
  96.             $navigation = new \Pimcore\Navigation\Container();
  97.             $this->navCacheTags = ['output''navigation'];
  98.             if ($navigationRootDocument->hasChildren()) {
  99.                 $this->currentLevel 0;
  100.                 $rootPage $this->buildNextLevel($navigationRootDocumenttrue$pageCallback, [], $maxDepth);
  101.                 $navigation->addPages($rootPage);
  102.             }
  103.             // we need to force caching here, otherwise the active classes and other settings will be set and later
  104.             // also written into cache (pass-by-reference) ... when serializing the data directly here, we don't have this problem
  105.             if ($cacheEnabled) {
  106.                 CacheManager::save($navigation$cacheKey$this->navCacheTags$cacheLifetime999true);
  107.             }
  108.         }
  109.         // set active path
  110.         $activePages = [];
  111.         if ($this->requestHelper->hasMainRequest()) {
  112.             $request $this->requestHelper->getMainRequest();
  113.             // try to find a page matching exactly the request uri
  114.             $activePages $navigation->findAllBy('uri'$request->getRequestUri());
  115.             if (empty($activePages)) {
  116.                 // try to find a page matching the path info
  117.                 $activePages $navigation->findAllBy('uri'$request->getPathInfo());
  118.             }
  119.         }
  120.         if ($activeDocument instanceof Document) {
  121.             if (empty($activePages)) {
  122.                 // use the provided pimcore document
  123.                 $activePages $navigation->findAllBy('realFullPath'$activeDocument->getRealFullPath());
  124.             }
  125.             if (empty($activePages)) {
  126.                 // find by link target
  127.                 $activePages $navigation->findAllBy('uri'$activeDocument->getFullPath());
  128.             }
  129.         }
  130.         // cleanup active pages from links
  131.         // pages have priority, if we don't find any active page, we use all we found
  132.         $tmpPages = [];
  133.         foreach ($activePages as $page) {
  134.             if ($page instanceof DocumentPage && $page->getDocumentType() !== 'link') {
  135.                 $tmpPages[] = $page;
  136.             }
  137.         }
  138.         if (count($tmpPages)) {
  139.             $activePages $tmpPages;
  140.         }
  141.         if (!empty($activePages)) {
  142.             // we found an active document, so we can build the active trail by getting respectively the parent
  143.             foreach ($activePages as $activePage) {
  144.                 $this->addActiveCssClasses($activePagetrue);
  145.             }
  146.         } elseif ($activeDocument instanceof Document) {
  147.             // we didn't find the active document, so we try to build the trail on our own
  148.             $allPages $navigation->findAllBy('uri''/.*/'true);
  149.             foreach ($allPages as $page) {
  150.                 $activeTrail false;
  151.                 if ($page instanceof Url && $page->getUri()) {
  152.                     if (strpos($activeDocument->getRealFullPath(), $page->getUri() . '/') === 0) {
  153.                         $activeTrail true;
  154.                     } elseif (
  155.                         $page instanceof DocumentPage &&
  156.                         $page->getDocumentType() === 'link' &&
  157.                         strpos($activeDocument->getFullPath(), $page->getUri() . '/') === 0
  158.                     ) {
  159.                         $activeTrail true;
  160.                     }
  161.                 }
  162.                 if ($activeTrail) {
  163.                     $page->setActive(true);
  164.                     $page->setClass($page->getClass() . ' active active-trail');
  165.                 }
  166.             }
  167.         }
  168.         return $navigation;
  169.     }
  170.     /**
  171.      * @param Page $page
  172.      * @param bool $isActive
  173.      *
  174.      * @throws \Exception
  175.      */
  176.     protected function addActiveCssClasses(Page $page$isActive false)
  177.     {
  178.         $page->setActive(true);
  179.         $parent $page->getParent();
  180.         $isRoot false;
  181.         $classes '';
  182.         if ($parent instanceof DocumentPage) {
  183.             $this->addActiveCssClasses($parent);
  184.         } else {
  185.             $isRoot true;
  186.         }
  187.         $classes .= ' active';
  188.         if (!$isActive) {
  189.             $classes .= ' active-trail';
  190.         }
  191.         if ($isRoot && $isActive) {
  192.             $classes .= ' mainactive';
  193.         }
  194.         $page->setClass($page->getClass() . $classes);
  195.     }
  196.     /**
  197.      * @param string $pageClass
  198.      *
  199.      * @return $this
  200.      */
  201.     public function setPageClass(string $pageClass)
  202.     {
  203.         $this->pageClass $pageClass;
  204.         return $this;
  205.     }
  206.     /**
  207.      * Returns the name of the pageclass
  208.      *
  209.      * @return String
  210.      */
  211.     public function getPageClass()
  212.     {
  213.         return $this->pageClass;
  214.     }
  215.     /**
  216.      * @param Document $parentDocument
  217.      *
  218.      * @return Document[]
  219.      */
  220.     protected function getChildren(Document $parentDocument): array
  221.     {
  222.         // the intention of this function is mainly to be overridden in order to customize the behavior of the navigation
  223.         // e.g. for custom filtering and other very specific use-cases
  224.         return $parentDocument->getChildren();
  225.     }
  226.     /**
  227.      * @param Document $parentDocument
  228.      * @param bool $isRoot
  229.      * @param callable $pageCallback
  230.      * @param array $parents
  231.      * @param int|null $maxDepth
  232.      *
  233.      * @return array
  234.      *
  235.      * @throws \Exception
  236.      */
  237.     protected function buildNextLevel($parentDocument$isRoot false$pageCallback null$parents = [], $maxDepth null)
  238.     {
  239.         $this->currentLevel++;
  240.         $pages = [];
  241.         $childs $this->getChildren($parentDocument);
  242.         $parents[$parentDocument->getId()] = $parentDocument;
  243.         if (!is_array($childs)) {
  244.             return $pages;
  245.         }
  246.         foreach ($childs as $child) {
  247.             $classes '';
  248.             if ($child instanceof Document\Hardlink) {
  249.                 $child Document\Hardlink\Service::wrap($child);
  250.                 if (!$child) {
  251.                     continue;
  252.                 }
  253.             }
  254.             // infinite loop detection, we use array keys here, because key lookups are much faster
  255.             if (isset($parents[$child->getId()])) {
  256.                 Logger::critical('Navigation: Document with ID ' $child->getId() . ' would produce an infinite loop -> skipped, parent IDs (' implode(','array_keys($parents)) . ')');
  257.                 continue;
  258.             }
  259.             if (($child instanceof Document\Folder || $child instanceof Document\Page || $child instanceof Document\Link) && $child->getProperty('navigation_name')) {
  260.                 $path $child->getFullPath();
  261.                 if ($child instanceof Document\Link) {
  262.                     $path $child->getHref();
  263.                 }
  264.                 /** @var DocumentPage $page */
  265.                 $page = new $this->pageClass();
  266.                 if (!$child instanceof Document\Folder) {
  267.                     $page->setUri($path $child->getProperty('navigation_parameters') . $child->getProperty('navigation_anchor'));
  268.                 }
  269.                 $page->setLabel($child->getProperty('navigation_name'));
  270.                 $page->setActive(false);
  271.                 $page->setId($this->htmlMenuIdPrefix $child->getId());
  272.                 $page->setClass($child->getProperty('navigation_class'));
  273.                 $page->setTarget($child->getProperty('navigation_target'));
  274.                 $page->setTitle($child->getProperty('navigation_title'));
  275.                 $page->setAccesskey($child->getProperty('navigation_accesskey'));
  276.                 $page->setTabindex($child->getProperty('navigation_tabindex'));
  277.                 $page->setRelation($child->getProperty('navigation_relation'));
  278.                 $page->setDocument($child);
  279.                 if ($child->getProperty('navigation_exclude') || !$child->getPublished()) {
  280.                     $page->setVisible(false);
  281.                 }
  282.                 if ($isRoot) {
  283.                     $classes .= ' main';
  284.                 }
  285.                 $page->setClass($page->getClass() . $classes);
  286.                 if ($child->hasChildren() && (!$maxDepth || $maxDepth $this->currentLevel)) {
  287.                     $childPages $this->buildNextLevel($childfalse$pageCallback$parents$maxDepth);
  288.                     $page->setPages($childPages);
  289.                 }
  290.                 if ($pageCallback instanceof \Closure) {
  291.                     $pageCallback($page$child);
  292.                 }
  293.                 $this->navCacheTags[] = $page->getDocument()->getCacheTag();
  294.                 $pages[] = $page;
  295.             }
  296.         }
  297.         $this->currentLevel--;
  298.         return $pages;
  299.     }
  300. }