custom/plugins/S360MegaMenu/src/Service/MenuLoader.php line 52

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace S360\MegaMenu\Service;
  3. use S360\MegaMenu\Core\Content\Menu\MenuCollection;
  4. use S360\MegaMenu\Core\Content\Menu\MenuEntity;
  5. use S360\MegaMenu\Core\Content\MenuItem\MenuItemCollection;
  6. use S360\MegaMenu\Core\Content\MenuItem\MenuItemEntity;
  7. use S360\MegaMenu\Struct\Tree;
  8. use S360\MegaMenu\Struct\TreeItem;
  9. use Shopware\Core\Content\Category\CategoryEntity;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
  14. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  15. use Symfony\Component\HttpFoundation\Request;
  16. /**
  17.  * Loader for MegaMenu.
  18.  * @package S360\MegaMenu\Service
  19.  */
  20. class MenuLoader
  21. {
  22.     public const CONTENT_TYPES_WITH_PSEUDO_CHILDREN = [
  23.         'custom''categories'
  24.     ];
  25.     public const ROOT_ID_QUERY_PARAM 's360MegamenuItemId';
  26.     public const ACTIVE_PROPERTY_ITEM_ID 'id';
  27.     public const ACTIVE_PROPERTY_CATEGORY_ID 'categoryLinkId';
  28.     /**
  29.      * @var EntityRepositoryInterface
  30.      */
  31.     private $menuRepository;
  32.     /**
  33.      * @var TreeItem
  34.      */
  35.     private $treeItem;
  36.     /**
  37.      * @param EntityRepositoryInterface $menuRepository
  38.      */
  39.     public function __construct(EntityRepositoryInterface $menuRepository)
  40.     {
  41.         $this->menuRepository $menuRepository;
  42.         $this->treeItem = new TreeItem(null, []);
  43.     }
  44.     public function load(
  45.         SalesChannelContext $context,
  46.         Request $request,
  47.         ?string $activeId,
  48.         string $activeProperty
  49.     ): MenuCollection {
  50.         $criteria = new Criteria();
  51.         $criteria
  52.             ->addAssociation('items.categoryLink.children.children.children.media')
  53.             ->addAssociation('items.categoryLink.children.children.media')
  54.             ->addAssociation('items.categoryLink.children.media')
  55.             ->addAssociation('items.categoryLink.media')
  56.             ->addAssociation('items.customIcon')
  57.             ->addFilter(new OrFilter([
  58.                 new EqualsFilter('salesChannelId'$context->getSalesChannel()->getId()),
  59.                 new EqualsFilter('salesChannelId'null),
  60.             ]));
  61.         // Only load active menues if not in preview mode.
  62.         if (empty($request->get('menu_preview'))) {
  63.             $criteria->addFilter(new EqualsFilter('isActive'1));
  64.         }
  65.         // Load Preview Menu
  66.         if (!empty($request->get('menu_preview'))) {
  67.             $criteria->addFilter(new EqualsFilter('id'$request->get('menu_preview')));
  68.         }
  69.         $root $request->query->get(self::ROOT_ID_QUERY_PARAMnull);
  70.         /** @var MenuCollection $collection */
  71.         $collection $this->menuRepository->search($criteria$context->getContext())->getEntities();
  72.         foreach ($collection->getIterator() as $menu) {
  73.             // Remove inactive menue if it is not currently previewed.
  74.             if ($request->get('menu_preview') != $menu->getId() && !$menu->getIsActive()) {
  75.                 $collection->remove($menu->getId());
  76.             }
  77.             // Build Tree
  78.             $tree $this->getTree($root$menu->getItems() ?? new MenuItemCollection(), $activeId$activeProperty);
  79.             $menu->setTree($tree);
  80.             // Check if active menu has pseudo children (ie a dropdown but no direct children of type MenuItem)
  81.             $active $tree->getActive();
  82.             if ($active && in_array($active->getContentType(), self::CONTENT_TYPES_WITH_PSEUDO_CHILDREN)) {
  83.                 $menu->getTree()->setPseudoChildrenParent(new TreeItem($menu->getTree()->getActive(), []));
  84.             }
  85.             // Checks if we have a active category or child of a menu item
  86.             // If so set its parent (only necessary for mobile menu)
  87.             if (!$active && $menu->getType() == MenuCollection::MOBILE_MENU_TYPE) {
  88.                 $this->fetchPseudoChildParent($menu$activeId);
  89.             }
  90.         }
  91.         return $collection;
  92.     }
  93.     /**
  94.      * Get a deeply nested category by its id
  95.      *
  96.      * @param CategoryEntity|null $category
  97.      * @param string $key
  98.      * @return CategoryEntity|null
  99.      */
  100.     public function getDeepCategory(?CategoryEntity $categorystring $key): ?CategoryEntity
  101.     {
  102.         if ($category) {
  103.             if ($category->getId() == $key) {
  104.                 return $category;
  105.             }
  106.             if ($category->getChildCount() > && $category->getChildren()) {
  107.                 foreach ($category->getChildren() as $child) {
  108.                     $childCat $this->getDeepCategory($child$key);
  109.                     if ($childCat) {
  110.                         return $childCat;
  111.                     }
  112.                 }
  113.             }
  114.         }
  115.         return null;
  116.     }
  117.     /**
  118.      * Load the active category entity.
  119.      *
  120.      * @param Tree|null $tree
  121.      * @param string $navigationId
  122.      * @return CategoryEntity|null
  123.      */
  124.     public function loadActiveCategory(?Tree $treestring $navigationId): ?CategoryEntity
  125.     {
  126.         if (empty($tree) || !empty($tree->getActive()) || empty($tree->getPseudoChildrenParent())) {
  127.             return null;
  128.         }
  129.         $activeCat null;
  130.         $item $tree->getPseudoChildrenParent()->getItem()->getCategoryLink();
  131.         if (empty($item)) {
  132.             return null;
  133.         }
  134.         if ($item->getId() == $navigationId) {
  135.             $activeCat $item;
  136.         }
  137.         if ($item->getChildren()) {
  138.             foreach ($item->getChildren() as $child) {
  139.                 $activeCat $this->getDeepCategory($child$navigationId);
  140.                 if ($activeCat) {
  141.                     break;
  142.                 }
  143.             }
  144.         }
  145.         return $activeCat;
  146.     }
  147.     /**
  148.      * Sort Category
  149.      *
  150.      * @param CategoryEntity|null $active
  151.      * @return void
  152.      */
  153.     public function sortCategories(?CategoryEntity $category): void
  154.     {
  155.         if ($category && $category->getChildren()) {
  156.             $category->getChildren()->sortByPosition();
  157.             // Sort Children of Child
  158.             foreach ($category->getChildren() as $child) {
  159.                 $this->sortCategories($child);
  160.             }
  161.         }
  162.     }
  163.     /**
  164.      * Fetch a pseudo child parent of a category child
  165.      *
  166.      * @param MenuEntity $menu
  167.      * @param string $activeId
  168.      * @return void
  169.      */
  170.     private function fetchPseudoChildParent(MenuEntity $menustring $activeId): void
  171.     {
  172.         $items $menu->getItems()->filter(function ($item) use ($activeId) {
  173.             /** @var MenuItemEntity $item */
  174.             if (!in_array($item->getContentType(), self::CONTENT_TYPES_WITH_PSEUDO_CHILDREN)) {
  175.                 return false;
  176.             }
  177.             if ($item->getCategoryLinkId() == $activeId) {
  178.                 return true;
  179.             }
  180.             if ($item->getCategoryLink() && $item->getCategoryLink()->getChildCount()) {
  181.                 foreach ($item->getCategoryLink()->getChildren() as $child) {
  182.                     $activeCat $this->getDeepCategory($child$activeId);
  183.                     if ($activeCat) {
  184.                         return true;
  185.                     }
  186.                 }
  187.             }
  188.         });
  189.         $item $items->first();
  190.         if ($item && in_array($item->getContentType(), self::CONTENT_TYPES_WITH_PSEUDO_CHILDREN)) {
  191.             $menu->getTree()->setTree([]);
  192.             // $menu->getTree()->setActive($item);
  193.             $menu->getTree()->setPseudoChildrenParent(new TreeItem($item, []));
  194.         }
  195.     }
  196.     /**
  197.      * @param string|null $parentId
  198.      * @param MenuItemCollection $items
  199.      * @param string|null $activeId
  200.      * @return Tree
  201.      */
  202.     private function getTree(?string $parentIdMenuItemCollection $items, ?string $activeId$activeProperty): Tree
  203.     {
  204.         $active null;
  205.         if ($activeProperty === self::ACTIVE_PROPERTY_ITEM_ID) {
  206.             $active $items->get($activeId);
  207.         }
  208.         return new Tree(
  209.             $active ?? $items->getFirstBy($activeProperty$activeId),
  210.             $this->buildTree($parentId$items->getElements())
  211.         );
  212.     }
  213.     /**
  214.      * @param string|null $parentId
  215.      * @param MenuItemEntity[] $items
  216.      * @return TreeItem[]
  217.      */
  218.     private function buildTree(?string $parentId, array $items): array
  219.     {
  220.         $children = new MenuItemCollection();
  221.         foreach ($items as $key => $item) {
  222.             if ($item->getParentId() !== $parentId) {
  223.                 continue;
  224.             }
  225.             unset($items[$key]);
  226.             $children->add($item);
  227.         }
  228.         $children->sortByPosition();
  229.         $tree = [];
  230.         foreach ($children->getIterator() as $child) {
  231.             if (!$child->getActive()) {
  232.                 continue;
  233.             }
  234.             $this->sortCategories($child->getCategoryLink());
  235.             $item = clone $this->treeItem;
  236.             $item->setItem($child);
  237.             $item->setChildren(
  238.                 $this->buildTree($child->getId(), $items)
  239.             );
  240.             $tree[$child->getId()] = $item;
  241.         }
  242.         return $tree;
  243.     }
  244. }