diff --git a/Api/Data/NodeInterface.php b/Api/Data/NodeInterface.php
index 8f10a24a..f3ca6860 100644
--- a/Api/Data/NodeInterface.php
+++ b/Api/Data/NodeInterface.php
@@ -26,6 +26,7 @@ interface NodeInterface
const ADDITIONAL_DATA = 'additional_data';
const SELECTED_ITEM_ID = 'selected_item_id';
const CUSTOMER_GROUPS = 'customer_groups';
+ const HIDE_IF_EMPTY = 'hide_if_empty';
/**
* Get node id
@@ -316,4 +317,16 @@ public function setCustomerGroups($customerGroups);
* @return bool
*/
public function isVisible($customerGroupId);
+
+
+ /**
+ * @return int
+ */
+ public function getHideIfEmpty();
+
+ /**
+ * @param int $hideIfEmpty
+ * @return $this
+ */
+ public function setHideIfEmpty($hideIfEmpty);
}
diff --git a/Block/Menu.php b/Block/Menu.php
index dd076269..06a72c5d 100644
--- a/Block/Menu.php
+++ b/Block/Menu.php
@@ -24,6 +24,7 @@
class Menu extends Template implements DataObject\IdentityInterface
{
const XML_SNOWMENU_GENERAL_CUSTOMER_GROUPS = 'snowmenu/general/customer_groups';
+ const XML_SNOWMENU_GENERAL_CACHE_TAGS = 'snowmenu/general/cache_tags';
/**
* @var MenuRepositoryInterface
@@ -84,6 +85,11 @@ class Menu extends Template implements DataObject\IdentityInterface
*/
private $httpContext;
+ /**
+ * @var array
+ */
+ private $nodeTypeCaches = [];
+
/**
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -97,6 +103,7 @@ public function __construct(
ImageFile $imageFile,
Escaper $escaper,
Context $httpContext,
+ array $nodeTypeCaches = [],
array $data = []
) {
parent::__construct($context, $data);
@@ -110,6 +117,7 @@ public function __construct(
$this->setTemplate($this->getMenuTemplate($this->_template));
$this->submenuTemplate = $this->getSubmenuTemplate();
$this->httpContext = $httpContext;
+ $this->nodeTypeCaches = $nodeTypeCaches;
}
/**
@@ -119,11 +127,22 @@ public function __construct(
*/
public function getIdentities()
{
- return [
+ $tags = [
\Snowdog\Menu\Model\Menu::CACHE_TAG . '_' . $this->loadMenu()->getId(),
Block::CACHE_TAG,
\Snowdog\Menu\Model\Menu::CACHE_TAG
];
+ if (!$this->canGatherEntityCacheTags()) {
+ return $tags;
+ }
+ $otherCacheTagsArrays = [];
+ foreach ($this->nodeTypeCaches as $provider) {
+ $entityCacheTags = $this->nodeTypeProvider->getProvider($provider)->getEntityCacheTags();
+ if (!empty($entityCacheTags)) {
+ $otherCacheTagsArrays[] = $entityCacheTags;
+ }
+ }
+ return array_merge($tags, ...$otherCacheTagsArrays);
}
protected function getCacheLifetime()
@@ -440,6 +459,9 @@ private function getSubmenuBlock($nodes, $parentNode, $level = 0)
return $block;
}
+ /**
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ */
private function fetchData()
{
$nodes = $this->nodeRepository->getByMenu($this->loadMenu()->getId());
@@ -464,16 +486,29 @@ private function fetchData()
$result[$level][$parent] = [];
}
$result[$level][$parent][] = $node;
+ $idx = array_key_last($result[$level][$parent]);
$type = $node->getType();
if (!isset($types[$type])) {
$types[$type] = [];
}
- $types[$type][] = $node;
+ $types[$type][] = [
+ 'node' => $node,
+ 'path' => [$level, $parent, $idx]
+ ];
}
$this->nodes = $result;
foreach ($types as $type => $nodes) {
- $this->nodeTypeProvider->prepareData($type, $nodes);
+ $this->nodeTypeProvider->prepareData($type, array_column($nodes, 'node'));
+ }
+
+ foreach ($types['category'] ?? [] as $nodes) {
+ $categoryProvider = $this->nodeTypeProvider->getProvider('category');
+ $productCount = $categoryProvider->getCategoryProductCount($nodes['node']->getNodeId());
+ if (empty($productCount) && $nodes['node']->getHideIfEmpty()) {
+ [$level, $parent, $idx] = $nodes['path'];
+ unset($this->nodes[$level][$parent][$idx]);
+ }
}
}
@@ -509,6 +544,15 @@ private function getSubmenuTemplate()
return $this->getMenuTemplate($baseSubmenuTemplate);
}
+ private function canGatherEntityCacheTags()
+ {
+ if (!$this->_scopeConfig->isSetFlag(self::XML_SNOWMENU_GENERAL_CACHE_TAGS)) {
+ return false;
+ }
+
+ return !empty($this->nodeTypeCaches);
+ }
+
public function getCustomerGroupId()
{
return $this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP);
diff --git a/Block/NodeType/Category.php b/Block/NodeType/Category.php
index 990c5aab..5722ecb7 100644
--- a/Block/NodeType/Category.php
+++ b/Block/NodeType/Category.php
@@ -43,6 +43,15 @@ class Category extends AbstractNode
* @var array
*/
private $categories;
+ /**
+ * @var array
+ */
+ private $cacheTags;
+
+ /**
+ * @var array
+ */
+ private $categoryProductCounts;
/**
* Category constructor.
@@ -103,7 +112,14 @@ public function fetchData(array $nodes)
{
$storeId = $this->_storeManager->getStore()->getId();
- list($this->nodes, $this->categoryUrls, $this->categories) = $this->_categoryModel->fetchData($nodes, $storeId);
+ [
+ $this->nodes,
+ $this->categoryUrls,
+ $this->categories,
+ $this->categoryProductCounts,
+ $this->cacheTags
+ ] = $this->_categoryModel->fetchData($nodes, $storeId);
+
}
/**
@@ -151,6 +167,22 @@ public function getCategoryUrl($nodeId, $storeId = null)
return false;
}
+ public function getCategoryProductCount($nodeId)
+ {
+ if (!isset($this->nodes[$nodeId])) {
+ throw new \InvalidArgumentException('Invalid node identifier specified');
+ }
+
+ $node = $this->nodes[$nodeId];
+ $categoryId = (int) $node->getContent();
+
+ if (isset($this->categoryProductCounts[$categoryId])) {
+ return $this->categoryProductCounts[$categoryId];
+ }
+
+ return 0;
+ }
+
/**
* @param int $nodeId
*
@@ -199,4 +231,9 @@ public function getLabel()
{
return __("Category");
}
+
+ public function getEntityCacheTags()
+ {
+ return $this->cacheTags;
+ }
}
diff --git a/Model/GraphQl/Resolver/DataProvider/Node.php b/Model/GraphQl/Resolver/DataProvider/Node.php
index 6c52e85e..b12c4466 100644
--- a/Model/GraphQl/Resolver/DataProvider/Node.php
+++ b/Model/GraphQl/Resolver/DataProvider/Node.php
@@ -113,7 +113,8 @@ private function convertData(NodeInterface $node): array
NodeInterface::UPDATE_TIME => $node->getUpdateTime(),
NodeInterface::ADDITIONAL_DATA => $node->getAdditionalData(),
NodeInterface::SELECTED_ITEM_ID => $node->getSelectedItemId(),
- NodeInterface::CUSTOMER_GROUPS => $node->getCustomerGroups()
+ NodeInterface::CUSTOMER_GROUPS => $node->getCustomerGroups(),
+ NodeInterface::HIDE_IF_EMPTY => $node->getHideIfEmpty(),
];
}
diff --git a/Model/Menu/Node.php b/Model/Menu/Node.php
index 4291d991..c9401adc 100644
--- a/Model/Menu/Node.php
+++ b/Model/Menu/Node.php
@@ -10,6 +10,9 @@
use Magento\Framework\Serialize\SerializerInterface;
use Snowdog\Menu\Api\Data\NodeInterface;
+/**
+ * @SuppressWarnings(PHPMD.ExcessivePublicCount)
+ */
class Node extends AbstractModel implements NodeInterface, IdentityInterface
{
const CACHE_TAG = 'snowdog_menu_node';
@@ -391,4 +394,14 @@ public function isVisible($customerGroupId)
return false;
}
+
+ public function getHideIfEmpty()
+ {
+ return (int) $this->_getData(NodeInterface::HIDE_IF_EMPTY);
+ }
+
+ public function setHideIfEmpty($hideIfEmpty)
+ {
+ return $this->setData(NodeInterface::HIDE_IF_EMPTY, (int) $hideIfEmpty);
+ }
}
diff --git a/Model/NodeType/Category.php b/Model/NodeType/Category.php
index 32b10a44..e80ab876 100644
--- a/Model/NodeType/Category.php
+++ b/Model/NodeType/Category.php
@@ -122,10 +122,12 @@ public function fetchData(array $nodes, $storeId)
$categoryUrls = $this->getResource()->fetchData($storeId, $categoryIds);
$categories = $this->getCategories($storeId, $categoryIds);
+ $categoryProductCounts = $this->getResource()->getCategoriesProductCount($categoryIds);
+ $cacheTags = preg_filter('/^/', 'cat_c_p' . '_', $categoryIds);
$this->profiler->stop(__METHOD__);
- return [$localNodes, $categoryUrls, $categories];
+ return [$localNodes, $categoryUrls, $categories, $categoryProductCounts, $cacheTags];
}
/**
@@ -136,6 +138,7 @@ public function fetchData(array $nodes, $storeId)
public function getCategories($store, array $categoryIds)
{
$return = [];
+ /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categories */
$categories = $this->categoryCollection->create()
->addAttributeToSelect('*')
->setStoreId($store)
@@ -144,6 +147,7 @@ public function getCategories($store, array $categoryIds)
['in' => $categoryIds]
);
+ /** @var \Magento\Catalog\Api\Data\CategoryInterface $category */
foreach ($categories as $category) {
$return[$category->getId()] = $category;
}
diff --git a/Model/ResourceModel/NodeType/Category.php b/Model/ResourceModel/NodeType/Category.php
index 831a0ad4..e269401c 100644
--- a/Model/ResourceModel/NodeType/Category.php
+++ b/Model/ResourceModel/NodeType/Category.php
@@ -15,6 +15,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Model\Store;
+use Zend_Db_Expr;
class Category extends AbstractNode
{
@@ -101,4 +102,22 @@ public function fetchData($storeId = Store::DEFAULT_STORE_ID, $categoryIds = [])
return $connection->fetchPairs($select);
}
+
+ /**
+ * Get products count in categories
+ *
+ * @see \Magento\Catalog\Model\ResourceModel\Category::getProductCount
+ */
+ public function getCategoriesProductCount($categoryIds = [])
+ {
+ $productTable = $this->getConnection()->getTableName('catalog_category_product');
+
+ $select = $this->getConnection()
+ ->select()
+ ->from($productTable, ['category_id', new Zend_Db_Expr('COUNT(product_id)')])
+ ->where('category_id IN (?)', $categoryIds)
+ ->group('category_id');
+
+ return $this->getConnection()->fetchPairs($select);
+ }
}
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 0cd14336..543dfb41 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -15,6 +15,11 @@
Controls serving different menus to different customer groups
Magento\Config\Model\Config\Source\Yesno
+
+
+ Controls menu caching mechanisms. Enabling this will allow for gathering cache tags from menu nodes, which allows handling menus that are heavily dependent on e.g. category contents - see https://github.com/SnowdogApps/magento2-menu/discussions/317
+ Magento\Config\Model\Config\Source\Yesno
+
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
index 21d02c1d..dd45623c 100644
--- a/etc/db_schema.xml
+++ b/etc/db_schema.xml
@@ -33,6 +33,7 @@
+
diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json
index 94d4d708..1c0b24f3 100644
--- a/etc/db_schema_whitelist.json
+++ b/etc/db_schema_whitelist.json
@@ -35,7 +35,8 @@
"selected_item_id": true,
"image_width": true,
"image_heigth": true,
- "customer_groups": true
+ "customer_groups": true,
+ "hide_if_empty": true
},
"constraint": {
"PRIMARY": true,
diff --git a/etc/di.xml b/etc/di.xml
index bf2f437f..c46b4223 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -74,4 +74,12 @@
Snowdog\Menu\Model\ImportExport\Processor\Import\Node\Validator\Proxy
+
+
+
+
+ - category
+
+
+
diff --git a/view/adminhtml/templates/menu/nodes.phtml b/view/adminhtml/templates/menu/nodes.phtml
index 2d1b2723..7e9f5b09 100644
--- a/view/adminhtml/templates/menu/nodes.phtml
+++ b/view/adminhtml/templates/menu/nodes.phtml
@@ -76,7 +76,9 @@ $vueComponents = $block->getVueComponents();
"imageHeight" : "= __('Image Height') ?>",
"selectedItemId" : "= __('Selected Item Id') ?>",
"customerGroups" : "= __('Customer Groups') ?>",
- "customerGroupsDescription" : "= __('If no customer group is selected, the node will be visible to all customers.') ?>"
+ "customerGroupsDescription" : "= __('If no customer group is selected, the node will be visible to all customers.') ?>",
+ "hideIfEmpty" : "= __("Hide node if empty") ?>",
+ "hideIfEmptyDescription" : "= __("(e.g. no product in categories)") ?>"
}
}
}
diff --git a/view/adminhtml/web/vue/app.vue b/view/adminhtml/web/vue/app.vue
index 0efeaa4c..2a7216fa 100644
--- a/view/adminhtml/web/vue/app.vue
+++ b/view/adminhtml/web/vue/app.vue
@@ -138,7 +138,8 @@
submenu_template: null,
columns: [],
is_active: 0,
- customer_groups: []
+ customer_groups: [],
+ hide_if_empty: 0
});
},
setUniqueIds(node) {