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

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