Skip to content

Commit 2cdadf4

Browse files
committed
Init
1 parent 44efe4f commit 2cdadf4

File tree

18 files changed

+634
-0
lines changed

18 files changed

+634
-0
lines changed

Model/Breadcrumbs/Strategy.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Model\Breadcrumbs;
9+
10+
use Magento\Framework\Phrase;
11+
12+
enum Strategy: string
13+
{
14+
private const NATIVE = 'native';
15+
private const DEEPEST = 'deepest';
16+
private const SHALLOWEST = 'shallowest';
17+
18+
private const LABELS = [
19+
self::NATIVE => 'Build breadcrumbs as it is natively.',
20+
self::DEEPEST => 'Build breadcrumbs from deepest category.',
21+
self::SHALLOWEST => 'Build breadcrumbs from shallowest category.',
22+
];
23+
24+
case Native = self::NATIVE;
25+
case Deepest = self::DEEPEST;
26+
case Shallowest = self::SHALLOWEST;
27+
28+
public function getLabel(): Phrase
29+
{
30+
return new Phrase(self::LABELS[$this->value] ?? $this->name);
31+
}
32+
}

Model/Config/Breadcrumbs.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Model\Config;
9+
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Store\Model\ScopeInterface;
12+
use Opengento\ProductBreadcrumbs\Model\Breadcrumbs\Strategy;
13+
14+
use function array_filter;
15+
use function array_map;
16+
use function explode;
17+
use function preg_replace;
18+
19+
class Breadcrumbs
20+
{
21+
private const CONFIG_PATH_IS_SERVER_SIDE = 'catalog/seo/render_product_breadcrumbs_server_side';
22+
private const CONFIG_PATH_IS_CLIENT_SIDE_OVERRIDE_ALLOWED = 'catalog/seo/override_product_breadcrumbs_client_side';
23+
private const CONFIG_PATH_STRATEGY = 'catalog/seo/product_breadcrumbs_strategy';
24+
private const CONFIG_PATH_EXCLUDED_CATEGORIES = 'catalog/seo/product_breadcrumbs_exclude_categories';
25+
26+
public function __construct(private ScopeConfigInterface $scopeConfig) {}
27+
28+
public function isServerSideRendered(int|string|null $store = null): bool
29+
{
30+
return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_IS_SERVER_SIDE, ScopeInterface::SCOPE_STORES, $store);
31+
}
32+
33+
public function isClientSideOverrideAllowed(int|string|null $store = null): bool
34+
{
35+
return $this->scopeConfig->isSetFlag(
36+
self::CONFIG_PATH_IS_CLIENT_SIDE_OVERRIDE_ALLOWED,
37+
ScopeInterface::SCOPE_STORES,
38+
$store
39+
);
40+
}
41+
42+
public function getStrategy(int|string|null $store = null): Strategy
43+
{
44+
return Strategy::from(
45+
$this->scopeConfig->getValue(self::CONFIG_PATH_STRATEGY, ScopeInterface::SCOPE_STORES, $store)
46+
);
47+
}
48+
49+
public function getExcludedCategories(int|string|null $store = null): array
50+
{
51+
return array_map(intval(...), array_filter(explode(
52+
',',
53+
preg_replace('/\s+/', '', (string)$this->scopeConfig->getValue(
54+
self::CONFIG_PATH_EXCLUDED_CATEGORIES,
55+
ScopeInterface::SCOPE_STORES,
56+
$store
57+
))
58+
)));
59+
}
60+
}

