Skip to content

Commit 11c0daf

Browse files
committed
Initial commit
1 parent 945004a commit 11c0daf

File tree

13 files changed

+723
-2
lines changed

13 files changed

+723
-2
lines changed

Helper/Data.php

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php
2+
/**
3+
* ManiyaTech
4+
*
5+
* @author Milan Maniya
6+
* @package ManiyaTech_Seo
7+
*/
8+
9+
namespace ManiyaTech\Seo\Helper;
10+
11+
use Magento\Framework\App\Helper\Context;
12+
use Magento\Catalog\Api\CategoryRepositoryInterface;
13+
use Magento\Catalog\Api\ProductRepositoryInterface;
14+
use Magento\Framework\App\Config\ScopeConfigInterface;
15+
use Magento\Framework\App\Helper\AbstractHelper;
16+
use Magento\Framework\Model\AbstractModel;
17+
use Magento\Store\Model\ScopeInterface;
18+
use Magento\Framework\Exception\NoSuchEntityException;
19+
20+
class Data extends AbstractHelper
21+
{
22+
private const XML_PATH_MODULE_ENABLED = 'seo_config/general/enabled';
23+
private const XML_PATH_PRODUCT_MODULE = 'seo_config/product/enabled';
24+
private const CATEGORY_TAGS = ['CL1', 'CL2', 'CL3', 'CL4', 'CL5', 'CL6'];
25+
26+
/**
27+
* @var ScopeConfigInterface
28+
*/
29+
protected $scopeConfig;
30+
31+
/**
32+
* @var ProductRepositoryInterface
33+
*/
34+
protected $productRepository;
35+
36+
/**
37+
* @var CategoryRepositoryInterface
38+
*/
39+
protected $categoryRepository;
40+
41+
/**
42+
* AbstractData constructor.
43+
*
44+
* @param Context $context
45+
* @param ScopeConfigInterface $scopeConfig
46+
* @param ProductRepositoryInterface $productRepository
47+
* @param CategoryRepositoryInterface $categoryRepository
48+
*/
49+
public function __construct(
50+
Context $context,
51+
ScopeConfigInterface $scopeConfig,
52+
ProductRepositoryInterface $productRepository,
53+
CategoryRepositoryInterface $categoryRepository
54+
) {
55+
$this->scopeConfig = $scopeConfig;
56+
$this->productRepository = $productRepository;
57+
$this->categoryRepository = $categoryRepository;
58+
parent::__construct($context);
59+
}
60+
61+
/**
62+
* Check if the SEO module is enabled in system configuration.
63+
*
64+
* @return bool
65+
*/
66+
public function isModuleEnabled(): bool
67+
{
68+
return (bool) $this->scopeConfig->getValue(self::XML_PATH_MODULE_ENABLED, ScopeInterface::SCOPE_STORE);
69+
}
70+
71+
/**
72+
* Check if product meta updates are enabled.
73+
*
74+
* @return bool
75+
*/
76+
public function isProductMetaEnabled(): bool
77+
{
78+
return (bool) $this->scopeConfig->getValue(self::XML_PATH_PRODUCT_MODULE, ScopeInterface::SCOPE_STORE);
79+
}
80+
81+
/**
82+
* Check if category meta updates are enabled for a specific level.
83+
*
84+
* @param int|string $level Category depth level.
85+
* @return bool
86+
*/
87+
public function isCategoryMetaEnabled($level): bool
88+
{
89+
return (bool) $this->scopeConfig->getValue(
90+
'seo_config/category' . $level . '/enabled',
91+
ScopeInterface::SCOPE_STORE
92+
);
93+
}
94+
95+
/**
96+
* Retrieve the configured meta value for a category and resolve dynamic shortcodes.
97+
*
98+
* @param string $level Category level (e.g. "1", "2", etc.)
99+
* @param string $field Meta field key (e.g. "meta_title", "meta_description")
100+
* @param AbstractModel $category Category model instance
101+
* @return string|null
102+
*/
103+
public function getCategoryMeta(string $level, string $field, AbstractModel $category): ?string
104+
{
105+
$value = $this->scopeConfig->getValue("seo_config/category{$level}/{$field}", ScopeInterface::SCOPE_STORE);
106+
return !empty($value) ? $this->shortcode($value, $category) : null;
107+
}
108+
109+
/**
110+
* Retrieve the configured meta value for a product and resolve dynamic shortcodes.
111+
*
112+
* @param string $field Meta field key (e.g. "meta_title", "meta_description")
113+
* @param AbstractModel $product Product model instance
114+
* @return string|null
115+
*/
116+
public function getProductMeta(string $field, AbstractModel $product): ?string
117+
{
118+
$value = $this->scopeConfig->getValue("seo_config/product/{$field}", ScopeInterface::SCOPE_STORE);
119+
return !empty($value) ? $this->shortcode($value, $product) : null;
120+
}
121+
122+
/**
123+
* Parse template string and replace shortcodes like [name], [sku], [CL1], etc.
124+
*
125+
* @param string $template The template string containing placeholders
126+
* @param AbstractModel $entity Product or Category instance
127+
* @return string
128+
*/
129+
public function shortcode(string $template, AbstractModel $entity): string
130+
{
131+
preg_match_all('/\[(.*?)\]/', $template, $matches);
132+
133+
if (empty($matches[1])) {
134+
return $template;
135+
}
136+
137+
$isProduct = method_exists($entity, 'getTypeId') && !empty($entity->getTypeId());
138+
139+
foreach ($matches[1] as $i => $tag) {
140+
$replacement = '';
141+
142+
if ($isProduct) {
143+
try {
144+
$product = $this->productRepository->getById((int) $entity->getId());
145+
$replacement = (string) $product->getData($tag);
146+
} catch (NoSuchEntityException $e) {
147+
$replacement = '';
148+
}
149+
} else {
150+
$replacement = $this->resolveCategoryTag($tag, $entity);
151+
}
152+
153+
$template = str_replace($matches[0][$i], $replacement, $template);
154+
}
155+
156+
return $template;
157+
}
158+
159+
/**
160+
* Resolve category-specific shortcodes like [CL1], [CL2], etc.
161+
*
162+
* @param string $tag Tag placeholder to resolve
163+
* @param AbstractModel $category Category entity
164+
* @return string
165+
*/
166+
private function resolveCategoryTag(string $tag, AbstractModel $category): string
167+
{
168+
$path = $category->getPath() ?? '';
169+
$pathArray = explode('/', $path);
170+
171+
if (in_array($tag, self::CATEGORY_TAGS, true)) {
172+
$index = (int) substr($tag, -1) + 1;
173+
174+
if (isset($pathArray[$index])) {
175+
try {
176+
$parentCategory = $this->categoryRepository->get((int) $pathArray[$index]);
177+
return $parentCategory->getName() ?? '';
178+
} catch (NoSuchEntityException $e) {
179+
return '';
180+
}
181+
}
182+
}
183+
184+
return (string) $category->getData($tag);
185+
}
186+
}

