Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SMM-5 Dynamic menu improvements #355

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Api/Data/NodeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
50 changes: 47 additions & 3 deletions Block/Menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -84,6 +85,11 @@ class Menu extends Template implements DataObject\IdentityInterface
*/
private $httpContext;

/**
* @var array
*/
private $nodeTypeCaches = [];

/**
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
Expand All @@ -97,6 +103,7 @@ public function __construct(
ImageFile $imageFile,
Escaper $escaper,
Context $httpContext,
array $nodeTypeCaches = [],
array $data = []
) {
parent::__construct($context, $data);
Expand All @@ -110,6 +117,7 @@ public function __construct(
$this->setTemplate($this->getMenuTemplate($this->_template));
$this->submenuTemplate = $this->getSubmenuTemplate();
$this->httpContext = $httpContext;
$this->nodeTypeCaches = $nodeTypeCaches;
}

/**
Expand All @@ -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()
Expand Down Expand Up @@ -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());
Expand All @@ -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]);
}
}
}

Expand Down Expand Up @@ -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);
Expand Down
39 changes: 38 additions & 1 deletion Block/NodeType/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ class Category extends AbstractNode
* @var array
*/
private $categories;
/**
* @var array
*/
private $cacheTags;

/**
* @var array
*/
private $categoryProductCounts;

/**
* Category constructor.
Expand Down Expand Up @@ -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);

}

/**
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -199,4 +231,9 @@ public function getLabel()
{
return __("Category");
}

public function getEntityCacheTags()
{
return $this->cacheTags;
}
}
3 changes: 2 additions & 1 deletion Model/GraphQl/Resolver/DataProvider/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
];
}

Expand Down
13 changes: 13 additions & 0 deletions Model/Menu/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
}
6 changes: 5 additions & 1 deletion Model/NodeType/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

/**
Expand All @@ -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)
Expand All @@ -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;
}
Expand Down
19 changes: 19 additions & 0 deletions Model/ResourceModel/NodeType/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}
}
5 changes: 5 additions & 0 deletions etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<comment>Controls serving different menus to different customer groups</comment>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="cache_tags" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Gather node entities cache tags</label>
<comment>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</comment>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
</section>
</system>
Expand Down
1 change: 1 addition & 0 deletions etc/db_schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<column xsi:type="smallint" name="selected_item_id" padding="5" unsigned="true" nullable="true" identity="false" comment="Selected Item Id"/>
<column xsi:type="int" name="image_width" padding="11" unsigned="false" nullable="true" identity="false" comment="Image Width"/>
<column xsi:type="int" name="image_heigth" padding="11" unsigned="false" nullable="true" identity="false" comment="Image Height"/>
<column xsi:type="smallint" name="hide_if_empty" padding="6" unsigned="false" nullable="false" identity="false" default="0" comment="Hide If Empty"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="node_id"/>
</constraint>
Expand Down
3 changes: 2 additions & 1 deletion etc/db_schema_whitelist.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,12 @@
<argument name="validator" xsi:type="object">Snowdog\Menu\Model\ImportExport\Processor\Import\Node\Validator\Proxy</argument>
</arguments>
</type>

<type name="Snowdog\Menu\Block\Menu">
<arguments>
<argument name="nodeTypeCaches" xsi:type="array">
<item name="category" xsi:type="string">category</item>
</argument>
</arguments>
</type>
</config>
4 changes: 3 additions & 1 deletion view/adminhtml/templates/menu/nodes.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -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)") ?>"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion view/adminhtml/web/vue/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@
submenu_template: null,
columns: [],
is_active: 0,
customer_groups: []
customer_groups: [],
hide_if_empty: 0
});
},
setUniqueIds(node) {
Expand Down
Loading