categoryCollectionFactory = $categoryCollectionFactory; $this->productCollectionFactory = $productCollectionFactory; $this->imageHelper = $imageHelper; $this->jsonSerializer = $jsonSerializer; $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; $this->priceCurrency = $priceCurrency; $this->cardPriceResolver = $cardPriceResolver; $this->productRepository = $productRepository; $this->filterProvider = $filterProvider; } public function isEnabled(): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_ENABLED, ScopeInterface::SCOPE_STORE); } public function getTitle(): string { return trim((string)$this->scopeConfig->getValue(self::XML_PATH_TITLE, ScopeInterface::SCOPE_STORE)) ?: 'CLASSIFICATION'; } public function getRootCategoryId(): int { return (int)$this->scopeConfig->getValue(self::XML_PATH_ROOT_CATEGORY_ID, ScopeInterface::SCOPE_STORE); } public function getRootCategoryIds(): array { $configuredIds = (string)$this->scopeConfig->getValue(self::XML_PATH_ROOT_CATEGORY_IDS, ScopeInterface::SCOPE_STORE); $legacyId = $this->getRootCategoryId(); $ids = []; foreach (preg_split('/[\s,;]+/', $configuredIds) ?: [] as $value) { $id = (int)trim($value); if ($id > 0) { $ids[$id] = $id; } } if ($legacyId > 0) { $ids[$legacyId] = $legacyId; } return array_values($ids); } public function isMegaMenuRootCategory(int $categoryId): bool { return in_array($categoryId, $this->getRootCategoryIds(), true); } public function getMegaMenuType(int $rootCategoryId): string { $category = $this->getCategoryById($rootCategoryId, ['esun_megamenu_type', 'is_active']); $type = $category ? trim((string)$category->getData('esun_megamenu_type')) : ''; return $type === MegaMenuType::ICON_CATEGORY ? $type : MegaMenuType::PRODUCT; } public function isIconCategoryMenu(int $rootCategoryId): bool { return $this->getMegaMenuType($rootCategoryId) === MegaMenuType::ICON_CATEGORY; } public function getIconCategoryMenuData(int $rootCategoryId): array { if (!$rootCategoryId) { return []; } $storeId = (int)$this->storeManager->getStore()->getId(); $columns = $this->categoryCollectionFactory->create() ->setStoreId($storeId) ->addAttributeToSelect([ 'name', 'is_active', 'include_in_menu', 'url_key', 'url_path', 'esun_megamenu_icon', 'esun_megamenu_redirect_url' ]) ->addAttributeToFilter('parent_id', $rootCategoryId) ->addAttributeToFilter('is_active', 1) ->addAttributeToFilter('include_in_menu', 1) ->setOrder('position', 'ASC'); $menuData = []; foreach ($columns as $column) { $links = []; $children = $this->getChildCategories((int)$column->getId(), $storeId); foreach ($children as $child) { $links[] = [ 'id' => (int)$child->getId(), 'name' => (string)$child->getName(), 'url' => $this->getConfiguredCategoryUrl($child) ]; } $menuData[] = [ 'id' => (int)$column->getId(), 'name' => (string)$column->getName(), 'url' => $this->getConfiguredCategoryUrl($column), 'icon_url' => $this->getCategoryImageUrl( trim((string)$column->getData('esun_megamenu_icon')) ), 'children' => $links ]; } return $menuData; } public function getFeaturedProductData(int $rootCategoryId): ?array { if (!$rootCategoryId) { return null; } $storeId = (int)$this->storeManager->getStore()->getId(); $category = $this->getCategoryById($rootCategoryId, [ 'esun_megamenu_featured_background', 'esun_megamenu_featured_label', 'esun_megamenu_featured_sku', 'is_active' ]); if (!$category || !$category->getId()) { return null; } $sku = trim((string)$category->getData('esun_megamenu_featured_sku')); if ($sku === '') { return null; } try { $product = $this->productRepository->get($sku, false, $storeId); } catch (NoSuchEntityException $e) { return null; } if ((int)$product->getStatus() !== Status::STATUS_ENABLED) { return null; } $backgroundImage = trim((string)$category->getData('esun_megamenu_featured_background')); $label = trim((string)$category->getData('esun_megamenu_featured_label')) ?: 'New Arrival'; return [ 'label' => $label, 'name' => (string)$product->getName(), 'url' => $product->getProductUrl(), 'category_background_url' => $this->getCategoryImageUrl($backgroundImage) ]; } public function getTopLevelCategoryUrl(int $categoryId, string $fallbackUrl = '#'): string { if (!$categoryId || $this->isMegaMenuRootCategory($categoryId)) { return $fallbackUrl; } if (isset($this->categoryUrlCache[$categoryId])) { return $this->categoryUrlCache[$categoryId]; } $category = $this->getCategoryById($categoryId, [ 'esun_megamenu_redirect_url', 'url_key', 'url_path', 'is_active' ]); if (!$category || !$category->getId()) { return $fallbackUrl; } $redirectUrl = trim((string)$category->getData('esun_megamenu_redirect_url')); $resolvedUrl = $redirectUrl !== '' ? $this->resolveRedirectUrl($redirectUrl) : $category->getUrl(); $this->categoryUrlCache[$categoryId] = $resolvedUrl ?: $fallbackUrl; return $this->categoryUrlCache[$categoryId]; } private function getConfiguredCategoryUrl(Category $category): string { $redirectUrl = trim((string)$category->getData('esun_megamenu_redirect_url')); return $redirectUrl !== '' ? $this->resolveRedirectUrl($redirectUrl) : $category->getUrl(); } public function getRootCategoryUrl(?int $rootCategoryId = null): string { $rootCategoryId = $rootCategoryId ?: $this->getRootCategoryId(); if (!$rootCategoryId) { return '#'; } $category = $this->getCategoryById($rootCategoryId, [ 'esun_megamenu_redirect_url', 'is_active', 'url_key', 'url_path' ]); return $category && $category->getId() ? $this->getConfiguredCategoryUrl($category) : '#'; } public function getRootCategoryName(int $rootCategoryId): string { if (!$rootCategoryId) { return $this->getTitle(); } $category = $this->getCategoryById($rootCategoryId, ['name', 'is_active']); return $category && $category->getId() ? (string)$category->getName() : $this->getTitle(); } public function getRootCategoryDescription(int $rootCategoryId): string { if (!$rootCategoryId) { return ''; } $category = $this->getCategoryById($rootCategoryId, ['description', 'is_active']); if (!$category || !$category->getId()) { return ''; } $description = trim((string)$category->getDescription()); return $description !== '' ? (string)$this->filterProvider->getPageFilter()->filter($description) : ''; } public function getProductsPerSeries(): int { $limit = (int)$this->scopeConfig->getValue(self::XML_PATH_PRODUCTS_PER_SERIES, ScopeInterface::SCOPE_STORE); return $limit > 0 ? $limit : 6; } public function getSeriesLimit(): int { $limit = (int)$this->scopeConfig->getValue(self::XML_PATH_SERIES_LIMIT, ScopeInterface::SCOPE_STORE); return $limit > 0 ? $limit : 8; } public function getMegaMenuJson(?int $rootCategoryId = null): string { return $this->jsonSerializer->serialize($this->getMegaMenuData($rootCategoryId)); } public function getMegaMenuData(?int $rootCategoryId = null): array { $rootCategoryId = $rootCategoryId ?: $this->getRootCategoryId(); if (!$rootCategoryId) { return []; } $storeId = $this->storeManager->getStore()->getId(); $filamentCategories = $this->categoryCollectionFactory->create() ->setStoreId($storeId) ->addAttributeToSelect([ 'name', 'is_active', 'include_in_menu', 'url_key', 'url_path', 'esun_megamenu_redirect_url' ]) ->addAttributeToFilter('parent_id', $rootCategoryId) ->addAttributeToFilter('is_active', 1) ->setOrder('position', 'ASC'); $menuData = []; foreach ($filamentCategories as $filamentCategory) { $filamentData = [ 'id' => (int)$filamentCategory->getId(), 'name' => (string)$filamentCategory->getName(), 'url' => $filamentCategory->getUrl(), 'seriesCategories' => [] ]; $seriesCategories = $this->getChildCategories((int)$filamentCategory->getId(), $storeId, $this->getSeriesLimit()); foreach ($seriesCategories as $seriesCategory) { $filamentData['seriesCategories'][] = $this->buildSeriesCategoryData($seriesCategory); } // Some menu roots, such as Resins, assign products directly to their // first-level child categories instead of using a series level. if (!$filamentData['seriesCategories']) { $filamentData['seriesCategories'][] = $this->buildSeriesCategoryData($filamentCategory); } $menuData[] = $filamentData; } return $menuData; } private function getChildCategories(int $parentId, int $storeId, int $pageSize = 0) { $collection = $this->categoryCollectionFactory->create() ->setStoreId($storeId) ->addAttributeToSelect([ 'name', 'is_active', 'include_in_menu', 'url_key', 'url_path', 'esun_megamenu_redirect_url' ]) ->addAttributeToFilter('parent_id', $parentId) ->addAttributeToFilter('is_active', 1) ->addAttributeToFilter('include_in_menu', 1) ->setOrder('position', 'ASC'); if ($pageSize > 0) { $collection->setPageSize($pageSize)->setCurPage(1); } return $collection; } private function buildSeriesCategoryData(Category $category): array { return [ 'id' => (int)$category->getId(), 'name' => (string)$category->getName(), 'url' => $category->getUrl(), 'products' => $this->getProductsForCategory($category) ]; } private function getProductsForCategory(Category $category): array { $storeId = $this->storeManager->getStore()->getId(); $collection = $this->productCollectionFactory->create() ->setStoreId($storeId) ->addStoreFilter($storeId) ->addAttributeToSelect(['name', 'price', 'small_image', 'thumbnail']) ->addFinalPrice() ->addCategoryFilter($category) ->addAttributeToFilter('status', Status::STATUS_ENABLED) ->setVisibility([Visibility::VISIBILITY_IN_CATALOG, Visibility::VISIBILITY_BOTH]) ->addUrlRewrite() ->setPageSize($this->getProductsPerSeries()) ->setCurPage(1); $products = []; foreach ($collection as $product) { $products[] = [ 'id' => (int)$product->getId(), 'name' => (string)$product->getName(), 'url' => $product->getProductUrl(), 'price' => $this->priceCurrency->format( $this->cardPriceResolver->getFinalPrice($product), false ), 'image_url' => $this->imageHelper->init($product, 'category_page_grid')->getUrl() ]; } return $products; } private function getCategoryById(int $categoryId, array $attributes): ?Category { $storeId = $this->storeManager->getStore()->getId(); $collection = $this->categoryCollectionFactory->create() ->setStoreId($storeId) ->addAttributeToSelect($attributes) ->addAttributeToFilter('entity_id', $categoryId) ->addAttributeToFilter('is_active', 1) ->setPageSize(1); $category = $collection->getFirstItem(); return $category && $category->getId() ? $category : null; } private function getCategoryImageUrl(string $image): string { if ($image === '') { return ''; } if (preg_match('#^https?://#i', $image)) { return $image; } $image = ltrim($image, '/'); if (strpos($image, 'media/') === 0) { $image = substr($image, strlen('media/')); } $mediaPath = strpos($image, 'catalog/category/') === 0 ? $image : 'catalog/category/' . $image; return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $mediaPath; } private function resolveRedirectUrl(string $redirectUrl): string { if (preg_match('#^https?://#i', $redirectUrl)) { return $redirectUrl; } $path = ltrim($redirectUrl, '/'); return rtrim( $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_LINK), '/' ) . '/' . $path; } }