Plugin/CategoryMetaPlugin.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
/**
3+
* ManiyaTech
4+
*
5+
* @author Milan Maniya
6+
* @package ManiyaTech_Seo
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace ManiyaTech\Seo\Plugin;
12+
13+
use Magento\Catalog\Controller\Category\View as CategoryView;
14+
use Magento\Framework\Controller\ResultInterface;
15+
use Magento\Framework\View\Result\Page;
16+
use Magento\Framework\Registry;
17+
use ManiyaTech\Seo\Helper\Data as SeoHelper;
18+
use Magento\Catalog\Model\Category;
19+
20+
class CategoryMetaPlugin
21+
{
22+
/**
23+
* @var Registry
24+
*/
25+
protected $registry;
26+
27+
/**
28+
* @var SeoHelper
29+
*/
30+
protected $seoHelper;
31+
32+
/**
33+
* CategoryMetaPlugin Constructor
34+
*
35+
* @param Registry $registry
36+
* @param SeoHelper $seoHelper
37+
*/
38+
public function __construct(Registry $registry, SeoHelper $seoHelper)
39+
{
40+
$this->registry = $registry;
41+
$this->seoHelper = $seoHelper;
42+
}
43+
44+
/**
45+
* Plugin afterExecute method for Category View controller.
46+
*
47+
* @param CategoryView $subject
48+
* @param ResultInterface $result
49+
* @return ResultInterface
50+
*/
51+
public function afterExecute(CategoryView $subject, ResultInterface $result): ResultInterface
52+
{
53+
if (!$result instanceof Page) {
54+
return $result;
55+
}
56+
57+
$category = $this->registry->registry('current_category');
58+
if (!$category instanceof Category || !$category->getId()) {
59+
return $result;
60+
}
61+
62+
if (!$this->seoHelper->isModuleEnabled()) {
63+
return $result;
64+
}
65+
66+
$level = (int) $category->getLevel();
67+
68+
if (!$this->seoHelper->isCategoryMetaEnabled($level)) {
69+
return $result;
70+
}
71+
72+
$title = $this->seoHelper->getCategoryMeta((string) $level, 'meta_title', $category);
73+
$keywords = $this->seoHelper->getCategoryMeta((string) $level, 'meta_keyword', $category);
74+
$description = $this->seoHelper->getCategoryMeta((string) $level, 'meta_description', $category);
75+
76+
$pageConfig = $result->getConfig();
77+
if ($title) {
78+
$pageConfig->getTitle()->set($title);
79+
}
80+
if ($keywords) {
81+
$pageConfig->setKeywords($keywords);
82+
}
83+
if ($description) {
84+
$pageConfig->setDescription($description);
85+
}
86+
87+
return $result;
88+
}
89+
}

