<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Catalog\Plugin\Block;
use Magento\Catalog\Model\Category;
use Magento\Framework\Data\Collection;
use Magento\Framework\Data\Tree\Node;
/**
* Plugin for top menu block
*/
class Topmenu
{
/**
* Catalog category
*
* @var \Magento\Catalog\Helper\Category
*/
protected $catalogCategory;
/**
* @var \Magento\Catalog\Model\ResourceModel\Category\StateDependentCollectionFactory
*/
private $collectionFactory;
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var \Magento\Catalog\Model\Layer\Resolver
*/
private $layerResolver;
/**
* Initialize dependencies.
*
* @param \Magento\Catalog\Helper\Category $catalogCategory
* @param \Magento\Catalog\Model\ResourceModel\Category\StateDependentCollectionFactory $categoryCollectionFactory
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
*/
public function __construct(
\Magento\Catalog\Helper\Category $catalogCategory,
\Magento\Catalog\Model\ResourceModel\Category\StateDependentCollectionFactory $categoryCollectionFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Layer\Resolver $layerResolver
) {
$this->catalogCategory = $catalogCategory;
$this->collectionFactory = $categoryCollectionFactory;
$this->storeManager = $storeManager;
$this->layerResolver = $layerResolver;
}
/**
* Build category tree for menu block.
*
* @param \Magento\Theme\Block\Html\Topmenu $subject
* @param string $outermostClass
* @param string $childrenWrapClass
* @param int $limit
* @return void
* @SuppressWarnings("PMD.UnusedFormalParameter")
*/
public function beforeGetHtml(
\Magento\Theme\Block\Html\Topmenu $subject,
$outermostClass = '',
$childrenWrapClass = '',
$limit = 0
) {
$rootId = $this->storeManager->getStore()->getRootCategoryId();
$storeId = $this->storeManager->getStore()->getId();
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */
$collection = $this->getCategoryTree($storeId, $rootId);
$currentCategory = $this->getCurrentCategory();
$mapping = [$rootId => $subject->getMenu()]; // use nodes stack to avoid recursion
foreach ($collection as $category) {
$categoryParentId = $category->getParentId();
if (!isset($mapping[$categoryParentId])) {
$parentIds = $category->getParentIds();
foreach ($parentIds as $parentId) {
if (isset($mapping[$parentId])) {
$categoryParentId = $parentId;
}
}
}
/** @var Node $parentCategoryNode */
$parentCategoryNode = $mapping[$categoryParentId];
$categoryNode = new Node(
$this->getCategoryAsArray(
$category,
$currentCategory,
$category->getParentId() == $categoryParentId
),
'id',
$parentCategoryNode->getTree(),
$parentCategoryNode
);
$parentCategoryNode->addChild($categoryNode);
$mapping[$category->getId()] = $categoryNode; //add node in stack
}
}
/**
* Add list of associated identities to the top menu block for caching purposes.
*
* @param \Magento\Theme\Block\Html\Topmenu $subject
* @return void
*/
public function beforeGetIdentities(\Magento\Theme\Block\Html\Topmenu $subject)
{
$subject->addIdentity(Category::CACHE_TAG);
$rootId = $this->storeManager->getStore()->getRootCategoryId();
$storeId = $this->storeManager->getStore()->getId();
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */
$collection = $this->getCategoryTree($storeId, $rootId);
$mapping = [$rootId => $subject->getMenu()]; // use nodes stack to avoid recursion
foreach ($collection as $category) {
if (!isset($mapping[$category->getParentId()])) {
continue;
}
$subject->addIdentity(Category::CACHE_TAG . '_' . $category->getId());
}
}
/**
* Get current Category from catalog layer
*
* @return \Magento\Catalog\Model\Category
*/
private function getCurrentCategory()
{
$catalogLayer = $this->layerResolver->get();
if (!$catalogLayer) {
return null;
}
return $catalogLayer->getCurrentCategory();
}
/**
* Convert category to array
*
* @param \Magento\Catalog\Model\Category $category
* @param \Magento\Catalog\Model\Category $currentCategory
* @param bool $isParentActive
* @return array
*/
private function getCategoryAsArray($category, $currentCategory, $isParentActive)
{
$categoryId = $category->getId();
return [
'name' => $category->getName(),
'id' => 'category-node-' . $categoryId,
'url' => $this->catalogCategory->getCategoryUrl($category),
'has_active' => in_array((string)$categoryId, explode('/', (string)$currentCategory->getPath()), true),
'is_active' => $categoryId == $currentCategory->getId(),
'is_category' => true,
'is_parent_active' => $isParentActive
];
}
/**
* Get Category Tree
*
* @param int $storeId
* @param int $rootId
* @return \Magento\Catalog\Model\ResourceModel\Category\Collection
* @throws \Magento\Framework\Exception\LocalizedException
*/
protected function getCategoryTree($storeId, $rootId)
{
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */
$collection = $this->collectionFactory->create();
$collection->setStoreId($storeId);
$collection->addAttributeToSelect('name');
$collection->addFieldToFilter('path', ['like' => '1/' . $rootId . '/%']); //load only from store root
$collection->addAttributeToFilter('include_in_menu', 1);
$collection->addIsActiveFilter();
$collection->addNavigationMaxDepthFilter();
$collection->addUrlRewriteToResult();
$collection->addOrder('level', Collection::SORT_ORDER_ASC);
$collection->addOrder('position', Collection::SORT_ORDER_ASC);
$collection->addOrder('parent_id', Collection::SORT_ORDER_ASC);
$collection->addOrder('entity_id', Collection::SORT_ORDER_ASC);
return $collection;
}
/**
* Add active
*
* @param \Magento\Theme\Block\Html\Topmenu $subject
* @param string[] $result
* @return string[]
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterGetCacheKeyInfo(\Magento\Theme\Block\Html\Topmenu $subject, array $result)
{
$activeCategory = $this->getCurrentCategory();
if ($activeCategory) {
$result[] = Category::CACHE_TAG . '_' . $activeCategory->getId();
}
return $result;
}
}
|