Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Data/CategoryTreeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Loki\AdminComponents\Data;

class CategoryTreeNode
{
public function __construct(
public readonly int $id,
public readonly string $label,
public readonly bool $isActive,
public readonly int $level,
public readonly ?int $parentId = null,
public readonly array $parentIds = [],
public readonly array $children = [],
) {
}

public function toUiArray(): array
{
$uiData = array_merge(
get_object_vars($this),
[
'hasChildren' => $this->children !== [],
'expanded' => $this->level === 0,
'hovered' => false,
'parentsPath' => null,
]
);

unset($uiData['children']);

return $uiData;
}
}
76 changes: 76 additions & 0 deletions Form/Field/FieldType/CategorySelection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Loki\AdminComponents\Form\Field\FieldType;

use Loki\AdminComponents\Data\CategoryTreeNode;
use Loki\AdminComponents\Service\CategorySelection\CategoryTreeBuilder;
use Loki\AdminComponents\Service\CategorySelection\CategoryTreeRenderer;
use Loki\AdminComponents\Form\Field\Field;
use Loki\AdminComponents\Form\Field\FieldType\FieldTypeAbstract;
use Magento\Framework\Serialize\SerializerInterface;

class CategorySelection extends FieldTypeAbstract
{
public function __construct(
private readonly CategoryTreeBuilder $categoryTreeBuilder,
private readonly CategoryTreeRenderer $categoryTreeRenderer,
private readonly SerializerInterface $serializer,
) {
}

public function getTemplate(): string
{
return 'Loki_AdminComponents::form/field_type/category_selection.phtml';
}

public function getCategoryNodeJson(CategoryTreeNode $treeNode): string
{
return $this->serializer->serialize($treeNode->toUiArray());
}

/**
* @param CategoryTreeNode[] $categoryTreeNodes
*/
public function getMaxCategoryLevel(array $categoryTreeNodes): int
{
return $this->categoryTreeBuilder->calculateMaxLevel($categoryTreeNodes);
}

public function renderMainScript(Field $field): string
{
return $this->categoryTreeRenderer->renderMainScript($field);
}

public function renderChildCategoryNode(CategoryTreeNode $categoryTreeNode, Field $field): string
{
return $this->categoryTreeRenderer->renderChildNode($categoryTreeNode, $field);
}

public function renderCategoriesBreadcrumb(): string
{
return $this->categoryTreeRenderer->renderBreadcrumb();
}

public function renderSearchInput(): string
{
return $this->categoryTreeRenderer->renderSearchInput();
}

public function getHoverSequenceJson(): string
{
$categoryTree = $this->getCategoryTree();
$hoverSequence = $this->categoryTreeBuilder->buildHoverSequence($categoryTree);

return $this->serializer->serialize($hoverSequence);
}

/**
* @return CategoryTreeNode[]
*/
public function getCategoryTree(int $storeId = 0, ?string $filter = null): array
{
return $this->categoryTreeBuilder->build($storeId, $filter);
}
}
36 changes: 36 additions & 0 deletions Model/CategorySelection/CategoryTreeCacheKeyGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Loki\AdminComponents\Model\CategorySelection;

use Magento\Backend\Model\Auth\Session;

class CategoryTreeCacheKeyGenerator
{
public const CACHE_TAG_PREFIX = 'loki_admin_components_form_category_tree';

public function __construct(
private readonly Session $authSession,
) {
}

public function generate(int $storeId, ?string $filter): string
{
$parts = [
self::CACHE_TAG_PREFIX,
(string) $storeId,
$this->getUserRole(),
$filter ?? '',
];

return implode('_', array_filter($parts));
}

private function getUserRole(): string
{
$user = $this->authSession->getUser();

return (string)$user?->getAclRole();
}
}
63 changes: 63 additions & 0 deletions Service/CategorySelection/CategoryFilterService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Loki\AdminComponents\Service\CategorySelection;

use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\ResourceModel\Category\Collection;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
use Magento\Framework\DB\Helper as DbHelper;

class CategoryFilterService
{
public function __construct(
private readonly CollectionFactory $categoryCollectionFactory,
private readonly DbHelper $dbHelper,
) {
}

/**
* @return int[]
*/
public function getVisibleCategoryIds(int $storeId, ?string $filter): array
{
$collection = $this->createFilteredCollection($storeId, $filter);

return $this->extractCategoryIdsWithAncestors($collection);
}

private function createFilteredCollection(int $storeId, ?string $filter): Collection
{
$collection = $this->categoryCollectionFactory->create();
$collection->addAttributeToSelect('path')
->addAttributeToFilter('entity_id', ['neq' => Category::TREE_ROOT_ID])
->setStoreId($storeId);

if (!empty($filter)) {
$collection->addAttributeToFilter(
'name',
['like' => $this->dbHelper->addLikeEscape($filter, ['position' => 'any'])]
);
}

return $collection;
}

/**
* @return int[]
*/
private function extractCategoryIdsWithAncestors(Collection $collection): array
{
$categoryIds = [];

foreach ($collection as $category) {
$path = $category->getPath() ?? '';
foreach (explode('/', $path) as $ancestorId) {
$categoryIds[(int) $ancestorId] = true;
}
}

return array_keys($categoryIds);
}
}
Loading