Plugin/ProductMetaPlugin.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* ManiyaTech
4+
*
5+
* @author Milan Maniya
6+
* @package ManiyaTech_Seo
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace ManiyaTech\Seo\Plugin;
12+
13+
use Magento\Catalog\Helper\Product as ProductHelper;
14+
use Magento\Catalog\Model\Product;
15+
use Magento\Framework\Controller\ResultInterface;
16+
use Magento\Framework\App\RequestInterface;
17+
use Magento\Framework\App\ResponseInterface;
18+
use Magento\Framework\App\ActionInterface;
19+
use Magento\Framework\DataObject;
20+
use ManiyaTech\Seo\Helper\Data as SeoHelper;
21+
22+
class ProductMetaPlugin
23+
{
24+
/**
25+
* @var SeoHelper
26+
*/
27+
protected $seoHelper;
28+
29+
/**
30+
* ProductMetaPlugin Constructor
31+
*
32+
* @param SeoHelper $seoHelper
33+
*/
34+
public function __construct(SeoHelper $seoHelper)
35+
{
36+
$this->seoHelper = $seoHelper;
37+
}
38+
39+
/**
40+
* Plugin afterInitProduct method for Product Helper.
41+
*
42+
* @param ProductHelper $subject
43+
* @param Product|null $result
44+
* @param int $productId
45+
* @param ActionInterface $controller
46+
* @param DataObject|null $params
47+
* @return Product|null
48+
*/
49+
public function afterInitProduct(
50+
ProductHelper $subject,
51+
?Product $result,
52+
int $productId,
53+
ActionInterface $controller,
54+
?DataObject $params = null
55+
): ?Product {
56+
if (!$result || !$this->seoHelper->isModuleEnabled() || !$this->seoHelper->isProductMetaEnabled()) {
57+
return $result;
58+
}
59+
60+
$metaFields = [
61+
'meta_title' => 'setMetaTitle',
62+
'meta_keyword' => 'setMetaKeyword',
63+
'meta_description' => 'setMetaDescription',
64+
];
65+
66+
foreach ($metaFields as $field => $setter) {
67+
$value = $this->seoHelper->getProductMeta($field, $result);
68+
if ($value) {
69+
$result->$setter($value);
70+
}
71+
}
72+
73+
return $result;
74+
}
75+
}

README.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,44 @@
1-
# magento2-seo
2-
Adds dynamic meta title, keywords, and description to product and category pages based on custom admin configuration to improve SEO.
1+
# ManiyaTech SEO module for Magento 2
2+
3+
The ManiyaTech Seo module for Magento 2 allows store administrators to configure and dynamically apply meta title, meta keywords, and meta description templates to both product and category pages. These templates can include attribute placeholders (e.g., [name], [price], [description]) that get replaced with real values during page rendering. This improves SEO consistency, reduces manual effort, and ensures optimized metadata across the catalog.
4+
5+
## How to install ManiyaTech_Seo module
6+
7+
### Composer Installation
8+
9+
Run the following command in Magento 2 root directory to install ManiyaTech_Seo module via composer.
10+
11+
#### Install
12+
13+
```
14+
composer require maniyatech/magento2-seo
15+
php bin/magento setup:upgrade
16+
php bin/magento setup:static-content:deploy -f
17+
```
18+
19+
#### Update
20+
21+
```
22+
composer update maniyatech/magento2-seo
23+
php bin/magento setup:upgrade
24+
php bin/magento setup:static-content:deploy -f
25+
```
26+
27+
Run below command if your store is in the production mode:
28+
29+
```
30+
php bin/magento setup:di:compile
31+
```
32+
33+
### Manual Installation
34+
35+
If you prefer to install this module manually, kindly follow the steps described below -
36+
37+
- Download the latest version [here](https://github.com/maniyatech/magento2-seo/archive/refs/heads/main.zip)
38+
- Create a folder path like this `app/code/ManiyaTech/Seo` and extract the `main.zip` file into it.
39+
- Navigate to Magento root directory and execute the below commands.
40+
41+
```
42+
php bin/magento setup:upgrade
43+
php bin/magento setup:static-content:deploy -f
44+
```

0 commit comments

Comments
 (0)