From 40c294f14f3c2c42d5e4b6bfc10f3edbd7977ab7 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Fri, 25 Apr 2025 13:50:17 +0300 Subject: [PATCH 01/12] init --- Block/Adminhtml/Category/AbstractCategory.php | 321 +++++++++ Block/Adminhtml/Category/Tree.php | 455 ++++++++++++ .../Adminhtml/Category/CategoriesJson.php | 90 +++ Model/Category.php | 12 + Model/ResourceModel/Category/Tree.php | 655 ++++++++++++++++++ view/adminhtml/layout/blog_category_edit.xml | 2 + 6 files changed, 1535 insertions(+) create mode 100644 Block/Adminhtml/Category/AbstractCategory.php create mode 100644 Block/Adminhtml/Category/Tree.php create mode 100644 Controller/Adminhtml/Category/CategoriesJson.php create mode 100644 Model/ResourceModel/Category/Tree.php diff --git a/Block/Adminhtml/Category/AbstractCategory.php b/Block/Adminhtml/Category/AbstractCategory.php new file mode 100644 index 00000000..4b58f8c5 --- /dev/null +++ b/Block/Adminhtml/Category/AbstractCategory.php @@ -0,0 +1,321 @@ +_categoryTree = $categoryTree; + $this->_coreRegistry = $registry; + $this->_categoryFactory = $categoryFactory; + $this->collectionFactory = $collectionFactory; + $this->_withProductCount = true; + parent::__construct($context, $data); + } + + /** + * Retrieve current category instance + * + * @return array|null + */ + public function getCategory() + { + $categoryId = (int)$this->getRequest()->getParam('id'); + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + + $catRepo = $objectManager->create( \Magefan\Blog\Api\CategoryRepositoryInterface::class); + + return $catRepo->getById($categoryId); + } + + /** + * Get category id + * + * @return int|string|null + */ + public function getCategoryId() + { + if ($this->getCategory()) { + return $this->getCategory()->getId(); + } + return \Magefan\Blog\Model\Category::TREE_ROOT_ID; + } + + /** + * Get category name + * + * @return string + */ + public function getCategoryName() + { + return $this->getCategory()->getName(); + } + + /** + * Get category path + * + * @return mixed + */ + public function getCategoryPath() + { + if ($this->getCategory()) { + return $this->getCategory()->getPath(); + } + return \Magefan\Blog\Model\Category::TREE_ROOT_ID; + } + + /** + * Check store root category + * + * @return bool + */ + public function hasStoreRootCategory() + { + $root = $this->getRoot(); + if ($root && $root->getId()) { + return true; + } + return false; + } + + /** + * Get store from request + * + * @return Store + */ + public function getStore() + { + $storeId = (int)$this->getRequest()->getParam('store'); + return $this->_storeManager->getStore($storeId); + } + + /** + * Get root category for tree + * + * @param mixed|null $parentNodeCategory + * @param int $recursionLevel + * @return Node|array|null + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function getRoot($parentNodeCategory = null, $recursionLevel = 3) + { + if ($parentNodeCategory !== null && $parentNodeCategory->getId()) { + return $this->getNode($parentNodeCategory, $recursionLevel); + } + + $root = $this->_coreRegistry->registry('root'); + if ($root === null) { + $storeId = (int)$this->getRequest()->getParam('store'); + + if ($storeId) { + $store = $this->_storeManager->getStore($storeId); + $rootId = $store->getRootCategoryId(); + } else { + $rootId = \Magefan\Blog\Model\Category::TREE_ROOT_ID; + } + + $tree = $this->_categoryTree->load(null, $recursionLevel); + + if ($this->getCategory()) { + $tree->loadEnsuredNodes($this->getCategory(), $tree->getNodeById($rootId)); + } + + $tree->addCollectionData($this->getCategoryCollection()); + + $root = $tree->getNodeById($rootId); + + if ($root) { + $root->setIsVisible(true); + if ($root->getId() == \Magefan\Blog\Model\Category::TREE_ROOT_ID) { + $root->setName(__('Root')); + } + } + + + $this->_coreRegistry->register('root', $root); + } + + return $root; + } + + /** + * Get Default Store Id + * + * @return int + */ + protected function _getDefaultStoreId() + { + return \Magento\Store\Model\Store::DEFAULT_STORE_ID; + } + + /** + * Get category collection + * + * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + */ + public function getCategoryCollection() + { + $storeId = $this->getRequest()->getParam('store', $this->_getDefaultStoreId()); + $collection = $this->getData('category_collection'); + if ($collection === null) { + $collection = $this->collectionFactory->create(); + + $collection + ->addFieldToSelect('category_id') + ->addFieldToSelect('title') + ->addFieldToSelect('is_active'); + + $this->setData('category_collection', $collection); + } + return $collection; + } + + /** + * Get and register categories root by specified categories IDs + * + * IDs can be arbitrary set of any categories ids. + * Tree with minimal required nodes (all parents and neighbours) will be built. + * If ids are empty, default tree with depth = 2 will be returned. + * + * @param array $ids + * @return mixed + */ + public function getRootByIds($ids) + { + $root = $this->_coreRegistry->registry('root'); + if (null === $root) { + $ids = $this->_categoryTree->getExistingCategoryIdsBySpecifiedIds($ids); + $tree = $this->_categoryTree->loadByIds($ids); + $rootId = \Magefan\Blog\Model\Category::TREE_ROOT_ID; + $root = $tree->getNodeById($rootId); + if ($root && $rootId != \Magefan\Blog\Model\Category::TREE_ROOT_ID) { + $root->setIsVisible(true); + } elseif ($root && $root->getId() == \Magefan\Blog\Model\Category::TREE_ROOT_ID) { + $root->setName(__('Root')); + } + + $tree->addCollectionData($this->getCategoryCollection()); + $this->_coreRegistry->register('root', $root); + } + return $root; + } + + /** + * Get category node for tree + * + * @param mixed $parentNodeCategory + * @param int $recursionLevel + * @return Node + */ + public function getNode($parentNodeCategory, $recursionLevel = 2) + { + $nodeId = $parentNodeCategory->getId(); + $node = $this->_categoryTree->loadNode($nodeId); + $node->loadChildren($recursionLevel); + + if ($node && $nodeId != \Magefan\Blog\Model\Category::TREE_ROOT_ID) { + $node->setIsVisible(true); + } elseif ($node && $node->getId() == \Magefan\Blog\Model\Category::TREE_ROOT_ID) { + $node->setName(__('Root')); + } + + $this->_categoryTree->addCollectionData($this->getCategoryCollection()); + return $node; + } + + /** + * Get category save url + * + * @param array $args + * @return string + */ + public function getSaveUrl(array $args = []) + { + $params = ['_current' => false, '_query' => false, 'store' => $this->getStore()->getId()]; + $params = array_merge($params, $args); + return $this->getUrl('catalog/*/save', $params); + } + + /** + * Get category edit url + * + * @return string + */ + public function getEditUrl() + { + return $this->getUrl( + 'blog/category/edit', + ['store' => null, '_query' => false, 'id' => null, 'parent' => null] + ); + } + + /** + * Return ids of root categories as array + * + * @return array + */ + public function getRootIds() + { + $ids = $this->getData('root_ids'); + if ($ids === null) { + $ids = [\Magefan\Blog\Model\Category::TREE_ROOT_ID]; + foreach ($this->_storeManager->getGroups() as $store) { + $ids[] = $store->getRootCategoryId(); + } + $this->setData('root_ids', $ids); + } + return $ids; + } +} diff --git a/Block/Adminhtml/Category/Tree.php b/Block/Adminhtml/Category/Tree.php new file mode 100644 index 00000000..4a7400b9 --- /dev/null +++ b/Block/Adminhtml/Category/Tree.php @@ -0,0 +1,455 @@ +_jsonEncoder = $jsonEncoder; + $this->_resourceHelper = $resourceHelper; + $this->_backendSession = $backendSession; + parent::__construct($context, $categoryTree, $registry, $categoryFactory, $collectionFactory, $data); + $this->secureRenderer = $secureRenderer ?? ObjectManager::getInstance()->get(SecureHtmlRenderer::class); + } + + /** + * @inheritdoc + */ + protected function _construct() + { + parent::_construct(); + $this->setUseAjax(0); + } + + /** + * @inheritdoc + */ + protected function _prepareLayout() + { + $addUrl = $this->getUrl("*/*/add", ['_current' => false, 'id' => null, '_query' => false]); + if ($this->getStore()->getId() == Store::DEFAULT_STORE_ID) { + $this->addChild( + 'add_sub_button', + Button::class, + [ + 'label' => __('Add Subcategory'), + 'onclick' => "addNew('" . $addUrl . "', false)", + 'class' => 'add', + 'id' => 'add_subcategory_button', + 'style' => $this->canAddSubCategory() ? '' : 'display: none;' + ] + ); + + if ($this->canAddRootCategory()) { + $this->addChild( + 'add_root_button', + Button::class, + [ + 'label' => __('Add Root Category'), + 'onclick' => "addNew('" . $addUrl . "', true)", + 'class' => 'add', + 'id' => 'add_root_category_button' + ] + ); + } + } + + return parent::_prepareLayout(); + } + + + + /** + * Get add root button html + * + * @return string + */ + public function getAddRootButtonHtml() + { + return $this->getChildHtml('add_root_button'); + } + + /** + * Get add sub button html + * + * @return string + */ + public function getAddSubButtonHtml() + { + return $this->getChildHtml('add_sub_button'); + } + + /** + * Get expand button html + * + * @return string + */ + public function getExpandButtonHtml() + { + return $this->getChildHtml('expand_button'); + } + + /** + * Get collapse button html + * + * @return string + */ + public function getCollapseButtonHtml() + { + return $this->getChildHtml('collapse_button'); + } + + /** + * Get store switcher + * + * @return string + */ + public function getStoreSwitcherHtml() + { + return $this->getChildHtml('store_switcher'); + } + + /** + * Get loader tree url + * + * @param bool|null $expanded + * @return string + */ + public function getLoadTreeUrl($expanded = null) + { + $params = ['_current' => true, 'id' => null, 'store' => null]; + if ($expanded === null && $this->_backendSession->getIsTreeWasExpanded() || $expanded == true) { + $params['expand_all'] = true; + } + return $this->getUrl('*/*/categoriesJson', $params); + } + + /** + * Get nodes url + * + * @return string + */ + public function getNodesUrl() + { + return $this->getUrl('blog/category/tree'); + } + + /** + * Get switcher tree url + * + * @return string + */ + public function getSwitchTreeUrl() + { + return $this->getUrl( + 'blog/category/tree', + ['_current' => true, 'store' => null, '_query' => false, 'id' => null, 'parent' => null] + ); + } + + /** + * Get is was expanded + * + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getIsWasExpanded() + { + return $this->_backendSession->getIsTreeWasExpanded(); + } + + /** + * Get move url + * + * @return string + */ + public function getMoveUrl() + { + return $this->getUrl('blog/category/move', ['store' => $this->getRequest()->getParam('store')]); + } + + /** + * Get tree + * + * @param mixed|null $parenNodeCategory + * @return array + */ + public function getTree($parenNodeCategory = null) + { + $rootArray = $this->_getNodeJson($this->getRoot($parenNodeCategory)); + $tree = $rootArray['children'] ?? []; + + return $tree; + } + + /** + * Get tree json + * + * @param mixed|null $parenNodeCategory + * @return string + */ + public function getTreeJson($parenNodeCategory = null) + { + $rootArray = $this->_getNodeJson($this->getRoot($parenNodeCategory)); + $json = $this->_jsonEncoder->encode($rootArray['children'] ?? []); + + return $json; + } + + /** + * Get JSON of array of categories, that are breadcrumbs for specified category path + * + * @param string $path + * @param string $javascriptVarName + * @return string + */ + public function getBreadcrumbsJavascript($path, $javascriptVarName) + { + if (empty($path)) { + return ''; + } + + $categories = $this->_categoryTree->setStoreId($this->getStore()->getId())->loadBreadcrumbsArray($path); + if (empty($categories)) { + return ''; + } + foreach ($categories as $key => $category) { + $categories[$key] = $this->_getNodeJson($category); + } + $scriptString = 'require(["prototype"], function(){' . $javascriptVarName . ' = ' . $this->_jsonEncoder->encode( + $categories + ) . + ';' . + ($this->canAddSubCategory() ? '$("add_subcategory_button").show();' : '$("add_subcategory_button").hide();') + . '});'; + + return /* @noEscape */ $this->secureRenderer->renderTag('script', [], $scriptString, false); + } + + /** + * Get JSON of a tree node or an associative array + * + * @param Node|array $node + * @param int $level + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function _getNodeJson($node, $level = 0) + { + /* echo PHP_EOL. '_getNodeJson'; + echo 'TITLE: ' . $node->getTitle() . PHP_EOL; + echo 'LVL: ' . $level . PHP_EOL; + echo ' hasChildren: ' . ( $node->hasChildren() ? 'yes' : 'no' ) . PHP_EOL; +*/ + + // create a node from data array + if (is_array($node)) { + $node = new Node($node, 'entity_id', new \Magento\Framework\Data\Tree()); + } + + $item = []; + $item['text'] = $this->buildNodeName($node); + + $rootForStores = in_array($node->getEntityId(), $this->getRootIds()); + + $item['id'] = $node->getId(); + $item['store'] = (int)$this->getStore()->getId(); + $item['path'] = $node->getData('path'); + + $item['cls'] = 'folder ' . ($node->getIsActive() ? 'active-category' : 'no-active-category'); + //$item['allowDrop'] = ($level<3) ? true : false; + $allowMove = $this->_isCategoryMoveable($node); + $item['allowDrop'] = $allowMove; + // disallow drag if it's first level and category is root of a store + + $item['allowDrag'] = $allowMove && ($node->getLevel() == 1 && $rootForStores ? false : true); + + if ((int)$node->getChildrenCount() > 0) { + $item['children'] = []; + } + + $isParent = $this->_isParentSelectedCategory($node); + + if ($node->hasChildren()) { + $item['children'] = []; + if (!($this->getUseAjax() && $node->getLevel() > 1 && !$isParent)) { + //echo 'COUNT getChildren: ' . count($node->getChildren()) . PHP_EOL; + + foreach ($node->getChildren() as $child) { + $item['children'][] = $this->_getNodeJson($child, $level + 1); + } + } + } + + if ($isParent || $node->getLevel() < 1) { + $item['expanded'] = true; + } + + return $item; + } + + /** + * Get category name + * + * @param DataObject $node + * @return string + */ + public function buildNodeName($node) + { + $result = $this->escapeHtml($node->getTitle()); + $result .= ' (ID: ' . $node->getId() . ')'; + + if ($this->_withProductCount) { + $result .= ' (' . $node->getPostCount() . ')'; + } + return $result; + } + + /** + * Is category movable + * + * @param Node|array $node + * @return bool + */ + protected function _isCategoryMoveable($node) + { + $options = new DataObject(['is_moveable' => true, 'category' => $node]); + + $this->_eventManager->dispatch('adminhtml_blog_category_tree_is_moveable', ['options' => $options]); + + return $options->getIsMoveable(); + } + + /** + * Is parent selected category + * + * @param Node|array $node + * @return bool + */ + protected function _isParentSelectedCategory($node) + { + if ($node && $this->getCategory()) { + $pathIds = $this->getCategory()->getPathIds(); + if (in_array($node->getId(), $pathIds)) { + return true; + } + } + + return false; + } + + /** + * Check if page loaded by outside link to category edit + * + * @return boolean + */ + public function isClearEdit() + { + return (bool)$this->getRequest()->getParam('clear'); + } + + /** + * Check availability of adding root category + * + * @return boolean + */ + public function canAddRootCategory() + { + $options = new DataObject(['is_allow' => true]); + $this->_eventManager->dispatch( + 'adminhtml_blog_category_tree_can_add_root_category', + ['category' => $this->getCategory(), 'options' => $options, 'store' => $this->getStore()->getId()] + ); + + return $options->getIsAllow(); + } + + /** + * Check availability of adding sub category + * + * @return boolean + */ + public function canAddSubCategory() + { + $options = new DataObject(['is_allow' => true]); + $this->_eventManager->dispatch( + 'adminhtml_blog_category_tree_can_add_sub_category', + ['category' => $this->getCategory(), 'options' => $options, 'store' => $this->getStore()->getId()] + ); + + return $options->getIsAllow(); + } +} diff --git a/Controller/Adminhtml/Category/CategoriesJson.php b/Controller/Adminhtml/Category/CategoriesJson.php new file mode 100644 index 00000000..96e8e050 --- /dev/null +++ b/Controller/Adminhtml/Category/CategoriesJson.php @@ -0,0 +1,90 @@ +resultJsonFactory = $resultJsonFactory; + $this->layoutFactory = $layoutFactory; + $this->authSession = $authSession ?: ObjectManager::getInstance() + ->get(\Magento\Backend\Model\Auth\Session::class); + } + + /** + * Get tree node (Ajax version) + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + if ($this->getRequest()->getParam('expand_all')) { + $this->authSession->setIsTreeWasExpanded(true); + } else { + $this->authSession->setIsTreeWasExpanded(false); + } + $categoryId = (int)$this->getRequest()->getPost('id'); + if ($categoryId) { + $this->getRequest()->setParam('id', $categoryId); + + $category = $this->_getModel(); + if (!$category) { + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + return $resultRedirect->setPath('catalog/*/', ['_current' => true, 'id' => null]); + } + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setJsonData( + $this->layoutFactory->create()->createBlock(\Magefan\Blog\Block\Adminhtml\Category\Tree::class) + ->getTreeJson($category) + ); + } + } +} diff --git a/Model/Category.php b/Model/Category.php index aa27f07d..9f9d0cc6 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -46,6 +46,9 @@ class Category extends \Magento\Framework\Model\AbstractModel implements Identit const STATUS_ENABLED = 1; const STATUS_DISABLED = 0; + + const TREE_ROOT_ID = 1; + /** * Prefix of model events names * @@ -341,6 +344,15 @@ public function getLevel() return count($this->getParentIds()); } + + /** + * @return array + */ + public function getPathIds() + { + return $this->getParentIds(); + } + /** * Retrieve catgegory url route path * @return string diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php new file mode 100644 index 00000000..5a06af74 --- /dev/null +++ b/Model/ResourceModel/Category/Tree.php @@ -0,0 +1,655 @@ +_blogCategory = $blogCategory; + $this->_cache = $cache; + $this->_storeManager = $storeManager; + $this->_coreResource = $resource; + parent::__construct( + $resource->getConnection(), + $resource->getTableName('magefan_blog_category'), + [ + Dbp::ID_FIELD => 'category_id', + Dbp::PATH_FIELD => 'path', + Dbp::ORDER_FIELD => 'position', + Dbp::LEVEL_FIELD => 'category_id' + ] + ); + $this->_eventManager = $eventManager; + $this->_collectionFactory = $collectionFactory; + } + + /** + * Set store id + * + * @param integer $storeId + * @return \Magefan\Blog\Model\ResourceModel\Category\Tree + */ + public function setStoreId($storeId) + { + $this->_storeId = (int)$storeId; + return $this; + } + + /** + * Return store id + * + * @return integer + */ + public function getStoreId() + { + if ($this->_storeId === null) { + $this->_storeId = $this->_storeManager->getStore()->getId(); + } + return $this->_storeId; + } + + /** + * Add data to collection + * + * @param Collection $collection + * @param boolean $sorted + * @param array $exclude + * @param boolean $toLoad + * @param boolean $onlyActive + * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function addCollectionData( + $collection = null, + $sorted = false, + $exclude = [], + $toLoad = true, + $onlyActive = false + ) { + if ($collection === null) { + $collection = $this->getCollection($sorted); + } else { + $this->setCollection($collection); + } + + if (!is_array($exclude)) { + $exclude = [$exclude]; + } + + $nodeIds = []; + foreach ($this->getNodes() as $node) { + if (!in_array($node->getId(), $exclude)) { + $nodeIds[] = $node->getId(); + } + } + + $collection->addFieldToFilter('category_id', ['in' => $nodeIds]); + + + if ($onlyActive) { + $disabledIds = $this->_getDisabledIds($collection, $nodeIds); + if ($disabledIds) { + $collection->addFieldToFilter('category_id', ['nin' => $disabledIds]); + } + $collection->addAttributeToFilter('is_active', 1); + $collection->addAttributeToFilter('include_in_menu', 1); + } + + if ($this->_joinUrlRewriteIntoCollection) { + $collection->joinUrlRewrite(); + $this->_joinUrlRewriteIntoCollection = false; + } + + if ($toLoad) { + $collection->load(); + + foreach ($collection as $category) { + if ($this->getNodeById($category->getId())) { + $this->getNodeById($category->getId())->addData($category->getData()); + } + } + + foreach ($this->getNodes() as $node) { + if (!$collection->getItemById($node->getId()) && $node->getParent()) { + $this->removeNode($node); + } + } + } + + return $this; + } + + /** + * Add inactive categories ids + * + * @param mixed $ids + * @return $this + */ + public function addInactiveCategoryIds($ids) + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds); + return $this; + } + + /** + * Retrieve inactive categories ids + * + * @return $this + */ + protected function _initInactiveCategoryIds() + { + $this->_inactiveCategoryIds = []; + $this->_eventManager->dispatch('blog_category_tree_init_inactive_category_ids', ['tree' => $this]); + return $this; + } + + /** + * Retrieve inactive categories ids + * + * @return array + */ + public function getInactiveCategoryIds() + { + if (!is_array($this->_inactiveCategoryIds)) { + $this->_initInactiveCategoryIds(); + } + + return $this->_inactiveCategoryIds; + } + + /** + * Return disable category ids + * + * @param Collection $collection + * @param array $allIds + * @return array + */ + protected function _getDisabledIds($collection, $allIds) + { + $storeId = $this->_storeManager->getStore()->getId(); + $this->_inactiveItems = $this->getInactiveCategoryIds(); + $this->_inactiveItems = array_merge($this->_getInactiveItemIds($collection, $storeId), $this->_inactiveItems); + + $disabledIds = []; + + foreach ($allIds as $id) { + $parents = $this->getNodeById($id)->getPath(); + foreach ($parents as $parent) { + if (!$this->_getItemIsActive($parent->getId())) { + $disabledIds[] = $id; + continue; + } + } + } + return $disabledIds; + } + + /** + * Retrieve inactive category item ids + * + * @param Collection $collection + * @param int $storeId + * @return array + */ + protected function _getInactiveItemIds($collection, $storeId) + { + return [9,10]; + } + + /** + * Check is category items active + * + * @param int $id + * @return boolean + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + protected function _getItemIsActive($id) + { + if (!in_array($id, $this->_inactiveItems)) { + return true; + } + return false; + } + + /** + * Get categories collection + * + * @param boolean $sorted + * @return Collection + */ + public function getCollection($sorted = false) + { + if ($this->_collection === null) { + $this->_collection = $this->_getDefaultCollection($sorted); + } + return $this->_collection; + } + + /** + * Clean unneeded collection + * + * @param Collection|array $object + * @return void + */ + protected function _clean($object) + { + if (is_array($object)) { + foreach ($object as $obj) { + $this->_clean($obj); + } + } + unset($object); + } + + /** + * Enter description here... + * + * @param Collection $collection + * @return $this + */ + public function setCollection($collection) + { + if ($this->_collection !== null) { + $this->_clean($this->_collection); + } + $this->_collection = $collection; + return $this; + } + + /** + * Enter description here... + * + * @param boolean $sorted + * @return Collection + */ + protected function _getDefaultCollection($sorted = false) + { + $this->_joinUrlRewriteIntoCollection = true; + $collection = $this->_collectionFactory->create(); + $collection->addAttributeToSelect('title'); + + if ($sorted) { + if (is_string($sorted)) { + // $sorted is supposed to be attribute name + $collection->addAttributeToSort($sorted); + } else { + $collection->addAttributeToSort('name'); + } + } + + return $collection; + } + + /** + * Executing parents move method and cleaning cache after it + * + * @param mixed $category + * @param mixed $newParent + * @param mixed $prevNode + * @return void + */ + public function move($category, $newParent, $prevNode = null) + { + $this->_blogCategory->move($category->getId(), $newParent->getId()); + parent::move($category, $newParent, $prevNode); + + $this->_afterMove(); + } + + /** + * Move tree after + * + * @return $this + */ + protected function _afterMove() + { + $this->_cache->clean([\Magefan\Blog\Model\Category::CACHE_TAG]); + return $this; + } + + /** + * Load whole category tree, that will include specified categories ids. + * + * @param array $ids + * @param bool $addCollectionData + * @param bool $updateAnchorProductCount + * @return $this|bool + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function loadByIds($ids, $addCollectionData = true, $updateAnchorProductCount = true) + { + $levelField = $this->_conn->quoteIdentifier('level'); + $pathField = $this->_conn->quoteIdentifier('path'); + // load first two levels, if no ids specified + if (empty($ids)) { + $select = $this->_conn->select()->from($this->_table, 'category_id')->where($levelField . ' <= 2'); + $ids = $this->_conn->fetchCol($select); + } + if (!is_array($ids)) { + $ids = [$ids]; + } + foreach ($ids as $key => $id) { + $ids[$key] = (int)$id; + } + + // collect paths of specified IDs and prepare to collect all their parents and neighbours + $select = $this->_conn->select()->from($this->_table, ['path', 'level'])->where('category_id IN (?)', $ids); + $where = [$levelField . '=0' => true]; + + foreach ($this->_conn->fetchAll($select) as $item) { + $pathIds = explode('/', $item['path'] ?? ''); + $level = (int)$item['level']; + while ($level > 0) { + $pathIds[count($pathIds) - 1] = '%'; + $path = implode('/', $pathIds); + $where["{$levelField}={$level} AND {$pathField} LIKE '{$path}'"] = true; + array_pop($pathIds); + $level--; + } + } + $where = array_keys($where); + + // get all required records + if ($addCollectionData) { + $select = $this->_createCollectionDataSelect(); + } else { + $select = clone $this->_select; + $select->order($this->_orderField . ' ' . \Magento\Framework\DB\Select::SQL_ASC); + } + $select->where(implode(' OR ', $where)); + + // get array of records and add them as nodes to the tree + $arrNodes = $this->_conn->fetchAll($select); + if (!$arrNodes) { + return false; + } + if ($updateAnchorProductCount) { + $this->_updateAnchorProductCount($arrNodes); + } + $childrenItems = []; + foreach ($arrNodes as $key => $nodeInfo) { + $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); + array_pop($pathToParent); + $pathToParent = implode('/', $pathToParent); + $childrenItems[$pathToParent][] = $nodeInfo; + } + $this->addChildNodes($childrenItems, '', null); + return $this; + } + + /** + * Load array of category parents + * + * @param string $path + * @param bool $addCollectionData + * @param bool $withRootNode + * @return array + */ + public function loadBreadcrumbsArray($path, $addCollectionData = true, $withRootNode = false) + { + $pathIds = explode('/', $path ?: ''); + if (!$withRootNode) { + array_shift($pathIds); + } + $result = []; + if (!empty($pathIds)) { + if ($addCollectionData) { + $select = $this->_createCollectionDataSelect(false); + } else { + $select = clone $this->_select; + } + $select->where( + 'e.category_id IN(?)', + $pathIds + )->order( + $this->_conn->getLengthSql('e.path') . ' ' . \Magento\Framework\DB\Select::SQL_ASC + ); + $result = $this->_conn->fetchAll($select); + $this->_updateAnchorProductCount($result); + } + return $result; + } + + /** + * Replace products count with self products count, if category is non-anchor + * + * @param array &$data + * @return void + */ + protected function _updateAnchorProductCount(&$data) + { + foreach ($data as $key => $row) { + if (0 === (int)$row['is_anchor']) { + $data[$key]['product_count'] = $row['self_product_count']; + } + } + } + + /** + * Obtain select for categories with attributes. + * By default everything from entity table is selected + * + name, is_active and is_anchor + * Also the correct product_count is selected, depending on is the category anchor or not. + * + * @param bool $sorted + * @param array $optionalAttributes + * @return \Magento\Framework\DB\Select + */ + protected function _createCollectionDataSelect($sorted = true, $optionalAttributes = []) + { + $meta = $this->getMetadataPool()->getMetadata(CategoryManagementInterface::class); + $linkField = $meta->getLinkField(); + + $select = $this->_getDefaultCollection($sorted ? $this->_orderField : false)->getSelect(); + // add attributes to select + $attributes = ['name', 'is_active', 'is_anchor']; + if ($optionalAttributes) { + $attributes = array_unique(array_merge($attributes, $optionalAttributes)); + } + $resource = $this->_blogCategory; + foreach ($attributes as $attributeCode) { + /* @var $attribute \Magento\Eav\Model\Entity\Attribute */ + $attribute = $resource->getAttribute($attributeCode); + // join non-static attribute table + if (!$attribute->getBackend()->isStatic()) { + $tableDefault = sprintf('d_%s', $attributeCode); + $tableStore = sprintf('s_%s', $attributeCode); + $valueExpr = $this->_conn->getCheckSql( + "{$tableStore}.value_id > 0", + "{$tableStore}.value", + "{$tableDefault}.value" + ); + + $select->joinLeft( + [$tableDefault => $attribute->getBackend()->getTable()], + sprintf( + '%1$s.' . $linkField . '=e.' . $linkField . + ' AND %1$s.attribute_id=%2$d AND %1$s.store_id=%3$d', + $tableDefault, + $attribute->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ), + [$attributeCode => 'value'] + )->joinLeft( + [$tableStore => $attribute->getBackend()->getTable()], + sprintf( + '%1$s.' . $linkField . '=e.' . $linkField . + ' AND %1$s.attribute_id=%2$d AND %1$s.store_id=%3$d', + $tableStore, + $attribute->getId(), + $this->getStoreId() + ), + [$attributeCode => $valueExpr] + ); + } + } + + // count children products qty plus self products qty + $categoriesTable = $this->_coreResource->getTableName('magefan_blog_category'); + $categoriesProductsTable = $this->_coreResource->getTableName('magefan_blog_post'); + + $subConcat = $this->_conn->getConcatSql(['e.path', $this->_conn->quote('/%')]); + $subSelect = $this->_conn->select()->from( + ['see' => $categoriesTable], + null + )->joinLeft( + ['scp' => $categoriesProductsTable], + 'see.category_id=scp.category_id', + ['COUNT(DISTINCT scp.product_id)'] + )->where( + 'see.category_id = e.category_id' + )->orWhere( + 'see.path LIKE ?', + $subConcat + ); + $select->columns(['product_count' => $subSelect]); + + $subSelect = $this->_conn->select()->from( + ['cp' => $categoriesProductsTable], + 'COUNT(cp.product_id)' + )->where( + 'cp.category_id = e.category_id' + ); + + $select->columns(['self_product_count' => $subSelect]); + + return $select; + } + + /** + * Get real existing category ids by specified ids + * + * @param array $ids + * @return array + */ + public function getExistingCategoryIdsBySpecifiedIds($ids) + { + if (empty($ids)) { + return []; + } + if (!is_array($ids)) { + $ids = [$ids]; + } + $select = $this->_conn->select()->from($this->_table, ['category_id'])->where('category_id IN (?)', $ids); + return $this->_conn->fetchCol($select); + } + + /** + * Get entity methadata pool. + * + * @return \Magento\Framework\EntityManager\MetadataPool + */ + private function getMetadataPool() + { + if (null === $this->metadataPool) { + $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\MetadataPool::class); + } + return $this->metadataPool; + } +} diff --git a/view/adminhtml/layout/blog_category_edit.xml b/view/adminhtml/layout/blog_category_edit.xml index 417cce40..f7e5a46f 100644 --- a/view/adminhtml/layout/blog_category_edit.xml +++ b/view/adminhtml/layout/blog_category_edit.xml @@ -13,6 +13,8 @@ + + From 5653001eacad4b63eb0204e532f67e4984991233 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Tue, 6 May 2025 16:43:52 +0300 Subject: [PATCH 02/12] move category logic --- Controller/Adminhtml/Category/Move.php | 117 +++++++++++++++++++ Model/Category.php | 55 +++++++++ Model/ResourceModel/Category.php | 101 ++++++++++++++++ Model/ResourceModel/Category/Tree.php | 19 ++- etc/db_schema.xml | 2 + view/adminhtml/layout/blog_category_edit.xml | 1 + view/adminhtml/templates/category/edit.phtml | 25 ++++ 7 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 Controller/Adminhtml/Category/Move.php create mode 100644 view/adminhtml/templates/category/edit.phtml diff --git a/Controller/Adminhtml/Category/Move.php b/Controller/Adminhtml/Category/Move.php new file mode 100644 index 00000000..374fb91f --- /dev/null +++ b/Controller/Adminhtml/Category/Move.php @@ -0,0 +1,117 @@ +resultJsonFactory = $resultJsonFactory; + $this->layoutFactory = $layoutFactory; + $this->logger = $logger; + } + + /** + * Move category action + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + /** + * New parent category identifier + */ + $parentNodeId = $this->getRequest()->getPost('pid', false); + /** + * Category id after which we have put our category + */ + $prevNodeId = $this->getRequest()->getPost('aid', false); + + /** @var $block \Magento\Framework\View\Element\Messages */ + $block = $this->layoutFactory->create()->getMessagesBlock(); + $error = false; + + try { + $categoryId = (int)$this->getRequest()->getPost('id'); + + if ($categoryId) { + $this->getRequest()->setParam('id', $categoryId); + + $category = $this->_getModel(); + + if ($category === false) { + throw new \Exception(__('Category is not available for requested store.')); + } + + $category->move($parentNodeId, $prevNodeId); + } + + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $error = true; + $this->messageManager->addExceptionMessage($e); + } catch (\Exception $e) { + $error = true; + $this->messageManager->addErrorMessage(__('There was a category move error.')); + $this->logger->critical($e); + } + + if (!$error) { + $this->messageManager->addSuccessMessage(__('You moved the category.')); + } + + $block->setMessages($this->messageManager->getMessages(true)); + $resultJson = $this->resultJsonFactory->create(); + + return $resultJson->setData([ + 'messages' => $block->getGroupedHtml(), + 'error' => $error + ]); + } +} diff --git a/Model/Category.php b/Model/Category.php index 9f9d0cc6..ea9b1db0 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -11,6 +11,7 @@ use Magefan\Blog\Model\Url; use Magento\Framework\DataObject\IdentityInterface; use Magefan\Blog\Api\ShortContentExtractorInterface; +use Magento\Framework\Exception\NoSuchEntityException; /** * Category model @@ -604,4 +605,58 @@ public function getCategoryImage() return $this->getData('category_image'); } + + /** + * Move category + * + * @param int $parentId new parent category id + * @param null|int $afterCategoryId category id after which we have put current category + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException|\Exception + */ + public function move($parentId, $afterCategoryId) + { + try { + $parent = $this->loadFromRepository($parentId); + } catch (NoSuchEntityException $e) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Sorry, but we can\'t find the new parent category you selected.'), $e + ); + } + + if (!$this->getId()) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Sorry, but we can\'t find the new category you selected.') + ); + } elseif ($parent->getId() == $this->getId()) { + throw new \Magento\Framework\Exception\LocalizedException( + __('We can\'t move the category because the parent category name matches the child category name.') + ); + } + + $eventParams = [ + $this->_eventObject => $this, + 'parent' => $parent, + 'category_id' => $this->getId(), + 'prev_parent_id' => $this->getParentId(), + 'parent_id' => $parentId + ]; + + $this->_getResource()->beginTransaction(); + try { + $this->_eventManager->dispatch($this->_eventPrefix . '_move_before', $eventParams); + $this->getResource()->changeParent($this, $parent, $afterCategoryId); + $this->_eventManager->dispatch($this->_eventPrefix . '_move_after', $eventParams); + $this->_getResource()->commit(); + + } catch (\Exception $e) { + $this->_getResource()->rollBack(); + throw $e; + } + $this->_eventManager->dispatch('blog_category_move', $eventParams); + $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); + $this->_cacheManager->clean([self::CACHE_TAG]); + + return $this; + } } diff --git a/Model/ResourceModel/Category.php b/Model/ResourceModel/Category.php index 68f184fc..b6fe6a26 100755 --- a/Model/ResourceModel/Category.php +++ b/Model/ResourceModel/Category.php @@ -292,4 +292,105 @@ public function getEntityType() { return 'category'; } + + /** + * Move category to another parent node + * + * @param \Magefan\Blog\Model\Category $category + * @param \Magefan\Blog\Model\Category $newParent + * @param null|int $afterCategoryId + * @return $this + */ + public function changeParent( + \Magefan\Blog\Model\Category $category, + \Magefan\Blog\Model\Category $newParent, + $afterCategoryId = null + ) { + $table = $this->getMainTable(); + $connection = $this->getConnection(); + $levelField = $connection->quoteIdentifier('level'); + $pathField = $connection->quoteIdentifier('path'); + + $position = $this->_processPositions($category, $newParent, $afterCategoryId); + + $newPath = sprintf('%s/%s', $newParent->getPath(), $category->getId()); + $newLevel = $newParent->getLevel() + 1; + $levelDisposition = $newLevel - $category->getLevel(); + + /** + * Update children nodes path + */ + $connection->update( + $table, + [ + 'path' => new \Zend_Db_Expr( + 'REPLACE(' . $pathField . ',' . $connection->quote( + $category->getPath() . '/' + ) . ', ' . $connection->quote( + $newPath . '/' + ) . ')' + ), + 'level' => new \Zend_Db_Expr($levelField . ' + ' . $levelDisposition) + ], + [$pathField . ' LIKE ?' => $category->getPath() . '/%'] + ); + /** + * Update moved category data + */ + $data = [ + 'path' => $newPath, + 'level' => $newLevel, + 'position' => $position, + 'parent_id' => $newParent->getId(), + ]; + $connection->update($table, $data, ['category_id = ?' => $category->getId()]); + + // Update category object to new data + $category->addData($data); + $category->unsetData('path_ids'); + + return $this; + } + + + /** + * Process positions of old parent category children and new parent category children. + * + * Get position for moved category + * + * @param \Magefan\Blog\Model\Category $category + * @param \Magefan\Blog\Model\Category $newParent + * @param null|int $afterCategoryId + * @return int + */ + protected function _processPositions($category, $newParent, $afterCategoryId) + { + $table = $this->getMainTable(); + $connection = $this->getConnection(); + $positionField = $connection->quoteIdentifier('position'); + + $bind = ['position' => new \Zend_Db_Expr($positionField . ' - 1')]; + $where = [ + 'parent_id = ?' => $category->getParentId(), + $positionField . ' > ?' => $category->getPosition(), + ]; + $connection->update($table, $bind, $where); + + /** + * Prepare position value + */ + if ($afterCategoryId) { + $select = $connection->select()->from($table, 'position')->where('category_id = :category_id'); + $position = $connection->fetchOne($select, ['category_id' => $afterCategoryId]); + $position = (int)$position + 1; + } else { + $position = 1; + } + + $bind = ['position' => new \Zend_Db_Expr($positionField . ' + 1')]; + $where = ['parent_id = ?' => $newParent->getId(), $positionField . ' >= ?' => $position]; + $connection->update($table, $bind, $where); + + return $position; + } } diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index 5a06af74..ad0177bb 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -116,7 +116,7 @@ public function __construct( Dbp::ID_FIELD => 'category_id', Dbp::PATH_FIELD => 'path', Dbp::ORDER_FIELD => 'position', - Dbp::LEVEL_FIELD => 'category_id' + Dbp::LEVEL_FIELD => 'level' ] ); $this->_eventManager = $eventManager; @@ -192,8 +192,8 @@ public function addCollectionData( if ($disabledIds) { $collection->addFieldToFilter('category_id', ['nin' => $disabledIds]); } - $collection->addAttributeToFilter('is_active', 1); - $collection->addAttributeToFilter('include_in_menu', 1); + $collection->addFieldToFilter('is_active', 1); + $collection->addFieldToFilter('include_in_menu', 1); } if ($this->_joinUrlRewriteIntoCollection) { @@ -297,7 +297,18 @@ protected function _getDisabledIds($collection, $allIds) */ protected function _getInactiveItemIds($collection, $storeId) { - return [9,10]; + $idsSelect = clone $collection->getSelect(); + + $idsSelect->reset(\Magento\Framework\DB\Select::ORDER); + $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT); + $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET); + $idsSelect->reset(\Magento\Framework\DB\Select::COLUMNS); + $idsSelect->reset(\Magento\Framework\DB\Select::GROUP); + $idsSelect->columns('category_id'); + // $idsSelect->where('is_active = ?', 0); + /////////////////////// $storeId + + return $this->_conn->fetchCol($idsSelect); } /** diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 28bb63c9..0bf0d142 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -102,6 +102,7 @@ + @@ -112,6 +113,7 @@ + diff --git a/view/adminhtml/layout/blog_category_edit.xml b/view/adminhtml/layout/blog_category_edit.xml index f7e5a46f..d7381c28 100644 --- a/view/adminhtml/layout/blog_category_edit.xml +++ b/view/adminhtml/layout/blog_category_edit.xml @@ -14,6 +14,7 @@ + diff --git a/view/adminhtml/templates/category/edit.phtml b/view/adminhtml/templates/category/edit.phtml new file mode 100644 index 00000000..9235d630 --- /dev/null +++ b/view/adminhtml/templates/category/edit.phtml @@ -0,0 +1,25 @@ + +
+
+
escapeHtml(__('This operation can take a long time')) ?>
+
+
+renderStyleAsTag('display: none;', 'div[data-id="information-dialog-category"]') ?> + + From 157904bd03cf94b37227fd64f71780369883fd56 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Tue, 6 May 2025 18:03:19 +0300 Subject: [PATCH 03/12] use 999999999 as root id --- Block/Adminhtml/Category/Tree.php | 2 +- Model/Category.php | 2 +- .../ui_component/blog_category_form.xml | 33 ------------------- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/Block/Adminhtml/Category/Tree.php b/Block/Adminhtml/Category/Tree.php index 4a7400b9..2d7a189d 100644 --- a/Block/Adminhtml/Category/Tree.php +++ b/Block/Adminhtml/Category/Tree.php @@ -98,7 +98,7 @@ protected function _construct() */ protected function _prepareLayout() { - $addUrl = $this->getUrl("*/*/add", ['_current' => false, 'id' => null, '_query' => false]); + $addUrl = $this->getUrl("*/*/new", ['_current' => false, 'id' => null, '_query' => false]); if ($this->getStore()->getId() == Store::DEFAULT_STORE_ID) { $this->addChild( 'add_sub_button', diff --git a/Model/Category.php b/Model/Category.php index ea9b1db0..571ea3dc 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -48,7 +48,7 @@ class Category extends \Magento\Framework\Model\AbstractModel implements Identit const STATUS_DISABLED = 0; - const TREE_ROOT_ID = 1; + const TREE_ROOT_ID = 999999999; /** * Prefix of model events names diff --git a/view/adminhtml/ui_component/blog_category_form.xml b/view/adminhtml/ui_component/blog_category_form.xml index 5f6887a2..520d1b50 100644 --- a/view/adminhtml/ui_component/blog_category_form.xml +++ b/view/adminhtml/ui_component/blog_category_form.xml @@ -97,39 +97,6 @@ - - - - Magefan\Blog\Model\Config\Source\CategoryPath - - Parent Category - field - select - Magento_Catalog/js/components/new-category - ui/grid/filters/elements/ui-select - path - true - true - true - true - false - 1 - 30 - false - - - - - - - text - Position - input - category - 40 - position - - From c074ef23dfccf05ce99c4f935ed0793f6f439f40 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Fri, 9 May 2025 17:52:46 +0300 Subject: [PATCH 04/12] upd git add .! --- Block/Adminhtml/Category/AbstractCategory.php | 10 ++- .../System/Config/Form/UpdateInfo.php | 2 +- Block/Post/PostList/Toolbar.php | 2 +- Block/Post/View/Comments/Magefan/Comment.php | 2 +- .../Adminhtml/Category/CategoriesJson.php | 2 +- Controller/Adminhtml/Category/Save.php | 49 ++++++++++++ Controller/Category/View.php | 2 +- Controller/Router.php | 4 +- Controller/Tag/View.php | 2 +- Model/Author.php | 4 +- Model/AuthorRepository.php | 2 +- Model/Category.php | 2 +- Model/CategoryRepository.php | 2 +- Model/Comment.php | 4 +- Model/CommentRepository.php | 2 +- Model/Import/AbstractImport.php | 8 +- Model/Post.php | 10 +-- Model/PostRepository.php | 2 +- Model/ResourceModel/Category.php | 78 ++++++++++++++++--- Model/ResourceModel/Category/Collection.php | 2 +- .../ResourceModel/Comment/Collection/Grid.php | 4 +- Model/ResourceModel/Post/Collection.php | 4 +- Model/ShortContentExtractor.php | 2 +- Model/Tag.php | 4 +- Model/TagRepository.php | 2 +- Model/UrlResolver.php | 2 +- ...thFrontendBlogActionControllerObserver.php | 2 +- .../Category/Form/CategoryDataProvider.php | 36 +++++++-- .../Comment/Form/CommentDataProvider.php | 2 +- etc/db_schema.xml | 2 +- .../ui_component/blog_category_form.xml | 34 ++++++++ 31 files changed, 227 insertions(+), 58 deletions(-) diff --git a/Block/Adminhtml/Category/AbstractCategory.php b/Block/Adminhtml/Category/AbstractCategory.php index 4b58f8c5..7260639b 100644 --- a/Block/Adminhtml/Category/AbstractCategory.php +++ b/Block/Adminhtml/Category/AbstractCategory.php @@ -73,11 +73,15 @@ public function __construct( public function getCategory() { $categoryId = (int)$this->getRequest()->getParam('id'); - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - $catRepo = $objectManager->create( \Magefan\Blog\Api\CategoryRepositoryInterface::class); + if ($categoryId) { + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - return $catRepo->getById($categoryId); + $catRepo = $objectManager->create( \Magefan\Blog\Api\CategoryRepositoryInterface::class); + return $catRepo->getById($categoryId); + } + + return null; } /** diff --git a/Block/Adminhtml/System/Config/Form/UpdateInfo.php b/Block/Adminhtml/System/Config/Form/UpdateInfo.php index 48b9da4b..336ec8f9 100644 --- a/Block/Adminhtml/System/Config/Form/UpdateInfo.php +++ b/Block/Adminhtml/System/Config/Form/UpdateInfo.php @@ -61,7 +61,7 @@ public function __construct( \Magento\Framework\HTTP\Client\Curl $curl, \Magento\Framework\Json\Helper\Data $jsonHelper, array $data = [], - GetModuleVersionInterface $getModuleVersion = null + ?GetModuleVersionInterface $getModuleVersion = null ) { $this->cacheManager = $context->getCache(); $this->jsonHelper = $jsonHelper; diff --git a/Block/Post/PostList/Toolbar.php b/Block/Post/PostList/Toolbar.php index ef064447..92310213 100755 --- a/Block/Post/PostList/Toolbar.php +++ b/Block/Post/PostList/Toolbar.php @@ -29,7 +29,7 @@ class Toolbar extends \Magento\Framework\View\Element\Template public function __construct( Context $context, array $data = [], - Config $config = null + ?Config $config = null ) { parent::__construct($context, $data); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); diff --git a/Block/Post/View/Comments/Magefan/Comment.php b/Block/Post/View/Comments/Magefan/Comment.php index ac1e98fe..c77041b4 100755 --- a/Block/Post/View/Comments/Magefan/Comment.php +++ b/Block/Post/View/Comments/Magefan/Comment.php @@ -35,7 +35,7 @@ class Comment extends Template implements IdentityInterface public function __construct( Template\Context $context, array $data = [], - TimezoneInterface $timezone = null + ?TimezoneInterface $timezone = null ) { $this->timezone = $timezone ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(TimezoneInterface::class); diff --git a/Controller/Adminhtml/Category/CategoriesJson.php b/Controller/Adminhtml/Category/CategoriesJson.php index 96e8e050..ccd29972 100644 --- a/Controller/Adminhtml/Category/CategoriesJson.php +++ b/Controller/Adminhtml/Category/CategoriesJson.php @@ -48,7 +48,7 @@ public function __construct( DataPersistorInterface $dataPersistor, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, - \Magento\Backend\Model\Auth\Session $authSession = null + ?\Magento\Backend\Model\Auth\Session $authSession = null ) { parent::__construct($context, $dataPersistor); $this->resultJsonFactory = $resultJsonFactory; diff --git a/Controller/Adminhtml/Category/Save.php b/Controller/Adminhtml/Category/Save.php index 9caa17fb..15e06d8a 100755 --- a/Controller/Adminhtml/Category/Save.php +++ b/Controller/Adminhtml/Category/Save.php @@ -36,6 +36,30 @@ protected function _afterSave($model, $request) protected function _beforeSave($model, $request) { + $categoryPostData = $request->getPostValue(); + + $isNewCategory = empty($categoryPostData['category_id']); + $parentId = $categoryPostData['parent'] ?? null; + + if ($parentId) { + $model->setParentId($parentId); + } + + if ($isNewCategory) { + $storeId = 0; + $parentCategory = $this->getParentCategory($parentId, $storeId); + + $path = $parentCategory->getPath(); + + if ($parentId) { + $path .= '/' . $parentId; + } + + $model->setPath($path); + $model->setParentId($parentCategory->getId()); + $model->setLevel(null); + } + /* Prepare images */ $this->prepareImagesBeforeSave($model, ['category_img']); } @@ -67,4 +91,29 @@ protected function filterParams($data) return $data; } + + + /** + * Get parent category + * + * @param int $parentId + * @param int $storeId + * + * @return \Magefan\Blog\Model\Category + */ + protected function getParentCategory($parentId, $storeId) + { + if (!$parentId) { + if ($storeId) { + //$parentId = $this->storeManager->getStore($storeId)->getRootCategoryId(); + } else { + $parentId = \Magento\Catalog\Model\Category::TREE_ROOT_ID; + return $this->_objectManager->create(\Magefan\Blog\Model\Category::class) + ->setId($parentId) + ->setPath(''); + } + } + + return $this->_objectManager->create(\Magefan\Blog\Model\Category::class)->load($parentId); + } } diff --git a/Controller/Category/View.php b/Controller/Category/View.php index d83c8f3b..fae9e4b0 100755 --- a/Controller/Category/View.php +++ b/Controller/Category/View.php @@ -33,7 +33,7 @@ class View extends \Magefan\Blog\App\Action\Action public function __construct( \Magento\Framework\App\Action\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magefan\Blog\Model\Url $url = null + ?\Magefan\Blog\Model\Url $url = null ) { parent::__construct($context); $this->_storeManager = $storeManager; diff --git a/Controller/Router.php b/Controller/Router.php index 3fdc1d4b..44870edc 100755 --- a/Controller/Router.php +++ b/Controller/Router.php @@ -123,8 +123,8 @@ public function __construct( \Magefan\Blog\Model\TagFactory $tagFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\ResponseInterface $response, - UrlResolverInterface $urlResolver = null, - Config $config = null + ?UrlResolverInterface $urlResolver = null, + ?Config $config = null ) { $this->actionFactory = $actionFactory; diff --git a/Controller/Tag/View.php b/Controller/Tag/View.php index b3252551..b63136dd 100755 --- a/Controller/Tag/View.php +++ b/Controller/Tag/View.php @@ -29,7 +29,7 @@ class View extends \Magefan\Blog\App\Action\Action public function __construct( Context $context, - \Magefan\Blog\Model\Url $url = null + ?\Magefan\Blog\Model\Url $url = null ) { parent::__construct($context); $this->url = $url ?: $this->_objectManager->get(\Magefan\Blog\Model\Url::class); diff --git a/Model/Author.php b/Model/Author.php index d4133a99..a070e189 100755 --- a/Model/Author.php +++ b/Model/Author.php @@ -51,8 +51,8 @@ public function __construct( Url $url, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); diff --git a/Model/AuthorRepository.php b/Model/AuthorRepository.php index e6d79ce3..c6790243 100644 --- a/Model/AuthorRepository.php +++ b/Model/AuthorRepository.php @@ -68,7 +68,7 @@ public function __construct( AuthorResourceModel $authorResourceModel, AuthorCollectionInterfaceFactory $collectionFactory, SearchResultsFactory $searchResultsFactory, - CollectionProcessorInterface $collectionProcessor = null + ?CollectionProcessorInterface $collectionProcessor = null ) { $this->authorFactory = $authorFactory; $this->authorResourceModel = $authorResourceModel; diff --git a/Model/Category.php b/Model/Category.php index 571ea3dc..e5ca4d52 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -48,7 +48,7 @@ class Category extends \Magento\Framework\Model\AbstractModel implements Identit const STATUS_DISABLED = 0; - const TREE_ROOT_ID = 999999999; + const TREE_ROOT_ID = 0; /** * Prefix of model events names diff --git a/Model/CategoryRepository.php b/Model/CategoryRepository.php index 44e32ba7..37d6d603 100644 --- a/Model/CategoryRepository.php +++ b/Model/CategoryRepository.php @@ -67,7 +67,7 @@ public function __construct( CategoryResourceModel $categoryResourceModel, CollectionFactory $collectionFactory, SearchResultsFactory $searchResultsFactory, - CollectionProcessorInterface $collectionProcessor = null + ?CollectionProcessorInterface $collectionProcessor = null ) { $this->categoryFactory = $categoryFactory; $this->categoryResourceModel = $categoryResourceModel; diff --git a/Model/Comment.php b/Model/Comment.php index da06dc01..f680692a 100755 --- a/Model/Comment.php +++ b/Model/Comment.php @@ -94,8 +94,8 @@ public function __construct( \Magento\Customer\Model\CustomerFactory $customerFactory, \Magefan\Blog\Api\AuthorInterfaceFactory $userFactory, \Magefan\Blog\Model\ResourceModel\Comment\CollectionFactory $commentCollectionFactory, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); diff --git a/Model/CommentRepository.php b/Model/CommentRepository.php index 9a8aa454..10c89aa2 100644 --- a/Model/CommentRepository.php +++ b/Model/CommentRepository.php @@ -62,7 +62,7 @@ public function __construct( CommentResourceModel $commentResourceModel, CollectionFactory $collectionFactory, SearchResultsFactory $searchResultsFactory, - CollectionProcessorInterface $collectionProcessor = null + ?CollectionProcessorInterface $collectionProcessor = null ) { $this->commentFactory = $commentFactory; $this->commentResourceModel = $commentResourceModel; diff --git a/Model/Import/AbstractImport.php b/Model/Import/AbstractImport.php index ec1c30dd..b581af4d 100644 --- a/Model/Import/AbstractImport.php +++ b/Model/Import/AbstractImport.php @@ -138,8 +138,8 @@ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\Filesystem $filesystem, \Magento\Framework\Filesystem\Io\File $file, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], $authorFactory = null, $productRepository = null @@ -275,7 +275,7 @@ protected function getDbAdapter() } catch (\Exception $e) { throw new \Exception("Failed connect to the database."); } - + } return $this->dbAdapter; } @@ -302,7 +302,7 @@ protected function getFeaturedImgBySrc($src) if (!$hasFormat) { $imageName .= '.jpg'; } - + $imagePath = $mediaPath . '/' . $imageName; $imageSource = false; if (!$this->file->fileExists($imagePath)) { diff --git a/Model/Post.php b/Model/Post.php index e0082e70..d89c368c 100755 --- a/Model/Post.php +++ b/Model/Post.php @@ -199,12 +199,12 @@ public function __construct( \Magefan\Blog\Model\ResourceModel\Tag\CollectionFactory $tagCollectionFactory, \Magefan\Blog\Model\ResourceModel\Comment\CollectionFactory $commentCollectionFactory, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - \Magefan\Blog\Api\AuthorRepositoryInterface $authorRepository = null, - \Magefan\Blog\Api\CategoryRepositoryInterface $categoryRepository = null, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone = null + ?\Magefan\Blog\Api\AuthorRepositoryInterface $authorRepository = null, + ?\Magefan\Blog\Api\CategoryRepositoryInterface $categoryRepository = null, + ?\Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone = null ) { parent::__construct($context, $registry, $resource, $resourceCollection, $data); diff --git a/Model/PostRepository.php b/Model/PostRepository.php index 71cb694b..22c8727f 100644 --- a/Model/PostRepository.php +++ b/Model/PostRepository.php @@ -62,7 +62,7 @@ public function __construct( PostResourceModel $postResourceModel, CollectionFactory $collectionFactory, SearchResultsFactory $searchResultsFactory, - CollectionProcessorInterface $collectionProcessor = null + ?CollectionProcessorInterface $collectionProcessor = null ) { $this->postFactory = $postFactory; $this->postResourceModel = $postResourceModel; diff --git a/Model/ResourceModel/Category.php b/Model/ResourceModel/Category.php index b6fe6a26..7874b971 100755 --- a/Model/ResourceModel/Category.php +++ b/Model/ResourceModel/Category.php @@ -33,7 +33,7 @@ class Category extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, \Magento\Framework\Stdlib\DateTime $dateTime, - $resourcePrefix = null + $resourcePrefix = null ) { parent::__construct($context, $resourcePrefix); $this->dateTime = $dateTime; @@ -81,7 +81,7 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) } $identifierGenerator = \Magento\Framework\App\ObjectManager::getInstance() - ->create(\Magefan\Blog\Model\ResourceModel\PageIdentifierGenerator::class); + ->create(\Magefan\Blog\Model\ResourceModel\PageIdentifierGenerator::class); $identifierGenerator->generate($object); if (!$this->isValidPageIdentifier($object)) { @@ -103,6 +103,31 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) ); } + if ($object->isObjectNew()) { + + if ($object->getPosition() === null) { + $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); + } + + $path = explode('/', (string)$object->getPath()); + $level = count($path)+1 - ($object->getId() ? 1 : 0); + $toUpdateChild = array_diff($path, [$object->getId()]); + + if (!$object->hasPosition()) { + $object->setPosition($this->_getMaxPosition(implode('/', $toUpdateChild)) + 1); + } + if (!$object->hasLevel()) { + $object->setLevel($level); + } + +/* + var_dump($object->getData('level')); + var_dump($object->getData('position')); + var_dump($object->getData('path')); + exit();*/ + } + + return parent::_beforeSave($object); } @@ -293,6 +318,34 @@ public function getEntityType() return 'category'; } + /** + * Get maximum position of child categories by specific tree path + * + * @param string $path + * @return int + */ + protected function _getMaxPosition($path) + { + $connection = $this->getConnection(); + $positionField = $connection->quoteIdentifier('position'); + $level = count(explode('/', (string)$path)); + $bind = ['c_level' => $level, 'c_path' => $path . '/%']; + $select = $connection->select()->from( + $this->getTable($this->getMainTable()), + 'MAX(' . $positionField . ')' + )->where( + $connection->quoteIdentifier('path') . ' LIKE :c_path' + )->where( + $connection->quoteIdentifier('level') . ' = :c_level' + ); + + $position = $connection->fetchOne($select, $bind); + if (!$position) { + $position = 0; + } + return $position; + } + /** * Move category to another parent node * @@ -304,7 +357,7 @@ public function getEntityType() public function changeParent( \Magefan\Blog\Model\Category $category, \Magefan\Blog\Model\Category $newParent, - $afterCategoryId = null + $afterCategoryId = null ) { $table = $this->getMainTable(); $connection = $this->getConnection(); @@ -313,8 +366,9 @@ public function changeParent( $position = $this->_processPositions($category, $newParent, $afterCategoryId); - $newPath = sprintf('%s/%s', $newParent->getPath(), $category->getId()); - $newLevel = $newParent->getLevel() + 1; + $newPath = sprintf('%s/%s', $newParent->getPath(), $newParent->getId()); + + $newLevel = $newParent->getLevel() + 2; $levelDisposition = $newLevel - $category->getLevel(); /** @@ -341,7 +395,7 @@ public function changeParent( 'path' => $newPath, 'level' => $newLevel, 'position' => $position, - 'parent_id' => $newParent->getId(), + // 'parent_id' => $newParent->getId(), ]; $connection->update($table, $data, ['category_id = ?' => $category->getId()]); @@ -352,7 +406,6 @@ public function changeParent( return $this; } - /** * Process positions of old parent category children and new parent category children. * @@ -371,7 +424,7 @@ protected function _processPositions($category, $newParent, $afterCategoryId) $bind = ['position' => new \Zend_Db_Expr($positionField . ' - 1')]; $where = [ - 'parent_id = ?' => $category->getParentId(), + 'path LIKE ?' => "%\\{$category->getParentId()}", $positionField . ' > ?' => $category->getPosition(), ]; $connection->update($table, $bind, $where); @@ -388,9 +441,16 @@ protected function _processPositions($category, $newParent, $afterCategoryId) } $bind = ['position' => new \Zend_Db_Expr($positionField . ' + 1')]; - $where = ['parent_id = ?' => $newParent->getId(), $positionField . ' >= ?' => $position]; + $where = [ + // 'parent_id = ?' => $newParent->getId(), + 'path LIKE ?' => "%\\{$newParent->getId()}", + $positionField . ' >= ?' => $position + ]; $connection->update($table, $bind, $where); return $position; } + + + } diff --git a/Model/ResourceModel/Category/Collection.php b/Model/ResourceModel/Category/Collection.php index 42a7000a..3b5300dd 100755 --- a/Model/ResourceModel/Category/Collection.php +++ b/Model/ResourceModel/Category/Collection.php @@ -59,7 +59,7 @@ public function __construct( \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Store\Model\StoreManagerInterface $storeManager, $connection = null, - \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null + ?\Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null ) { parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); $this->_storeManager = $storeManager; diff --git a/Model/ResourceModel/Comment/Collection/Grid.php b/Model/ResourceModel/Comment/Collection/Grid.php index 5ef84c93..e17c1181 100755 --- a/Model/ResourceModel/Comment/Collection/Grid.php +++ b/Model/ResourceModel/Comment/Collection/Grid.php @@ -21,7 +21,7 @@ class Grid extends Collection * @var int */ protected $_storeId; - + /** * @var bool */ @@ -43,7 +43,7 @@ public function __construct( \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Store\Model\StoreManagerInterface $storeManager, $connection = null, - \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null + ?\Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null ) { parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); $this->_storeManager = $storeManager; diff --git a/Model/ResourceModel/Post/Collection.php b/Model/ResourceModel/Post/Collection.php index dd4ecd00..8f8e7e1e 100755 --- a/Model/ResourceModel/Post/Collection.php +++ b/Model/ResourceModel/Post/Collection.php @@ -82,8 +82,8 @@ public function __construct( \Magento\Framework\Stdlib\DateTime\DateTime $date, \Magento\Store\Model\StoreManagerInterface $storeManager, $connection = null, - \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null, - \Magefan\Blog\Api\CategoryRepositoryInterface $categoryRepository = null + ?\Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null, + ?\Magefan\Blog\Api\CategoryRepositoryInterface $categoryRepository = null ) { parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); $this->_date = $date; diff --git a/Model/ShortContentExtractor.php b/Model/ShortContentExtractor.php index 2ba3c64f..c6a68770 100644 --- a/Model/ShortContentExtractor.php +++ b/Model/ShortContentExtractor.php @@ -33,7 +33,7 @@ class ShortContentExtractor implements ShortContentExtractorInterface */ public function __construct( \Magento\Cms\Model\Template\FilterProvider $filterProvider, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null + ?\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null ) { $this->filterProvider = $filterProvider; $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance() diff --git a/Model/Tag.php b/Model/Tag.php index c1d8f602..a4bf9cc6 100755 --- a/Model/Tag.php +++ b/Model/Tag.php @@ -77,8 +77,8 @@ public function __construct( \Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, Url $url, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { $this->_url = $url; diff --git a/Model/TagRepository.php b/Model/TagRepository.php index 1c333b34..d298dea9 100644 --- a/Model/TagRepository.php +++ b/Model/TagRepository.php @@ -62,7 +62,7 @@ public function __construct( TagResourceModel $tagResourceModel, CollectionFactory $collectionFactory, SearchResultsFactory $searchResultsFactory, - CollectionProcessorInterface $collectionProcessor = null + ?CollectionProcessorInterface $collectionProcessor = null ) { $this->tagFactory = $tagFactory; $this->tagResourceModel = $tagResourceModel; diff --git a/Model/UrlResolver.php b/Model/UrlResolver.php index 318977ff..5bdd59a0 100644 --- a/Model/UrlResolver.php +++ b/Model/UrlResolver.php @@ -78,7 +78,7 @@ public function __construct( AuthorInterfaceFactory $authorFactory, TagFactory $tagFactory, StoreManagerInterface $storeManager, - Config $config = null + ?Config $config = null ) { $this->url = $url; $this->postFactory = $postFactory; diff --git a/Observer/PredispathFrontendBlogActionControllerObserver.php b/Observer/PredispathFrontendBlogActionControllerObserver.php index 94ef5bda..7cd3157e 100644 --- a/Observer/PredispathFrontendBlogActionControllerObserver.php +++ b/Observer/PredispathFrontendBlogActionControllerObserver.php @@ -40,7 +40,7 @@ class PredispathFrontendBlogActionControllerObserver implements ObserverInterfac public function __construct( ScopeConfigInterface $scopeConfig, NoSlashUrlRedirect $noSlashUrlRedirect, - SlashUrlRedirect $slashUrlRedirect = null + ?SlashUrlRedirect $slashUrlRedirect = null ) { $this->scopeConfig = $scopeConfig; $this->noSlashUrlRedirect = $noSlashUrlRedirect; diff --git a/Ui/DataProvider/Category/Form/CategoryDataProvider.php b/Ui/DataProvider/Category/Form/CategoryDataProvider.php index 276b5978..783cc6f0 100644 --- a/Ui/DataProvider/Category/Form/CategoryDataProvider.php +++ b/Ui/DataProvider/Category/Form/CategoryDataProvider.php @@ -9,6 +9,7 @@ use Magefan\Blog\Model\ResourceModel\Category\CollectionFactory; use Magento\Framework\App\Request\DataPersistorInterface; +use Magento\Framework\App\RequestInterface; /** * Class DataProvider @@ -31,25 +32,33 @@ class CategoryDataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider protected $loadedData; /** - * @param string $name - * @param string $primaryFieldName - * @param string $requestFieldName + * @var RequestInterface + */ + protected $request; + + /** + * @param $name + * @param $primaryFieldName + * @param $requestFieldName * @param CollectionFactory $categoryCollectionFactory * @param DataPersistorInterface $dataPersistor + * @param RequestInterface $request * @param array $meta * @param array $data */ public function __construct( - $name, - $primaryFieldName, - $requestFieldName, + string $name, + string $primaryFieldName, + string $requestFieldName, CollectionFactory $categoryCollectionFactory, DataPersistorInterface $dataPersistor, + RequestInterface $request, array $meta = [], array $data = [] ) { $this->collection = $categoryCollectionFactory->create(); $this->dataPersistor = $dataPersistor; + $this->request = $request; parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->meta = $this->prepareMeta($this->meta); } @@ -62,7 +71,20 @@ public function __construct( */ public function prepareMeta(array $meta) { - return $meta; + $parent =(int)$this->request->getParam('parent'); + + $meta['general']['children']['parent'] = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'default' => $parent + ], + ], + ] + ]; + + + return $meta; } /** diff --git a/Ui/DataProvider/Comment/Form/CommentDataProvider.php b/Ui/DataProvider/Comment/Form/CommentDataProvider.php index 6889035e..5a707543 100755 --- a/Ui/DataProvider/Comment/Form/CommentDataProvider.php +++ b/Ui/DataProvider/Comment/Form/CommentDataProvider.php @@ -61,7 +61,7 @@ public function __construct( \Magento\Framework\UrlInterface $url, array $meta = [], array $data = [], - \Magento\Framework\Escaper $escaper = null + ?\Magento\Framework\Escaper $escaper = null ) { $this->collection = $commentCollectionFactory->create(); $this->dataPersistor = $dataPersistor; diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 0bf0d142..035b4ebb 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -102,7 +102,7 @@
- + diff --git a/view/adminhtml/ui_component/blog_category_form.xml b/view/adminhtml/ui_component/blog_category_form.xml index 520d1b50..1b49cc71 100644 --- a/view/adminhtml/ui_component/blog_category_form.xml +++ b/view/adminhtml/ui_component/blog_category_form.xml @@ -65,6 +65,40 @@ + + + + + + + category + field + + + + text + + + + + + category + + + + text + + + + + + category + + + + + + From 338b1f5f93c9235b6ded4c5cd19854dabb7c4bdf Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Mon, 12 May 2025 17:23:57 +0300 Subject: [PATCH 05/12] upd --- Model/Category.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Model/Category.php b/Model/Category.php index e5ca4d52..574938da 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -106,8 +106,8 @@ public function __construct( \Magento\Framework\Registry $registry, Url $url, \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + ?\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + ?\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { $this->_url = $url; From 0da29dbf4b7c942ea0c675c47af27f2e2fd97f9f Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Mon, 12 May 2025 17:47:35 +0300 Subject: [PATCH 06/12] overide loads methods --- Model/ResourceModel/Category/Tree.php | 151 +++++++++++++++++++++++--- 1 file changed, 137 insertions(+), 14 deletions(-) diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index ad0177bb..ea5f39cb 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -5,23 +5,12 @@ use Magefan\Blog\Model\ResourceModel\Category\Collection; use Magento\Framework\Data\Tree\Dbp; use Magefan\Blog\Api\Data\CategoryManagementInterface; +use Magento\Framework\Data\Tree\Node; use Magento\Framework\EntityManager\MetadataPool; -/** - * @api - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @since 100.0.2 - */ + class Tree extends Dbp { - public const ID_FIELD = 'id'; - - public const PATH_FIELD = 'path'; - - public const ORDER_FIELD = 'order'; - - public const LEVEL_FIELD = 'level'; - /** * @var array */ @@ -33,7 +22,6 @@ class Tree extends Dbp private $_eventManager; - /** * @var \Magefan\Blog\Model\ResourceModel\Category\CollectionFactory */ @@ -123,6 +111,141 @@ public function __construct( $this->_collectionFactory = $collectionFactory; } + + /** + * Load tree + * + * @param int|Node|string $parentNode + * @param int $recursionLevel + * @return $this + */ + public function load($parentNode = null, $recursionLevel = 0) + { + if (!$this->_loaded) { + $startLevel = 1; + $parentPath = ''; + + if ($parentNode instanceof Node) { + $parentPath = $parentNode->getData($this->_pathField); + $startLevel = $parentNode->getData($this->_levelField); + } elseif (is_numeric($parentNode)) { + $select = $this->_conn->select() + ->from($this->_table, [$this->_pathField, $this->_levelField]) + ->where("{$this->_idField} = ?", $parentNode); + $parent = $this->_conn->fetchRow($select); + + $startLevel = $parent[$this->_levelField]; + $parentPath = $parent[$this->_pathField]; + $parentNode = null; + } elseif (is_string($parentNode)) { + $parentPath = $parentNode; + $startLevel = count(explode(',', $parentPath)) - 1; + $parentNode = null; + } + + $select = clone $this->_select; + + $select->order($this->_table . '.' . $this->_orderField . ' ASC'); + if ($parentPath) { + $pathField = $this->_conn->quoteIdentifier([$this->_table, $this->_pathField]); + $select->where("{$pathField} LIKE ?", "{$parentPath}/%"); + } + if ($recursionLevel != 0) { + $levelField = $this->_conn->quoteIdentifier([$this->_table, $this->_levelField]); + $select->where("{$levelField} <= ?", $startLevel + $recursionLevel); + } + + $arrNodes = $this->_conn->fetchAll($select); + + $childrenItems = []; + + $dataRoot = [ + 'category_id' => 0 + ]; + + array_unshift($arrNodes, $dataRoot); + + foreach ($arrNodes as $nodeInfo) { + if (!empty($nodeInfo['category_id'])) { + if (empty($nodeInfo['path'])) { + $nodeInfo['path'] = $nodeInfo['category_id']; + } else { + $nodeInfo['path'] .= '/'. $nodeInfo['category_id']; + } + } + + $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); + array_pop($pathToParent); + $pathToParent = implode('/', $pathToParent); + + if (isset($nodeInfo['level']) && $pathToParent == '') { + $pathToParent = '0'; + } + + $childrenItems[$pathToParent][] = $nodeInfo; + } + + $this->addChildNodes($childrenItems, $parentPath, $parentNode); + + $this->_loaded = true; + } + + return $this; + } + + + /** + * Load ensured nodes + * + * @param object $category + * @param Node $rootNode + * @return void + */ + public function loadEnsuredNodes($category, $rootNode) + { + $pathIds = $category->getPathIds(); + $rootNodeId = $rootNode->getId(); + $rootNodePath = $rootNode->getData($this->_pathField); + + $select = clone $this->_select; + $select->order($this->_table . '.' . $this->_orderField . ' ASC'); + + if ($pathIds) { + $condition = $this->_conn->quoteInto("{$this->_table}.{$this->_idField} in (?)", $pathIds); + $select->where($condition); + } + + $arrNodes = $this->_conn->fetchAll($select); + + if ($arrNodes) { + $childrenItems = []; + foreach ($arrNodes as $nodeInfo) { + + if (!empty($nodeInfo['category_id'])) { + if (empty($nodeInfo['path'])) { + $nodeInfo['path'] = $nodeInfo['category_id']; + } else { + $nodeInfo['path'] .= '/'. $nodeInfo['category_id']; + } + } + + $nodeId = $nodeInfo[$this->_idField]; + if ($nodeId <= $rootNodeId) { + continue; + } + + $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); + array_pop($pathToParent); + $pathToParent = implode('/', $pathToParent); + $childrenItems[$pathToParent][] = $nodeInfo; + } + + $this->_addChildNodes($childrenItems, $rootNodePath, $rootNode, true); + } + } + + + /** * Set store id * From 5fe5b7140ddcbf99fdbd2ef833e5846446f112d9 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Mon, 12 May 2025 18:23:47 +0300 Subject: [PATCH 07/12] remove unrequired methods 1 --- Block/Adminhtml/Category/AbstractCategory.php | 69 +-- Block/Adminhtml/Category/Tree.php | 30 -- Model/ResourceModel/Category/Tree.php | 433 +----------------- 3 files changed, 24 insertions(+), 508 deletions(-) diff --git a/Block/Adminhtml/Category/AbstractCategory.php b/Block/Adminhtml/Category/AbstractCategory.php index 7260639b..6b221f6f 100644 --- a/Block/Adminhtml/Category/AbstractCategory.php +++ b/Block/Adminhtml/Category/AbstractCategory.php @@ -18,6 +18,9 @@ */ class AbstractCategory extends \Magento\Backend\Block\Template { + + protected $currentCategory = null; + /** * Core registry * @@ -66,22 +69,22 @@ public function __construct( } /** - * Retrieve current category instance - * - * @return array|null + * @return mixed|null */ public function getCategory() { - $categoryId = (int)$this->getRequest()->getParam('id'); + if (null === $this->currentCategory) { + $categoryId = (int)$this->getRequest()->getParam('id'); - if ($categoryId) { - $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + if ($categoryId) { + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - $catRepo = $objectManager->create( \Magefan\Blog\Api\CategoryRepositoryInterface::class); - return $catRepo->getById($categoryId); + $catRepo = $objectManager->create( \Magefan\Blog\Api\CategoryRepositoryInterface::class); + $this->currentCategory = $catRepo->getById($categoryId); + } } - return null; + return $this->currentCategory; } /** @@ -94,6 +97,7 @@ public function getCategoryId() if ($this->getCategory()) { return $this->getCategory()->getId(); } + return \Magefan\Blog\Model\Category::TREE_ROOT_ID; } @@ -117,6 +121,7 @@ public function getCategoryPath() if ($this->getCategory()) { return $this->getCategory()->getPath(); } + return \Magefan\Blog\Model\Category::TREE_ROOT_ID; } @@ -161,14 +166,15 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3) $root = $this->_coreRegistry->registry('root'); if ($root === null) { - $storeId = (int)$this->getRequest()->getParam('store'); + /*$storeId = (int)$this->getRequest()->getParam('store'); if ($storeId) { $store = $this->_storeManager->getStore($storeId); $rootId = $store->getRootCategoryId(); } else { $rootId = \Magefan\Blog\Model\Category::TREE_ROOT_ID; - } + }*/ + $rootId = \Magefan\Blog\Model\Category::TREE_ROOT_ID; $tree = $this->_categoryTree->load(null, $recursionLevel); @@ -187,7 +193,6 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3) } } - $this->_coreRegistry->register('root', $root); } @@ -226,36 +231,6 @@ public function getCategoryCollection() return $collection; } - /** - * Get and register categories root by specified categories IDs - * - * IDs can be arbitrary set of any categories ids. - * Tree with minimal required nodes (all parents and neighbours) will be built. - * If ids are empty, default tree with depth = 2 will be returned. - * - * @param array $ids - * @return mixed - */ - public function getRootByIds($ids) - { - $root = $this->_coreRegistry->registry('root'); - if (null === $root) { - $ids = $this->_categoryTree->getExistingCategoryIdsBySpecifiedIds($ids); - $tree = $this->_categoryTree->loadByIds($ids); - $rootId = \Magefan\Blog\Model\Category::TREE_ROOT_ID; - $root = $tree->getNodeById($rootId); - if ($root && $rootId != \Magefan\Blog\Model\Category::TREE_ROOT_ID) { - $root->setIsVisible(true); - } elseif ($root && $root->getId() == \Magefan\Blog\Model\Category::TREE_ROOT_ID) { - $root->setName(__('Root')); - } - - $tree->addCollectionData($this->getCategoryCollection()); - $this->_coreRegistry->register('root', $root); - } - return $root; - } - /** * Get category node for tree * @@ -312,14 +287,6 @@ public function getEditUrl() */ public function getRootIds() { - $ids = $this->getData('root_ids'); - if ($ids === null) { - $ids = [\Magefan\Blog\Model\Category::TREE_ROOT_ID]; - foreach ($this->_storeManager->getGroups() as $store) { - $ids[] = $store->getRootCategoryId(); - } - $this->setData('root_ids', $ids); - } - return $ids; + return [\Magefan\Blog\Model\Category::TREE_ROOT_ID]; } } diff --git a/Block/Adminhtml/Category/Tree.php b/Block/Adminhtml/Category/Tree.php index 2d7a189d..4e8aded0 100644 --- a/Block/Adminhtml/Category/Tree.php +++ b/Block/Adminhtml/Category/Tree.php @@ -268,36 +268,6 @@ public function getTreeJson($parenNodeCategory = null) return $json; } - /** - * Get JSON of array of categories, that are breadcrumbs for specified category path - * - * @param string $path - * @param string $javascriptVarName - * @return string - */ - public function getBreadcrumbsJavascript($path, $javascriptVarName) - { - if (empty($path)) { - return ''; - } - - $categories = $this->_categoryTree->setStoreId($this->getStore()->getId())->loadBreadcrumbsArray($path); - if (empty($categories)) { - return ''; - } - foreach ($categories as $key => $category) { - $categories[$key] = $this->_getNodeJson($category); - } - $scriptString = 'require(["prototype"], function(){' . $javascriptVarName . ' = ' . $this->_jsonEncoder->encode( - $categories - ) . - ';' . - ($this->canAddSubCategory() ? '$("add_subcategory_button").show();' : '$("add_subcategory_button").hide();') - . '});'; - - return /* @noEscape */ $this->secureRenderer->renderTag('script', [], $scriptString, false); - } - /** * Get JSON of a tree node or an associative array * diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index ea5f39cb..8a1bdf3b 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -6,22 +6,14 @@ use Magento\Framework\Data\Tree\Dbp; use Magefan\Blog\Api\Data\CategoryManagementInterface; use Magento\Framework\Data\Tree\Node; -use Magento\Framework\EntityManager\MetadataPool; - class Tree extends Dbp { - /** - * @var array - */ - private $_inactiveItems; - /** * @var \Magento\Framework\Event\ManagerInterface */ private $_eventManager; - /** * @var \Magefan\Blog\Model\ResourceModel\Category\CollectionFactory */ @@ -34,16 +26,6 @@ class Tree extends Dbp */ protected $_collection; - /** - * @var boolean - */ - protected $_joinUrlRewriteIntoCollection = false; - - /** - * @var array - */ - protected $_inactiveCategoryIds = null; - /** * @var integer */ @@ -69,12 +51,6 @@ class Tree extends Dbp */ protected $_blogCategory; - /** - * @var MetadataPool - * @since 101.0.0 - */ - protected $metadataPool; - /** * Tree constructor. * @param \Magefan\Blog\Model\ResourceModel\Category $blogCategory @@ -111,7 +87,6 @@ public function __construct( $this->_collectionFactory = $collectionFactory; } - /** * Load tree * @@ -272,182 +247,28 @@ public function getStoreId() } /** - * Add data to collection - * - * @param Collection $collection - * @param boolean $sorted - * @param array $exclude - * @param boolean $toLoad - * @param boolean $onlyActive + * @param $collection * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function addCollectionData( - $collection = null, - $sorted = false, - $exclude = [], - $toLoad = true, - $onlyActive = false - ) { + public function addCollectionData($collection = null) + { if ($collection === null) { - $collection = $this->getCollection($sorted); + $collection = $this->getCollection(); } else { $this->setCollection($collection); } - if (!is_array($exclude)) { - $exclude = [$exclude]; - } - $nodeIds = []; + foreach ($this->getNodes() as $node) { - if (!in_array($node->getId(), $exclude)) { - $nodeIds[] = $node->getId(); - } + $nodeIds[] = $node->getId(); } $collection->addFieldToFilter('category_id', ['in' => $nodeIds]); - - if ($onlyActive) { - $disabledIds = $this->_getDisabledIds($collection, $nodeIds); - if ($disabledIds) { - $collection->addFieldToFilter('category_id', ['nin' => $disabledIds]); - } - $collection->addFieldToFilter('is_active', 1); - $collection->addFieldToFilter('include_in_menu', 1); - } - - if ($this->_joinUrlRewriteIntoCollection) { - $collection->joinUrlRewrite(); - $this->_joinUrlRewriteIntoCollection = false; - } - - if ($toLoad) { - $collection->load(); - - foreach ($collection as $category) { - if ($this->getNodeById($category->getId())) { - $this->getNodeById($category->getId())->addData($category->getData()); - } - } - - foreach ($this->getNodes() as $node) { - if (!$collection->getItemById($node->getId()) && $node->getParent()) { - $this->removeNode($node); - } - } - } - - return $this; - } - - /** - * Add inactive categories ids - * - * @param mixed $ids - * @return $this - */ - public function addInactiveCategoryIds($ids) - { - if (!is_array($this->_inactiveCategoryIds)) { - $this->_initInactiveCategoryIds(); - } - $this->_inactiveCategoryIds = array_merge($ids, $this->_inactiveCategoryIds); - return $this; - } - - /** - * Retrieve inactive categories ids - * - * @return $this - */ - protected function _initInactiveCategoryIds() - { - $this->_inactiveCategoryIds = []; - $this->_eventManager->dispatch('blog_category_tree_init_inactive_category_ids', ['tree' => $this]); return $this; } - /** - * Retrieve inactive categories ids - * - * @return array - */ - public function getInactiveCategoryIds() - { - if (!is_array($this->_inactiveCategoryIds)) { - $this->_initInactiveCategoryIds(); - } - - return $this->_inactiveCategoryIds; - } - - /** - * Return disable category ids - * - * @param Collection $collection - * @param array $allIds - * @return array - */ - protected function _getDisabledIds($collection, $allIds) - { - $storeId = $this->_storeManager->getStore()->getId(); - $this->_inactiveItems = $this->getInactiveCategoryIds(); - $this->_inactiveItems = array_merge($this->_getInactiveItemIds($collection, $storeId), $this->_inactiveItems); - - $disabledIds = []; - - foreach ($allIds as $id) { - $parents = $this->getNodeById($id)->getPath(); - foreach ($parents as $parent) { - if (!$this->_getItemIsActive($parent->getId())) { - $disabledIds[] = $id; - continue; - } - } - } - return $disabledIds; - } - - /** - * Retrieve inactive category item ids - * - * @param Collection $collection - * @param int $storeId - * @return array - */ - protected function _getInactiveItemIds($collection, $storeId) - { - $idsSelect = clone $collection->getSelect(); - - $idsSelect->reset(\Magento\Framework\DB\Select::ORDER); - $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT); - $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET); - $idsSelect->reset(\Magento\Framework\DB\Select::COLUMNS); - $idsSelect->reset(\Magento\Framework\DB\Select::GROUP); - $idsSelect->columns('category_id'); - // $idsSelect->where('is_active = ?', 0); - /////////////////////// $storeId - - return $this->_conn->fetchCol($idsSelect); - } - - /** - * Check is category items active - * - * @param int $id - * @return boolean - * @SuppressWarnings(PHPMD.BooleanGetMethodName) - */ - protected function _getItemIsActive($id) - { - if (!in_array($id, $this->_inactiveItems)) { - return true; - } - return false; - } /** * Get categories collection @@ -544,246 +365,4 @@ protected function _afterMove() $this->_cache->clean([\Magefan\Blog\Model\Category::CACHE_TAG]); return $this; } - - /** - * Load whole category tree, that will include specified categories ids. - * - * @param array $ids - * @param bool $addCollectionData - * @param bool $updateAnchorProductCount - * @return $this|bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function loadByIds($ids, $addCollectionData = true, $updateAnchorProductCount = true) - { - $levelField = $this->_conn->quoteIdentifier('level'); - $pathField = $this->_conn->quoteIdentifier('path'); - // load first two levels, if no ids specified - if (empty($ids)) { - $select = $this->_conn->select()->from($this->_table, 'category_id')->where($levelField . ' <= 2'); - $ids = $this->_conn->fetchCol($select); - } - if (!is_array($ids)) { - $ids = [$ids]; - } - foreach ($ids as $key => $id) { - $ids[$key] = (int)$id; - } - - // collect paths of specified IDs and prepare to collect all their parents and neighbours - $select = $this->_conn->select()->from($this->_table, ['path', 'level'])->where('category_id IN (?)', $ids); - $where = [$levelField . '=0' => true]; - - foreach ($this->_conn->fetchAll($select) as $item) { - $pathIds = explode('/', $item['path'] ?? ''); - $level = (int)$item['level']; - while ($level > 0) { - $pathIds[count($pathIds) - 1] = '%'; - $path = implode('/', $pathIds); - $where["{$levelField}={$level} AND {$pathField} LIKE '{$path}'"] = true; - array_pop($pathIds); - $level--; - } - } - $where = array_keys($where); - - // get all required records - if ($addCollectionData) { - $select = $this->_createCollectionDataSelect(); - } else { - $select = clone $this->_select; - $select->order($this->_orderField . ' ' . \Magento\Framework\DB\Select::SQL_ASC); - } - $select->where(implode(' OR ', $where)); - - // get array of records and add them as nodes to the tree - $arrNodes = $this->_conn->fetchAll($select); - if (!$arrNodes) { - return false; - } - if ($updateAnchorProductCount) { - $this->_updateAnchorProductCount($arrNodes); - } - $childrenItems = []; - foreach ($arrNodes as $key => $nodeInfo) { - $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); - array_pop($pathToParent); - $pathToParent = implode('/', $pathToParent); - $childrenItems[$pathToParent][] = $nodeInfo; - } - $this->addChildNodes($childrenItems, '', null); - return $this; - } - - /** - * Load array of category parents - * - * @param string $path - * @param bool $addCollectionData - * @param bool $withRootNode - * @return array - */ - public function loadBreadcrumbsArray($path, $addCollectionData = true, $withRootNode = false) - { - $pathIds = explode('/', $path ?: ''); - if (!$withRootNode) { - array_shift($pathIds); - } - $result = []; - if (!empty($pathIds)) { - if ($addCollectionData) { - $select = $this->_createCollectionDataSelect(false); - } else { - $select = clone $this->_select; - } - $select->where( - 'e.category_id IN(?)', - $pathIds - )->order( - $this->_conn->getLengthSql('e.path') . ' ' . \Magento\Framework\DB\Select::SQL_ASC - ); - $result = $this->_conn->fetchAll($select); - $this->_updateAnchorProductCount($result); - } - return $result; - } - - /** - * Replace products count with self products count, if category is non-anchor - * - * @param array &$data - * @return void - */ - protected function _updateAnchorProductCount(&$data) - { - foreach ($data as $key => $row) { - if (0 === (int)$row['is_anchor']) { - $data[$key]['product_count'] = $row['self_product_count']; - } - } - } - - /** - * Obtain select for categories with attributes. - * By default everything from entity table is selected - * + name, is_active and is_anchor - * Also the correct product_count is selected, depending on is the category anchor or not. - * - * @param bool $sorted - * @param array $optionalAttributes - * @return \Magento\Framework\DB\Select - */ - protected function _createCollectionDataSelect($sorted = true, $optionalAttributes = []) - { - $meta = $this->getMetadataPool()->getMetadata(CategoryManagementInterface::class); - $linkField = $meta->getLinkField(); - - $select = $this->_getDefaultCollection($sorted ? $this->_orderField : false)->getSelect(); - // add attributes to select - $attributes = ['name', 'is_active', 'is_anchor']; - if ($optionalAttributes) { - $attributes = array_unique(array_merge($attributes, $optionalAttributes)); - } - $resource = $this->_blogCategory; - foreach ($attributes as $attributeCode) { - /* @var $attribute \Magento\Eav\Model\Entity\Attribute */ - $attribute = $resource->getAttribute($attributeCode); - // join non-static attribute table - if (!$attribute->getBackend()->isStatic()) { - $tableDefault = sprintf('d_%s', $attributeCode); - $tableStore = sprintf('s_%s', $attributeCode); - $valueExpr = $this->_conn->getCheckSql( - "{$tableStore}.value_id > 0", - "{$tableStore}.value", - "{$tableDefault}.value" - ); - - $select->joinLeft( - [$tableDefault => $attribute->getBackend()->getTable()], - sprintf( - '%1$s.' . $linkField . '=e.' . $linkField . - ' AND %1$s.attribute_id=%2$d AND %1$s.store_id=%3$d', - $tableDefault, - $attribute->getId(), - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ), - [$attributeCode => 'value'] - )->joinLeft( - [$tableStore => $attribute->getBackend()->getTable()], - sprintf( - '%1$s.' . $linkField . '=e.' . $linkField . - ' AND %1$s.attribute_id=%2$d AND %1$s.store_id=%3$d', - $tableStore, - $attribute->getId(), - $this->getStoreId() - ), - [$attributeCode => $valueExpr] - ); - } - } - - // count children products qty plus self products qty - $categoriesTable = $this->_coreResource->getTableName('magefan_blog_category'); - $categoriesProductsTable = $this->_coreResource->getTableName('magefan_blog_post'); - - $subConcat = $this->_conn->getConcatSql(['e.path', $this->_conn->quote('/%')]); - $subSelect = $this->_conn->select()->from( - ['see' => $categoriesTable], - null - )->joinLeft( - ['scp' => $categoriesProductsTable], - 'see.category_id=scp.category_id', - ['COUNT(DISTINCT scp.product_id)'] - )->where( - 'see.category_id = e.category_id' - )->orWhere( - 'see.path LIKE ?', - $subConcat - ); - $select->columns(['product_count' => $subSelect]); - - $subSelect = $this->_conn->select()->from( - ['cp' => $categoriesProductsTable], - 'COUNT(cp.product_id)' - )->where( - 'cp.category_id = e.category_id' - ); - - $select->columns(['self_product_count' => $subSelect]); - - return $select; - } - - /** - * Get real existing category ids by specified ids - * - * @param array $ids - * @return array - */ - public function getExistingCategoryIdsBySpecifiedIds($ids) - { - if (empty($ids)) { - return []; - } - if (!is_array($ids)) { - $ids = [$ids]; - } - $select = $this->_conn->select()->from($this->_table, ['category_id'])->where('category_id IN (?)', $ids); - return $this->_conn->fetchCol($select); - } - - /** - * Get entity methadata pool. - * - * @return \Magento\Framework\EntityManager\MetadataPool - */ - private function getMetadataPool() - { - if (null === $this->metadataPool) { - $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\MetadataPool::class); - } - return $this->metadataPool; - } } From fa52070c0cb02ed36682a8116c50e1edc3f1b4a3 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Mon, 12 May 2025 19:03:15 +0300 Subject: [PATCH 08/12] calculate post count --- Model/ResourceModel/Category/Tree.php | 28 ++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index 8a1bdf3b..4822cabe 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -51,6 +51,11 @@ class Tree extends Dbp */ protected $_blogCategory; + /** + * @var null + */ + protected $categoryPostsCount = null; + /** * Tree constructor. * @param \Magefan\Blog\Model\ResourceModel\Category $blogCategory @@ -149,6 +154,8 @@ public function load($parentNode = null, $recursionLevel = 0) } } + $nodeInfo['post_count'] = $this->getCategoryPostsCount((int)$nodeInfo['category_id']); + $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); array_pop($pathToParent); $pathToParent = implode('/', $pathToParent); @@ -168,7 +175,6 @@ public function load($parentNode = null, $recursionLevel = 0) return $this; } - /** * Load ensured nodes * @@ -212,6 +218,7 @@ public function loadEnsuredNodes($category, $rootNode) $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); array_pop($pathToParent); $pathToParent = implode('/', $pathToParent); + $childrenItems[$pathToParent][] = $nodeInfo; } @@ -219,6 +226,25 @@ public function loadEnsuredNodes($category, $rootNode) } } + /** + * @param int $categoryId + * @return int + */ + private function getCategoryPostsCount(int $categoryId): int + { + if (null === $this->categoryPostsCount) { + $select = $this->_conn->select() + ->from( + $this->_coreResource->getTableName('magefan_blog_post_category'), + ['category_id', 'post_count' => new \Zend_Db_Expr('COUNT(post_id)')] + ) + ->group('category_id'); + + $this->categoryPostsCount = $this->_conn->fetchPairs($select); + } + + return $this->categoryPostsCount[$categoryId] ?? 0; + } /** From 41e1ecf306e6b4ecdd07f178b79de689179c19ed Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Wed, 14 May 2025 12:33:18 +0300 Subject: [PATCH 09/12] resolved issue with menu --- Block/Adminhtml/Category/AbstractCategory.php | 1 + Block/Adminhtml/Category/Tree.php | 18 ++++-------------- Controller/Adminhtml/Category/Save.php | 6 +++++- Model/Category.php | 5 +++-- Model/ResourceModel/Category/Tree.php | 7 ++++++- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Block/Adminhtml/Category/AbstractCategory.php b/Block/Adminhtml/Category/AbstractCategory.php index 6b221f6f..34312256 100644 --- a/Block/Adminhtml/Category/AbstractCategory.php +++ b/Block/Adminhtml/Category/AbstractCategory.php @@ -242,6 +242,7 @@ public function getNode($parentNodeCategory, $recursionLevel = 2) { $nodeId = $parentNodeCategory->getId(); $node = $this->_categoryTree->loadNode($nodeId); + $node->loadChildren($recursionLevel); if ($node && $nodeId != \Magefan\Blog\Model\Category::TREE_ROOT_ID) { diff --git a/Block/Adminhtml/Category/Tree.php b/Block/Adminhtml/Category/Tree.php index 4e8aded0..532a12ba 100644 --- a/Block/Adminhtml/Category/Tree.php +++ b/Block/Adminhtml/Category/Tree.php @@ -279,12 +279,6 @@ public function getTreeJson($parenNodeCategory = null) */ protected function _getNodeJson($node, $level = 0) { - /* echo PHP_EOL. '_getNodeJson'; - echo 'TITLE: ' . $node->getTitle() . PHP_EOL; - echo 'LVL: ' . $level . PHP_EOL; - echo ' hasChildren: ' . ( $node->hasChildren() ? 'yes' : 'no' ) . PHP_EOL; -*/ - // create a node from data array if (is_array($node)) { $node = new Node($node, 'entity_id', new \Magento\Framework\Data\Tree()); @@ -298,26 +292,22 @@ protected function _getNodeJson($node, $level = 0) $item['id'] = $node->getId(); $item['store'] = (int)$this->getStore()->getId(); $item['path'] = $node->getData('path'); - + $item['a_attr'] = ['class' => $node->getIsActive() ? 'active-category' : 'not-active-category']; $item['cls'] = 'folder ' . ($node->getIsActive() ? 'active-category' : 'no-active-category'); - //$item['allowDrop'] = ($level<3) ? true : false; + $allowMove = $this->_isCategoryMoveable($node); $item['allowDrop'] = $allowMove; // disallow drag if it's first level and category is root of a store $item['allowDrag'] = $allowMove && ($node->getLevel() == 1 && $rootForStores ? false : true); - if ((int)$node->getChildrenCount() > 0) { - $item['children'] = []; - } - $isParent = $this->_isParentSelectedCategory($node); + $item['children'] = []; + if ($node->hasChildren()) { $item['children'] = []; if (!($this->getUseAjax() && $node->getLevel() > 1 && !$isParent)) { - //echo 'COUNT getChildren: ' . count($node->getChildren()) . PHP_EOL; - foreach ($node->getChildren() as $child) { $item['children'][] = $this->_getNodeJson($child, $level + 1); } diff --git a/Controller/Adminhtml/Category/Save.php b/Controller/Adminhtml/Category/Save.php index 15e06d8a..37d497f6 100755 --- a/Controller/Adminhtml/Category/Save.php +++ b/Controller/Adminhtml/Category/Save.php @@ -52,7 +52,11 @@ protected function _beforeSave($model, $request) $path = $parentCategory->getPath(); if ($parentId) { - $path .= '/' . $parentId; + if ($path) { + $path .= '/' . $parentId; + } else { + $path = $parentId; + } } $model->setPath($path); diff --git a/Model/Category.php b/Model/Category.php index 574938da..b3617746 100755 --- a/Model/Category.php +++ b/Model/Category.php @@ -345,13 +345,14 @@ public function getLevel() return count($this->getParentIds()); } - /** * @return array */ public function getPathIds() { - return $this->getParentIds(); + $pathIds = $this->getParentIds(); + $pathIds[] = $this->getId(); + return $pathIds; } /** diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index 4822cabe..d11531b4 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -128,7 +128,12 @@ public function load($parentNode = null, $recursionLevel = 0) $select->order($this->_table . '.' . $this->_orderField . ' ASC'); if ($parentPath) { $pathField = $this->_conn->quoteIdentifier([$this->_table, $this->_pathField]); - $select->where("{$pathField} LIKE ?", "{$parentPath}/%"); + + $like = explode('/', $parentPath); + array_pop($like); + $like = implode('/', $like); + + $select->where("{$pathField} LIKE ?", "{$like}/%"); } if ($recursionLevel != 0) { $levelField = $this->_conn->quoteIdentifier([$this->_table, $this->_levelField]); From 9a06da63a21a9a370285f1972709718b8ce8f9b4 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Wed, 14 May 2025 13:12:02 +0300 Subject: [PATCH 10/12] added calculete of children count --- Block/Adminhtml/Category/Tree.php | 6 +++- Model/ResourceModel/Category/Tree.php | 49 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/Block/Adminhtml/Category/Tree.php b/Block/Adminhtml/Category/Tree.php index 532a12ba..58318d4b 100644 --- a/Block/Adminhtml/Category/Tree.php +++ b/Block/Adminhtml/Category/Tree.php @@ -303,7 +303,11 @@ protected function _getNodeJson($node, $level = 0) $isParent = $this->_isParentSelectedCategory($node); - $item['children'] = []; + // used to identify if children may be loaded via ajax in jstree + // if you face some issue with child display - remove condition and just add $item['children'] = []; + if ((int)$node->getChildrenCount() > 0) { + $item['children'] = []; + } if ($node->hasChildren()) { $item['children'] = []; diff --git a/Model/ResourceModel/Category/Tree.php b/Model/ResourceModel/Category/Tree.php index d11531b4..03512446 100644 --- a/Model/ResourceModel/Category/Tree.php +++ b/Model/ResourceModel/Category/Tree.php @@ -56,6 +56,11 @@ class Tree extends Dbp */ protected $categoryPostsCount = null; + /** + * @var null + */ + protected $categoryChildrenCount = null; + /** * Tree constructor. * @param \Magefan\Blog\Model\ResourceModel\Category $blogCategory @@ -160,6 +165,7 @@ public function load($parentNode = null, $recursionLevel = 0) } $nodeInfo['post_count'] = $this->getCategoryPostsCount((int)$nodeInfo['category_id']); + $nodeInfo['children_count'] = $this->getCategoryChildrenCount((int)$nodeInfo['category_id']); $pathToParent = explode('/', $nodeInfo[$this->_pathField] ?? ''); array_pop($pathToParent); @@ -251,6 +257,49 @@ private function getCategoryPostsCount(int $categoryId): int return $this->categoryPostsCount[$categoryId] ?? 0; } + /** + * @param int $categoryId + * @return int + */ + private function getCategoryChildrenCount(int $categoryId): int + { + if (null === $this->categoryChildrenCount) { + $tableName = $this->_coreResource->getTableName('magefan_blog_category'); + + // Fetch all categories with their path + $select = $this->_conn->select() + ->from($tableName, ['category_id', 'path']); + + $rows = $this->_conn->fetchAll($select); + + $collectData = []; + + foreach ($rows as $row) { + $path = (string)$row['path']; + $parts = explode('/', $path); + + foreach ($parts as $level => $catId) { + if (isset($collectData[$level][$catId])) { + $collectData[$level][$catId]++; + } else { + $collectData[$level][$catId] = 0; + } + } + } + + foreach ($collectData as $level => $categories) { + foreach ($categories as $catId => $count) { + if (isset($this->categoryChildrenCount[$catId])) { + $this->categoryChildrenCount[$catId] += $count; + } else { + $this->categoryChildrenCount[$catId] = $count; + } + } + } + } + + return $this->categoryChildrenCount[$categoryId] ?? 0; + } /** * Set store id From d3abf03700f6999987252ac77a5834e7c36f34e5 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Wed, 14 May 2025 14:07:30 +0300 Subject: [PATCH 11/12] upd level calc --- Model/ResourceModel/Category.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Model/ResourceModel/Category.php b/Model/ResourceModel/Category.php index 7874b971..27bd9cad 100755 --- a/Model/ResourceModel/Category.php +++ b/Model/ResourceModel/Category.php @@ -104,13 +104,13 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) } if ($object->isObjectNew()) { - if ($object->getPosition() === null) { $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); } $path = explode('/', (string)$object->getPath()); - $level = count($path)+1 - ($object->getId() ? 1 : 0); + $level = $object->getPath() ? count($path) + 1 : 1; + $toUpdateChild = array_diff($path, [$object->getId()]); if (!$object->hasPosition()) { @@ -119,7 +119,6 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) if (!$object->hasLevel()) { $object->setLevel($level); } - /* var_dump($object->getData('level')); var_dump($object->getData('position')); From 1f53503d9a9b08838d58d3a80620970ae7dee3e0 Mon Sep 17 00:00:00 2001 From: Bohdan Berezhniy Date: Wed, 28 May 2025 15:42:32 +0300 Subject: [PATCH 12/12] added store switcher --- view/adminhtml/layout/blog_category_edit.xml | 9 +++++++++ view/adminhtml/ui_component/blog_category_form.xml | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/view/adminhtml/layout/blog_category_edit.xml b/view/adminhtml/layout/blog_category_edit.xml index d7381c28..6197117c 100644 --- a/view/adminhtml/layout/blog_category_edit.xml +++ b/view/adminhtml/layout/blog_category_edit.xml @@ -12,6 +12,15 @@ + + + + + 1 + + + + diff --git a/view/adminhtml/ui_component/blog_category_form.xml b/view/adminhtml/ui_component/blog_category_form.xml index 1b49cc71..09cb7629 100644 --- a/view/adminhtml/ui_component/blog_category_form.xml +++ b/view/adminhtml/ui_component/blog_category_form.xml @@ -54,6 +54,18 @@ 10 + + + + + category + + + + number + + + @@ -66,8 +78,6 @@ - -