Model/Config/Source/Strategies.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Model\Config\Source;
9+
10+
use Magento\Framework\Data\OptionSourceInterface;
11+
use Opengento\ProductBreadcrumbs\Model\Breadcrumbs\Strategy;
12+
13+
use function array_map;
14+
15+
class Strategies implements OptionSourceInterface
16+
{
17+
private ?array $options = null;
18+
19+
public function toOptionArray(): array
20+
{
21+
return $this->options ??= array_map(
22+
static fn (Strategy $strategy): array => ['label' => $strategy->getLabel(), 'value' => $strategy->value],
23+
Strategy::cases()
24+
);
25+
}
26+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Model\Config\Source;
9+
10+
use Magento\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
12+
use Magento\Framework\Data\OptionSourceInterface;
13+
use Magento\Framework\Exception\LocalizedException;
14+
15+
class TopLevelCategories implements OptionSourceInterface
16+
{
17+
private ?array $options = null;
18+
19+
public function __construct(private CollectionFactory $categoryCollectionFactory) {}
20+
21+
/**
22+
* @throws LocalizedException
23+
*/
24+
public function toOptionArray(): array
25+
{
26+
return $this->options ??= $this->createOptions();
27+
}
28+
29+
/**
30+
* @throws LocalizedException
31+
*/
32+
private function createOptions(): array
33+
{
34+
$collection = $this->categoryCollectionFactory->create()
35+
->setLoadProductCount(false)
36+
->addAttributeToSelect(CategoryInterface::KEY_NAME, true)
37+
->addFieldToFilter(CategoryInterface::KEY_LEVEL, [1, 2])
38+
->addOrder(CategoryInterface::KEY_PATH)
39+
->addOrder(CategoryInterface::KEY_POSITION);
40+
41+
$options = [];
42+
/** @var CategoryInterface $category */
43+
foreach ($collection->getItemsByColumnValue(CategoryInterface::KEY_LEVEL, 1) as $category) {
44+
$group = ['label' => $category->getName(), 'value' => []];
45+
foreach ($collection->getItemsByColumnValue(CategoryInterface::KEY_PARENT_ID, $category->getId()) as $child) {
46+
$group['value'][] = ['label' => $child->getName(), 'value' => $category->getId()];
47+
}
48+
49+
$options[] = $group;
50+
}
51+
52+
return $options;
53+
}
54+
}

Plugin/ProductBreadcrumbsStrategy.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Plugin;
9+
10+
use Magento\Catalog\Helper\Data;
11+
use Magento\Catalog\Model\Product;
12+
use Opengento\ProductBreadcrumbs\Model\Config\Breadcrumbs as BreadcrumbsConfig;
13+
use Opengento\ProductBreadcrumbs\Service\Breadcrumbs\CreateProductBreadcrumbsInterface;
14+
15+
class ProductBreadcrumbsStrategy
16+
{
17+
private ?array $breadcrumbs = null;
18+
19+
/**
20+
* @param CreateProductBreadcrumbsInterface[] $strategies
21+
*/
22+
public function __construct(
23+
private BreadcrumbsConfig $breadcrumbsConfig,
24+
private array $strategies = []
25+
) {}
26+
27+
public function afterGetBreadcrumbPath(Data $subject, array $result): array
28+
{
29+
$product = $subject->getProduct();
30+
31+
return $product !== null && $this->breadcrumbsConfig->isServerSideRendered($product->getStoreId())
32+
? $this->breadcrumbs ??= $this->createProductBreadCrumbs($product, $result)
33+
: $result;
34+
}
35+
36+
private function createProductBreadCrumbs(Product $product, array $defaultBreadcrumbs): array
37+
{
38+
return $this->strategies[$this->breadcrumbsConfig->getStrategy($product->getStoreId())->value]?->execute($product) ?? $defaultBreadcrumbs;
39+
}
40+
}

Provider/Category.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Provider;
9+
10+
use Magento\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\Product;
12+
use Magento\Catalog\Model\ResourceModel\Category\Collection;
13+
use Magento\Catalog\Model\ResourceModel\Helper;
14+
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
15+
use Opengento\ProductBreadcrumbs\Model\Config\Breadcrumbs as BreadcrumbsConfig;
16+
17+
use function sprintf;
18+
19+
class Category
20+
{
21+
public function __construct(
22+
private ProductResource $productResource,
23+
private BreadcrumbsConfig $breadcrumbsConfig,
24+
private Helper $helper
25+
) {}
26+
27+
public function createCollection(Product $product): Collection
28+
{
29+
$collection = $this->productResource->getCategoryCollection($product);
30+
$collection->setStoreId($product->getStoreId());
31+
$collection->setProductStoreId($product->getStoreId());
32+
$collection->setLoadProductCount(false);
33+
$collection->addFieldToSelect(CategoryInterface::KEY_PATH);
34+
$collection->addIsActiveFilter();
35+
36+
$excludedCategoryIds = $this->breadcrumbsConfig->getExcludedCategories($product->getStoreId());
37+
if ($excludedCategoryIds) {
38+
foreach ($excludedCategoryIds as $categoryId) {
39+
$collection->addFieldToFilter(
40+
CategoryInterface::KEY_PATH,
41+
['nlike' => $this->helper->escapeLikeValue(sprintf('/%s/', $categoryId), ['position' => 'any'])]
42+
);
43+
}
44+
$collection->addFieldToFilter($collection->getIdFieldName(), ['nin' => $excludedCategoryIds]);
45+
}
46+
47+
return $collection;
48+
}
49+
}

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Sales Sequence Configurable Module for Magento 2
2+
3+
[![Latest Stable Version](https://img.shields.io/packagist/v/opengento/module-product-breadcrumbs.svg?style=flat-square)](https://packagist.org/packages/opengento/module-product-breadcrumbs)
4+
[![License: MIT](https://img.shields.io/github/license/opengento/magento2-product-breadcrumbs.svg?style=flat-square)](./LICENSE)
5+
[![Packagist](https://img.shields.io/packagist/dt/opengento/module-product-breadcrumbs.svg?style=flat-square)](https://packagist.org/packages/opengento/module-product-breadcrumbs/stats)
6+
[![Packagist](https://img.shields.io/packagist/dm/opengento/module-product-breadcrumbs.svg?style=flat-square)](https://packagist.org/packages/opengento/module-product-breadcrumbs/stats)
7+
8+
This module allows to toggle server side rendered breadcrumb for product pages.
9+
10+
- [Setup](#setup)
11+
- [Composer installation](#composer-installation)
12+
- [Setup the module](#setup-the-module)
13+
- [Features](#features)
14+
- [Settings](#settings)
15+
- [Documentation](#documentation)
16+
- [Support](#support)
17+
- [Authors](#authors)
18+
- [License](#license)
19+
20+
## Setup
21+
22+
Magento 2 Open Source or Commerce edition is required.
23+
24+
### Composer installation
25+
26+
Run the following composer command:
27+
28+
```
29+
composer require opengento/module-product-breadcrumbs
30+
```
31+
32+
### Setup the module
33+
34+
Run the following magento command:
35+
36+
```
37+
bin/magento setup:upgrade
38+
```
39+
40+
**If you are in production mode, do not forget to recompile and redeploy the static resources.**
41+
42+
## Features
43+
44+
This module is Hyva-ready!
45+
This module render server-sided product breadcrumb so it's visible to any robot without requiring any js loads and process.
46+
Can you still enable the js breadcrumb so your user can see natural breadcrumbs following their navigation.
47+
48+
## Documentation
49+
50+
- Toggle render server side the product rbeadcrumbs
51+
- Toggle allows client side to override product breadcrumbs based on navigation
52+
- Select best strategy to generate the product breadcrumbs (native, deepest, shallowest)
53+
- Exclude some root categories from being used to generate the breadcrumbs
54+
55+
## Support
56+
57+
Raise a new [request](https://github.com/opengento/magento2-product-breadcrumbs/issues) to the issue tracker.
58+
59+
## Authors
60+
61+
- **Opengento Community** - *Lead* - [![Twitter Follow](https://img.shields.io/twitter/follow/opengento.svg?style=social)](https://twitter.com/opengento)
62+
- **Thomas Klein** - *Maintainer* - [![GitHub followers](https://img.shields.io/github/followers/thomas-kl1.svg?style=social)](https://github.com/thomas-kl1)
63+
- **Contributors** - *Contributor* - [![GitHub contributors](https://img.shields.io/github/contributors/opengento/magento2-product-breadcrumbs.svg?style=flat-square)](https://github.com/opengento/magento2-product-breadcrumbs/graphs/contributors)
64+
65+
## License
66+
67+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) details.
68+
69+
***That's all folks!***
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* Copyright © OpenGento, All rights reserved.
4+
* See LICENSE bundled with this library for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Opengento\ProductBreadcrumbs\Service\Breadcrumbs;
9+
10+
use Magento\Catalog\Model\Product;
11+
12+
/**
13+
* @api
14+
*/
15+
interface CreateProductBreadcrumbsInterface
16+
{
17+
public function execute(Product $product): array;
18+
}

0 commit comments

Comments
 (0)