Initial shared URL engine package
Extract URL pattern management, routing, entity lifecycle handling, and schema installation from mprfriendlyurl into a shared Composer package. Both mprfriendlyurl and mprseorevolution will consume this via configurable table prefix and pattern storage abstraction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
27
composer.json
Normal file
27
composer.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "myprestarocks/prestashop-url",
|
||||||
|
"description": "Shared URL engine for PrestaShop modules - pattern management, routing, entity lifecycle",
|
||||||
|
"keywords": ["prestashop", "url", "seo", "routing", "rewrite"],
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "mypresta.rocks",
|
||||||
|
"email": "info@mypresta.rocks"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1.3"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"MyPrestaRocks\\Url\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable"
|
||||||
|
}
|
||||||
1061
src/EntityLifecycleHandler.php
Normal file
1061
src/EntityLifecycleHandler.php
Normal file
File diff suppressed because it is too large
Load Diff
338
src/Schema/UrlSchemaInstaller.php
Normal file
338
src/Schema/UrlSchemaInstaller.php
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL Schema Installer
|
||||||
|
*
|
||||||
|
* Creates and migrates URL tables (cache, history, override, pattern).
|
||||||
|
* Table prefix is configurable per module (e.g., 'mprfurl_' or 'mprseo_').
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks
|
||||||
|
* @copyright mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Url\Schema;
|
||||||
|
|
||||||
|
class UrlSchemaInstaller
|
||||||
|
{
|
||||||
|
/** @var string Table prefix (e.g., 'mprfurl_' or 'mprseo_') */
|
||||||
|
protected $tablePrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $tablePrefix
|
||||||
|
*/
|
||||||
|
public function __construct($tablePrefix)
|
||||||
|
{
|
||||||
|
$this->tablePrefix = $tablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install all URL tables
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$engine = _MYSQL_ENGINE_;
|
||||||
|
|
||||||
|
return $this->createUrlCacheTable($engine)
|
||||||
|
&& $this->createUrlHistoryTable($engine)
|
||||||
|
&& $this->createUrlOverrideTable($engine)
|
||||||
|
&& $this->createUrlPatternTable($engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall all URL tables
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute('DROP TABLE IF EXISTS `' . $prefix . 'url_cache`')
|
||||||
|
&& \Db::getInstance()->execute('DROP TABLE IF EXISTS `' . $prefix . 'url_history`')
|
||||||
|
&& \Db::getInstance()->execute('DROP TABLE IF EXISTS `' . $prefix . 'url_override`')
|
||||||
|
&& \Db::getInstance()->execute('DROP TABLE IF EXISTS `' . $prefix . 'url_pattern`');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create url_cache table
|
||||||
|
*
|
||||||
|
* @param string $engine MySQL engine
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function createUrlCacheTable($engine)
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute('
|
||||||
|
CREATE TABLE IF NOT EXISTS `' . $prefix . 'url_cache` (
|
||||||
|
`id_cache` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`entity_type` VARCHAR(50) NOT NULL,
|
||||||
|
`id_entity` INT NOT NULL,
|
||||||
|
`id_lang` INT NOT NULL,
|
||||||
|
`id_shop` INT NOT NULL,
|
||||||
|
`url` VARCHAR(500) NOT NULL,
|
||||||
|
`url_hash` CHAR(32) NOT NULL,
|
||||||
|
`full_url` VARCHAR(600) DEFAULT NULL,
|
||||||
|
`source` ENUM(\'pattern\', \'override\') DEFAULT \'pattern\',
|
||||||
|
`pattern_version` INT DEFAULT 1,
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
`date_upd` DATETIME NOT NULL,
|
||||||
|
UNIQUE KEY `unique_entity` (`entity_type`, `id_entity`, `id_lang`, `id_shop`),
|
||||||
|
INDEX `idx_url_hash` (`url_hash`),
|
||||||
|
INDEX `idx_shop_lang` (`id_shop`, `id_lang`)
|
||||||
|
) ENGINE=' . $engine . ' DEFAULT CHARSET=utf8mb4
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create url_history table
|
||||||
|
*
|
||||||
|
* @param string $engine MySQL engine
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function createUrlHistoryTable($engine)
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute('
|
||||||
|
CREATE TABLE IF NOT EXISTS `' . $prefix . 'url_history` (
|
||||||
|
`id_history` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`entity_type` VARCHAR(50) NOT NULL,
|
||||||
|
`id_entity` INT NOT NULL,
|
||||||
|
`id_lang` INT NOT NULL,
|
||||||
|
`id_shop` INT NOT NULL,
|
||||||
|
`old_url` VARCHAR(500) NOT NULL,
|
||||||
|
`old_url_hash` CHAR(32) NOT NULL,
|
||||||
|
`new_url` VARCHAR(500) DEFAULT NULL,
|
||||||
|
`redirect_type` ENUM(\'301\', \'302\', \'307\', \'410\') DEFAULT \'301\',
|
||||||
|
`redirect_enabled` TINYINT(1) DEFAULT 1,
|
||||||
|
`change_reason` ENUM(\'pattern_change\', \'manual_edit\', \'entity_update\', \'entity_delete\', \'bulk_regenerate\', \'entity_disabled\') DEFAULT \'entity_update\',
|
||||||
|
`hits` INT DEFAULT 0,
|
||||||
|
`last_hit` DATETIME DEFAULT NULL,
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
INDEX `idx_old_url_hash` (`old_url_hash`),
|
||||||
|
INDEX `idx_entity` (`entity_type`, `id_entity`, `id_lang`),
|
||||||
|
INDEX `idx_shop_enabled` (`id_shop`, `redirect_enabled`)
|
||||||
|
) ENGINE=' . $engine . ' DEFAULT CHARSET=utf8mb4
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create url_override table (full version with redirect target fields)
|
||||||
|
*
|
||||||
|
* @param string $engine MySQL engine
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function createUrlOverrideTable($engine)
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute('
|
||||||
|
CREATE TABLE IF NOT EXISTS `' . $prefix . 'url_override` (
|
||||||
|
`id_override` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`entity_type` VARCHAR(50) NOT NULL,
|
||||||
|
`id_entity` INT NOT NULL,
|
||||||
|
`id_lang` INT NOT NULL,
|
||||||
|
`id_shop` INT NOT NULL,
|
||||||
|
`id_override_group` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`custom_url` VARCHAR(500) NOT NULL DEFAULT \'\',
|
||||||
|
`custom_url_hash` CHAR(32) NOT NULL DEFAULT \'\',
|
||||||
|
`target_entity_type` VARCHAR(50) DEFAULT NULL,
|
||||||
|
`target_id_entity` INT DEFAULT 0,
|
||||||
|
`redirect_type` ENUM(\'301\', \'302\', \'307\') DEFAULT \'301\',
|
||||||
|
`target_custom_url` VARCHAR(500) DEFAULT NULL,
|
||||||
|
`locked` TINYINT(1) DEFAULT 1,
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
`date_upd` DATETIME NOT NULL,
|
||||||
|
UNIQUE KEY `unique_entity` (`entity_type`, `id_entity`, `id_lang`, `id_shop`),
|
||||||
|
UNIQUE KEY `unique_url` (`custom_url_hash`, `id_lang`, `id_shop`),
|
||||||
|
INDEX `idx_shop_lang` (`id_shop`, `id_lang`),
|
||||||
|
INDEX `idx_override_group` (`id_override_group`)
|
||||||
|
) ENGINE=' . $engine . ' DEFAULT CHARSET=utf8mb4
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create url_pattern table (for SqlPatternStorage)
|
||||||
|
*
|
||||||
|
* @param string $engine MySQL engine
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function createUrlPatternTable($engine)
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute('
|
||||||
|
CREATE TABLE IF NOT EXISTS `' . $prefix . 'url_pattern` (
|
||||||
|
`id_pattern` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`entity_type` VARCHAR(50) NOT NULL,
|
||||||
|
`id_shop` INT NOT NULL,
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
`pattern` VARCHAR(500) NOT NULL,
|
||||||
|
`suffix` VARCHAR(10) DEFAULT \'\',
|
||||||
|
`include_parent_categories` TINYINT(1) DEFAULT 0,
|
||||||
|
`category_separator` VARCHAR(10) DEFAULT \'/\',
|
||||||
|
`max_category_depth` INT DEFAULT 0,
|
||||||
|
`include_cms_category` TINYINT(1) DEFAULT 0,
|
||||||
|
`lowercase` TINYINT(1) DEFAULT 1,
|
||||||
|
`replace_spaces` VARCHAR(10) DEFAULT \'-\',
|
||||||
|
`remove_accents` TINYINT(1) DEFAULT 1,
|
||||||
|
`remove_special_chars` TINYINT(1) DEFAULT 1,
|
||||||
|
`collision_suffix` ENUM(\'id\', \'increment\', \'sku\', \'ean13\') DEFAULT \'id\',
|
||||||
|
`cache_enabled` TINYINT(1) DEFAULT 1,
|
||||||
|
`cache_ttl` INT DEFAULT 86400,
|
||||||
|
`redirect_on_delete` VARCHAR(20) DEFAULT \'parent\',
|
||||||
|
`redirect_on_disable` VARCHAR(20) DEFAULT \'parent\',
|
||||||
|
`redirect_on_url_change` VARCHAR(20) DEFAULT \'301\',
|
||||||
|
`product_redirect_target` VARCHAR(50) DEFAULT \'default_category\',
|
||||||
|
`disable_attribute_urls` TINYINT(1) DEFAULT 0,
|
||||||
|
`attribute_url_style` VARCHAR(50) DEFAULT \'group-value\',
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
`date_upd` DATETIME NOT NULL,
|
||||||
|
UNIQUE KEY `unique_type_shop` (`entity_type`, `id_shop`),
|
||||||
|
INDEX `idx_enabled` (`enabled`)
|
||||||
|
) ENGINE=' . $engine . ' DEFAULT CHARSET=utf8mb4
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate mprseorevolution's url_override table to add missing columns
|
||||||
|
* Safe to call multiple times — uses IF NOT EXISTS / column existence checks
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function migrateOverrideTable()
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
$db = \Db::getInstance();
|
||||||
|
|
||||||
|
$columns = $this->getTableColumns($prefix . 'url_override');
|
||||||
|
|
||||||
|
$success = true;
|
||||||
|
|
||||||
|
if (!in_array('id_override_group', $columns)) {
|
||||||
|
$success = $success && $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_override`
|
||||||
|
ADD COLUMN `id_override_group` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `id_shop`,
|
||||||
|
ADD INDEX `idx_override_group` (`id_override_group`)
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array('target_entity_type', $columns)) {
|
||||||
|
$success = $success && $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_override`
|
||||||
|
ADD COLUMN `target_entity_type` VARCHAR(50) DEFAULT NULL AFTER `locked`
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array('target_id_entity', $columns)) {
|
||||||
|
$success = $success && $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_override`
|
||||||
|
ADD COLUMN `target_id_entity` INT DEFAULT 0 AFTER `target_entity_type`
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array('redirect_type', $columns)) {
|
||||||
|
$success = $success && $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_override`
|
||||||
|
ADD COLUMN `redirect_type` ENUM(\'301\', \'302\', \'307\') DEFAULT \'301\' AFTER `target_id_entity`
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array('target_custom_url', $columns)) {
|
||||||
|
$success = $success && $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_override`
|
||||||
|
ADD COLUMN `target_custom_url` VARCHAR(500) DEFAULT NULL AFTER `redirect_type`
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate url_history table to add entity_disabled change_reason
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function migrateHistoryTable()
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
$db = \Db::getInstance();
|
||||||
|
|
||||||
|
// Check if entity_disabled is already in the ENUM
|
||||||
|
$columnInfo = $db->getRow('
|
||||||
|
SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = "' . pSQL($prefix) . 'url_history"
|
||||||
|
AND COLUMN_NAME = "change_reason"
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($columnInfo && strpos($columnInfo['COLUMN_TYPE'], 'entity_disabled') === false) {
|
||||||
|
return $db->execute('
|
||||||
|
ALTER TABLE `' . $prefix . 'url_history`
|
||||||
|
MODIFY COLUMN `change_reason`
|
||||||
|
ENUM(\'pattern_change\', \'manual_edit\', \'entity_update\', \'entity_delete\', \'bulk_regenerate\', \'entity_disabled\')
|
||||||
|
DEFAULT \'entity_update\'
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all migrations (safe to call multiple times)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function migrate()
|
||||||
|
{
|
||||||
|
return $this->migrateOverrideTable()
|
||||||
|
&& $this->migrateHistoryTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get column names for a table
|
||||||
|
*
|
||||||
|
* @param string $tableName Full table name (with ps_ prefix)
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getTableColumns($tableName)
|
||||||
|
{
|
||||||
|
$columns = [];
|
||||||
|
|
||||||
|
$rows = \Db::getInstance()->executeS('
|
||||||
|
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = "' . pSQL($tableName) . '"
|
||||||
|
');
|
||||||
|
|
||||||
|
foreach ($rows ?: [] as $row) {
|
||||||
|
$columns[] = $row['COLUMN_NAME'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a specific URL table exists
|
||||||
|
*
|
||||||
|
* @param string $tableSuffix e.g., 'url_cache', 'url_history'
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function tableExists($tableSuffix)
|
||||||
|
{
|
||||||
|
$prefix = _DB_PREFIX_ . pSQL($this->tablePrefix);
|
||||||
|
$tableName = $prefix . pSQL($tableSuffix);
|
||||||
|
|
||||||
|
$result = \Db::getInstance()->getValue('
|
||||||
|
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = "' . pSQL($tableName) . '"
|
||||||
|
');
|
||||||
|
|
||||||
|
return (int) $result > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/Storage/ConfigPatternStorage.php
Normal file
82
src/Storage/ConfigPatternStorage.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config-based Pattern Storage
|
||||||
|
*
|
||||||
|
* Stores URL patterns as JSON in the mpr_config table
|
||||||
|
* via MyPrestaRocks\Admin\Config\ConfigTable.
|
||||||
|
* Used by mprfriendlyurl.
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks
|
||||||
|
* @copyright mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Url\Storage;
|
||||||
|
|
||||||
|
use MyPrestaRocks\Admin\Config\ConfigTable;
|
||||||
|
|
||||||
|
class ConfigPatternStorage implements PatternStorageInterface
|
||||||
|
{
|
||||||
|
/** @var string Module name for ConfigTable scope */
|
||||||
|
protected $moduleName;
|
||||||
|
|
||||||
|
/** @var array Default patterns per entity type */
|
||||||
|
protected $defaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $moduleName Module name used as ConfigTable namespace
|
||||||
|
* @param array $defaults Default pattern configs keyed by entity type
|
||||||
|
*/
|
||||||
|
public function __construct($moduleName, array $defaults = [])
|
||||||
|
{
|
||||||
|
$this->moduleName = $moduleName;
|
||||||
|
$this->defaults = $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPattern($entityType, $idShop)
|
||||||
|
{
|
||||||
|
$configKey = 'pattern.' . $entityType;
|
||||||
|
$stored = ConfigTable::get($this->moduleName, $configKey, $idShop);
|
||||||
|
|
||||||
|
if ($stored) {
|
||||||
|
$pattern = json_decode($stored, true);
|
||||||
|
if (is_array($pattern)) {
|
||||||
|
return $pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->defaults[$entityType] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setPattern($entityType, $idShop, array $data)
|
||||||
|
{
|
||||||
|
$configKey = 'pattern.' . $entityType;
|
||||||
|
|
||||||
|
return ConfigTable::set($this->moduleName, $configKey, json_encode($data), $idShop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getAllPatterns($idShop)
|
||||||
|
{
|
||||||
|
$entityTypes = ['product', 'category', 'cms', 'cms_category', 'manufacturer', 'supplier'];
|
||||||
|
$patterns = [];
|
||||||
|
|
||||||
|
foreach ($entityTypes as $type) {
|
||||||
|
$pattern = $this->getPattern($type, $idShop);
|
||||||
|
if ($pattern) {
|
||||||
|
$patterns[$type] = $pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $patterns;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/Storage/PatternStorageInterface.php
Normal file
45
src/Storage/PatternStorageInterface.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern Storage Interface
|
||||||
|
*
|
||||||
|
* Abstraction for URL pattern configuration storage.
|
||||||
|
* Modules choose between SQL table storage (mprseorevolution)
|
||||||
|
* or JSON config storage via ConfigTable (mprfriendlyurl).
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks
|
||||||
|
* @copyright mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Url\Storage;
|
||||||
|
|
||||||
|
interface PatternStorageInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the URL pattern configuration for an entity type
|
||||||
|
*
|
||||||
|
* @param string $entityType One of: product, category, cms, cms_category, manufacturer, supplier
|
||||||
|
* @param int $idShop
|
||||||
|
* @return array|null Pattern config array or null if not set
|
||||||
|
*/
|
||||||
|
public function getPattern($entityType, $idShop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save URL pattern configuration for an entity type
|
||||||
|
*
|
||||||
|
* @param string $entityType
|
||||||
|
* @param int $idShop
|
||||||
|
* @param array $data Pattern config array
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function setPattern($entityType, $idShop, array $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all pattern configurations for a shop
|
||||||
|
*
|
||||||
|
* @param int $idShop
|
||||||
|
* @return array Keyed by entity type
|
||||||
|
*/
|
||||||
|
public function getAllPatterns($idShop);
|
||||||
|
}
|
||||||
133
src/Storage/SqlPatternStorage.php
Normal file
133
src/Storage/SqlPatternStorage.php
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL-based Pattern Storage
|
||||||
|
*
|
||||||
|
* Stores URL patterns in a dedicated SQL table with individual columns.
|
||||||
|
* Used by mprseorevolution via {prefix}_url_pattern table.
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks
|
||||||
|
* @copyright mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Url\Storage;
|
||||||
|
|
||||||
|
class SqlPatternStorage implements PatternStorageInterface
|
||||||
|
{
|
||||||
|
/** @var string Table prefix (e.g., 'mprseo_') */
|
||||||
|
protected $tablePrefix;
|
||||||
|
|
||||||
|
/** @var array Default patterns per entity type */
|
||||||
|
protected $defaults;
|
||||||
|
|
||||||
|
/** @var array Column list for the pattern table */
|
||||||
|
protected static $columns = [
|
||||||
|
'enabled', 'pattern', 'suffix',
|
||||||
|
'include_parent_categories', 'category_separator', 'max_category_depth',
|
||||||
|
'include_cms_category',
|
||||||
|
'lowercase', 'replace_spaces', 'remove_accents', 'remove_special_chars',
|
||||||
|
'collision_suffix',
|
||||||
|
'cache_enabled', 'cache_ttl',
|
||||||
|
'redirect_on_delete', 'redirect_on_disable', 'redirect_on_url_change',
|
||||||
|
'product_redirect_target',
|
||||||
|
'disable_attribute_urls', 'attribute_url_style',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $tablePrefix Table prefix (e.g., 'mprseo_')
|
||||||
|
* @param array $defaults Default pattern configs keyed by entity type
|
||||||
|
*/
|
||||||
|
public function __construct($tablePrefix, array $defaults = [])
|
||||||
|
{
|
||||||
|
$this->tablePrefix = $tablePrefix;
|
||||||
|
$this->defaults = $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPattern($entityType, $idShop)
|
||||||
|
{
|
||||||
|
$row = \Db::getInstance()->getRow('
|
||||||
|
SELECT * FROM `' . _DB_PREFIX_ . pSQL($this->tablePrefix) . 'url_pattern`
|
||||||
|
WHERE entity_type = "' . pSQL($entityType) . '"
|
||||||
|
AND id_shop = ' . (int) $idShop
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($row) {
|
||||||
|
// Convert DB row to config array (same keys as ConfigPatternStorage)
|
||||||
|
$pattern = [];
|
||||||
|
foreach (self::$columns as $col) {
|
||||||
|
if (array_key_exists($col, $row)) {
|
||||||
|
$pattern[$col] = $row[$col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->defaults[$entityType] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setPattern($entityType, $idShop, array $data)
|
||||||
|
{
|
||||||
|
$existing = \Db::getInstance()->getRow('
|
||||||
|
SELECT id_pattern FROM `' . _DB_PREFIX_ . pSQL($this->tablePrefix) . 'url_pattern`
|
||||||
|
WHERE entity_type = "' . pSQL($entityType) . '"
|
||||||
|
AND id_shop = ' . (int) $idShop
|
||||||
|
);
|
||||||
|
|
||||||
|
$dbData = [];
|
||||||
|
foreach (self::$columns as $col) {
|
||||||
|
if (array_key_exists($col, $data)) {
|
||||||
|
$dbData[$col] = pSQL($data[$col]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$dbData['date_upd'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
return \Db::getInstance()->update(
|
||||||
|
pSQL($this->tablePrefix) . 'url_pattern',
|
||||||
|
$dbData,
|
||||||
|
'id_pattern = ' . (int) $existing['id_pattern']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbData['entity_type'] = pSQL($entityType);
|
||||||
|
$dbData['id_shop'] = (int) $idShop;
|
||||||
|
$dbData['date_add'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
return \Db::getInstance()->insert(
|
||||||
|
pSQL($this->tablePrefix) . 'url_pattern',
|
||||||
|
$dbData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getAllPatterns($idShop)
|
||||||
|
{
|
||||||
|
$rows = \Db::getInstance()->executeS('
|
||||||
|
SELECT * FROM `' . _DB_PREFIX_ . pSQL($this->tablePrefix) . 'url_pattern`
|
||||||
|
WHERE id_shop = ' . (int) $idShop
|
||||||
|
);
|
||||||
|
|
||||||
|
$patterns = [];
|
||||||
|
foreach ($rows ?: [] as $row) {
|
||||||
|
$pattern = [];
|
||||||
|
foreach (self::$columns as $col) {
|
||||||
|
if (array_key_exists($col, $row)) {
|
||||||
|
$pattern[$col] = $row[$col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$patterns[$row['entity_type']] = $pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $patterns;
|
||||||
|
}
|
||||||
|
}
|
||||||
2005
src/UrlPatternManager.php
Normal file
2005
src/UrlPatternManager.php
Normal file
File diff suppressed because it is too large
Load Diff
961
src/UrlRouter.php
Normal file
961
src/UrlRouter.php
Normal file
@@ -0,0 +1,961 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL Router
|
||||||
|
*
|
||||||
|
* Handles incoming URL resolution - parses custom URLs and routes to the correct entity.
|
||||||
|
* Counterpart to UrlPatternManager (which generates URLs).
|
||||||
|
* Table prefix and module name are configurable per module.
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks
|
||||||
|
* @copyright mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Url;
|
||||||
|
|
||||||
|
class UrlRouter
|
||||||
|
{
|
||||||
|
/** @var UrlPatternManager */
|
||||||
|
protected $patternManager;
|
||||||
|
|
||||||
|
/** @var string Table prefix for URL tables */
|
||||||
|
protected $tablePrefix;
|
||||||
|
|
||||||
|
/** @var string Module name for route generation */
|
||||||
|
protected $moduleName;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $idShop;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
protected $idLang;
|
||||||
|
|
||||||
|
/** @var array Cached route patterns */
|
||||||
|
protected static $routePatterns = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param UrlPatternManager $patternManager
|
||||||
|
* @param string $tablePrefix Table prefix (e.g., 'mprfurl_' or 'mprseo_')
|
||||||
|
* @param string $moduleName Module name for route params (e.g., 'mprfriendlyurl')
|
||||||
|
* @param int|null $idShop
|
||||||
|
* @param int|null $idLang
|
||||||
|
*/
|
||||||
|
public function __construct(UrlPatternManager $patternManager, $tablePrefix, $moduleName, $idShop = null, $idLang = null)
|
||||||
|
{
|
||||||
|
$this->patternManager = $patternManager;
|
||||||
|
$this->tablePrefix = $tablePrefix;
|
||||||
|
$this->moduleName = $moduleName;
|
||||||
|
$this->idShop = $idShop ?: (int) \Context::getContext()->shop->id;
|
||||||
|
$this->idLang = $idLang ?: (int) \Context::getContext()->language->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pattern manager instance
|
||||||
|
*
|
||||||
|
* @return UrlPatternManager
|
||||||
|
*/
|
||||||
|
public function getPatternManager()
|
||||||
|
{
|
||||||
|
return $this->patternManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a URL to an entity
|
||||||
|
*
|
||||||
|
* @param string $url The request URI (without domain, with or without language prefix)
|
||||||
|
* @return array|null [entity_type, id_entity, id_lang, id_product_attribute, controller] or redirect info
|
||||||
|
*/
|
||||||
|
public function resolve($url)
|
||||||
|
{
|
||||||
|
$url = $this->normalizeUrl($url);
|
||||||
|
|
||||||
|
if (empty($url)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check URL cache for exact match (fastest)
|
||||||
|
$cached = $this->lookupCachedUrl($url);
|
||||||
|
if ($cached) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check custom overrides
|
||||||
|
$override = $this->lookupOverride($url);
|
||||||
|
if ($override) {
|
||||||
|
return $override;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check URL history for redirects
|
||||||
|
$redirect = $this->patternManager->findRedirect($url, $this->idLang);
|
||||||
|
if ($redirect) {
|
||||||
|
return [
|
||||||
|
'redirect' => true,
|
||||||
|
'redirect_url' => $redirect['new_url'],
|
||||||
|
'redirect_type' => $redirect['redirect_type'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Try pattern matching (slowest, but handles dynamic URLs)
|
||||||
|
$matched = $this->matchPattern($url);
|
||||||
|
if ($matched) {
|
||||||
|
return $matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize URL for matching
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function normalizeUrl($url)
|
||||||
|
{
|
||||||
|
// Remove query string
|
||||||
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
|
$url = substr($url, 0, $pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = ltrim($url, '/');
|
||||||
|
|
||||||
|
// Remove language prefix if present
|
||||||
|
$languages = \Language::getLanguages(true, $this->idShop);
|
||||||
|
foreach ($languages as $lang) {
|
||||||
|
$prefix = $lang['iso_code'] . '/';
|
||||||
|
if (strpos($url, $prefix) === 0) {
|
||||||
|
$url = substr($url, strlen($prefix));
|
||||||
|
$this->idLang = (int) $lang['id_lang'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = rtrim($url, '/');
|
||||||
|
|
||||||
|
// Remove common suffixes for matching
|
||||||
|
$suffixes = ['.html', '.htm', '.php'];
|
||||||
|
foreach ($suffixes as $suffix) {
|
||||||
|
if (substr($url, -strlen($suffix)) === $suffix) {
|
||||||
|
$url = substr($url, 0, -strlen($suffix));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtolower($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup URL in cache table
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function lookupCachedUrl($url)
|
||||||
|
{
|
||||||
|
$urlVariants = [$url, $url . '.html', $url . '/'];
|
||||||
|
|
||||||
|
foreach ($urlVariants as $variant) {
|
||||||
|
$hash = md5($variant);
|
||||||
|
|
||||||
|
$result = \Db::getInstance()->getRow('
|
||||||
|
SELECT entity_type, id_entity, id_lang
|
||||||
|
FROM `' . _DB_PREFIX_ . $this->tablePrefix . 'url_cache`
|
||||||
|
WHERE url_hash = "' . pSQL($hash) . '"
|
||||||
|
AND id_shop = ' . (int) $this->idShop . '
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
return [
|
||||||
|
'entity_type' => $result['entity_type'],
|
||||||
|
'id_entity' => (int) $result['id_entity'],
|
||||||
|
'id_lang' => (int) $result['id_lang'],
|
||||||
|
'controller' => $this->getControllerForEntityType($result['entity_type']),
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup URL in overrides table
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function lookupOverride($url)
|
||||||
|
{
|
||||||
|
$urlVariants = [$url, $url . '.html', $url . '/'];
|
||||||
|
|
||||||
|
foreach ($urlVariants as $variant) {
|
||||||
|
$hash = md5($variant);
|
||||||
|
|
||||||
|
$result = \Db::getInstance()->getRow('
|
||||||
|
SELECT entity_type, id_entity, id_lang,
|
||||||
|
target_entity_type, target_id_entity, redirect_type, target_custom_url
|
||||||
|
FROM `' . _DB_PREFIX_ . $this->tablePrefix . 'url_override`
|
||||||
|
WHERE custom_url_hash = "' . pSQL($hash) . '"
|
||||||
|
AND id_shop = ' . (int) $this->idShop . '
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
// Redirect to target entity
|
||||||
|
if (!empty($result['target_entity_type']) && !empty($result['target_id_entity'])) {
|
||||||
|
$targetUrl = $this->resolveEntityUrl(
|
||||||
|
$result['target_entity_type'],
|
||||||
|
(int) $result['target_id_entity'],
|
||||||
|
(int) $result['id_lang']
|
||||||
|
);
|
||||||
|
if ($targetUrl) {
|
||||||
|
return [
|
||||||
|
'redirect' => true,
|
||||||
|
'redirect_url' => $targetUrl,
|
||||||
|
'redirect_type' => $result['redirect_type'] ?: '301',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to custom URL
|
||||||
|
if (!empty($result['target_custom_url'])) {
|
||||||
|
return [
|
||||||
|
'redirect' => true,
|
||||||
|
'redirect_url' => $result['target_custom_url'],
|
||||||
|
'redirect_type' => $result['redirect_type'] ?: '301',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy override: serve entity at custom URL
|
||||||
|
return [
|
||||||
|
'entity_type' => $result['entity_type'],
|
||||||
|
'id_entity' => (int) $result['id_entity'],
|
||||||
|
'id_lang' => (int) $result['id_lang'],
|
||||||
|
'controller' => $this->getControllerForEntityType($result['entity_type']),
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a target entity to its front-office URL (relative path)
|
||||||
|
*
|
||||||
|
* @param string $entityType
|
||||||
|
* @param int $idEntity
|
||||||
|
* @param int $idLang
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function resolveEntityUrl($entityType, $idEntity, $idLang)
|
||||||
|
{
|
||||||
|
$context = \Context::getContext();
|
||||||
|
$link = $context->link;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($entityType) {
|
||||||
|
case 'product':
|
||||||
|
$url = $link->getProductLink($idEntity, null, null, null, $idLang);
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
$url = $link->getCategoryLink($idEntity, null, $idLang);
|
||||||
|
break;
|
||||||
|
case 'cms':
|
||||||
|
$cmsObj = new \CMS($idEntity, $idLang);
|
||||||
|
$url = $link->getCMSLink($cmsObj, null, null, $idLang);
|
||||||
|
break;
|
||||||
|
case 'manufacturer':
|
||||||
|
$url = $link->getManufacturerLink($idEntity, null, $idLang);
|
||||||
|
break;
|
||||||
|
case 'supplier':
|
||||||
|
$url = $link->getSupplierLink($idEntity, null, $idLang);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseUrl = $context->shop->getBaseURL(true);
|
||||||
|
if (strpos($url, $baseUrl) === 0) {
|
||||||
|
return ltrim(substr($url, strlen($baseUrl)), '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ltrim(parse_url($url, PHP_URL_PATH), '/');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to match URL against patterns
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function matchPattern($url)
|
||||||
|
{
|
||||||
|
$entityTypes = UrlPatternManager::getSupportedEntityTypes();
|
||||||
|
|
||||||
|
foreach ($entityTypes as $entityType) {
|
||||||
|
if (!$this->patternManager->isEnabled($entityType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $this->patternManager->getPattern($entityType);
|
||||||
|
$regex = $this->patternToRegex($pattern['pattern'], $entityType);
|
||||||
|
|
||||||
|
if (preg_match($regex, $url, $matches)) {
|
||||||
|
$entity = $this->findEntityByPatternMatch($entityType, $matches);
|
||||||
|
if ($entity) {
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert pattern to regex for matching
|
||||||
|
*
|
||||||
|
* @param string $pattern
|
||||||
|
* @param string $entityType
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function patternToRegex($pattern, $entityType)
|
||||||
|
{
|
||||||
|
$cacheKey = $this->tablePrefix . $entityType . '_' . md5($pattern);
|
||||||
|
|
||||||
|
if (isset(self::$routePatterns[$cacheKey])) {
|
||||||
|
return self::$routePatterns[$cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
$regex = preg_quote($pattern, '#');
|
||||||
|
|
||||||
|
$placeholders = [
|
||||||
|
'\\{id\\}' => '(?P<id>[0-9]+)',
|
||||||
|
'\\{name\\}' => '(?P<name>[a-z0-9\-]+)',
|
||||||
|
'\\{rewrite\\}' => '(?P<name>[a-z0-9\-]+)',
|
||||||
|
'\\{reference\\}' => '(?P<reference>[a-z0-9\-]+)',
|
||||||
|
'\\{ean13\\}' => '(?P<ean13>[0-9]{13})?',
|
||||||
|
'\\{upc\\}' => '(?P<upc>[0-9]{12})?',
|
||||||
|
'\\{category\\}' => '(?P<category>[a-z0-9\-]+)',
|
||||||
|
'\\{categories\\}' => '(?P<categories>[a-z0-9\-\/]+)',
|
||||||
|
'\\{brand\\}' => '(?P<brand>[a-z0-9\-]+)',
|
||||||
|
'\\{supplier\\}' => '(?P<supplier>[a-z0-9\-]+)',
|
||||||
|
'\\{parent\\}' => '(?P<parent>[a-z0-9\-]+)',
|
||||||
|
'\\{parents\\}' => '(?P<parents>[a-z0-9\-\/]+)',
|
||||||
|
'\\{id_product_attribute\\}' => '(?P<id_product_attribute>[0-9]+)?',
|
||||||
|
'\\{attributes\\}' => '(?P<attributes>[a-z0-9\-]+)?',
|
||||||
|
'\\{tags\\}' => '(?P<tags>[a-z0-9\-]+)?',
|
||||||
|
'\\{meta_keywords\\}' => '(?P<meta_keywords>[a-z0-9\-]+)?',
|
||||||
|
'\\{meta_title\\}' => '(?P<meta_title>[a-z0-9\-]+)?',
|
||||||
|
];
|
||||||
|
|
||||||
|
$regex = preg_replace('#\\\\\\{attribute\\:[^}]+\\\\\\}#', '(?P<attribute_value>[a-z0-9\-]+)?', $regex);
|
||||||
|
|
||||||
|
foreach ($placeholders as $placeholder => $replacement) {
|
||||||
|
$regex = preg_replace('#' . $placeholder . '#', $replacement, $regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
$regex = preg_replace('#\\\\\\{[^}]+\\\\\\}#', '[a-z0-9\-]*', $regex);
|
||||||
|
|
||||||
|
$regex = '#^' . $regex . '$#i';
|
||||||
|
|
||||||
|
self::$routePatterns[$cacheKey] = $regex;
|
||||||
|
|
||||||
|
return $regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find entity by pattern match data
|
||||||
|
*
|
||||||
|
* @param string $entityType
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findEntityByPatternMatch($entityType, array $matches)
|
||||||
|
{
|
||||||
|
switch ($entityType) {
|
||||||
|
case 'product':
|
||||||
|
return $this->findProductByMatch($matches);
|
||||||
|
case 'category':
|
||||||
|
return $this->findCategoryByMatch($matches);
|
||||||
|
case 'cms':
|
||||||
|
return $this->findCmsByMatch($matches);
|
||||||
|
case 'cms_category':
|
||||||
|
return $this->findCmsCategoryByMatch($matches);
|
||||||
|
case 'manufacturer':
|
||||||
|
return $this->findManufacturerByMatch($matches);
|
||||||
|
case 'supplier':
|
||||||
|
return $this->findSupplierByMatch($matches);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find product by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findProductByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
$idProduct = (int) $matches['id'];
|
||||||
|
if ($this->productExists($idProduct)) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'product',
|
||||||
|
'id_entity' => $idProduct,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'product',
|
||||||
|
'id_product_attribute' => !empty($matches['id_product_attribute']) ? (int) $matches['id_product_attribute'] : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$sql = '
|
||||||
|
SELECT p.id_product
|
||||||
|
FROM `' . _DB_PREFIX_ . 'product` p
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'product_shop` ps ON ps.id_product = p.id_product AND ps.id_shop = ' . (int) $this->idShop . '
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON pl.id_product = p.id_product AND pl.id_lang = ' . (int) $this->idLang . ' AND pl.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE pl.link_rewrite = "' . pSQL($matches['name']) . '"
|
||||||
|
AND ps.active = 1
|
||||||
|
';
|
||||||
|
|
||||||
|
if (!empty($matches['category'])) {
|
||||||
|
$sql .= ' AND EXISTS (
|
||||||
|
SELECT 1 FROM `' . _DB_PREFIX_ . 'category_lang` cl
|
||||||
|
WHERE cl.id_category = p.id_category_default
|
||||||
|
AND cl.id_lang = ' . (int) $this->idLang . '
|
||||||
|
AND cl.link_rewrite = "' . pSQL($matches['category']) . '"
|
||||||
|
)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['brand'])) {
|
||||||
|
$sql .= ' AND EXISTS (
|
||||||
|
SELECT 1 FROM `' . _DB_PREFIX_ . 'manufacturer` m
|
||||||
|
WHERE m.id_manufacturer = p.id_manufacturer
|
||||||
|
AND LOWER(REPLACE(m.name, " ", "-")) = "' . pSQL($matches['brand']) . '"
|
||||||
|
)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' LIMIT 1';
|
||||||
|
|
||||||
|
$idProduct = (int) \Db::getInstance()->getValue($sql);
|
||||||
|
|
||||||
|
if ($idProduct) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'product',
|
||||||
|
'id_entity' => $idProduct,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'product',
|
||||||
|
'id_product_attribute' => !empty($matches['id_product_attribute']) ? (int) $matches['id_product_attribute'] : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['ean13'])) {
|
||||||
|
$idProduct = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT p.id_product
|
||||||
|
FROM `' . _DB_PREFIX_ . 'product` p
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'product_shop` ps ON ps.id_product = p.id_product AND ps.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE p.ean13 = "' . pSQL($matches['ean13']) . '"
|
||||||
|
AND ps.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idProduct) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'product',
|
||||||
|
'id_entity' => $idProduct,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'product',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['reference'])) {
|
||||||
|
$idProduct = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT p.id_product
|
||||||
|
FROM `' . _DB_PREFIX_ . 'product` p
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'product_shop` ps ON ps.id_product = p.id_product AND ps.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE LOWER(p.reference) = "' . pSQL(strtolower($matches['reference'])) . '"
|
||||||
|
AND ps.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idProduct) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'product',
|
||||||
|
'id_entity' => $idProduct,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'product',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find category by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findCategoryByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
$idCategory = (int) $matches['id'];
|
||||||
|
if ($this->categoryExists($idCategory)) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'category',
|
||||||
|
'id_entity' => $idCategory,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'category',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$sql = '
|
||||||
|
SELECT c.id_category
|
||||||
|
FROM `' . _DB_PREFIX_ . 'category` c
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'category_shop` cs ON cs.id_category = c.id_category AND cs.id_shop = ' . (int) $this->idShop . '
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON cl.id_category = c.id_category AND cl.id_lang = ' . (int) $this->idLang . ' AND cl.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE cl.link_rewrite = "' . pSQL($matches['name']) . '"
|
||||||
|
AND c.active = 1
|
||||||
|
';
|
||||||
|
|
||||||
|
if (!empty($matches['parent'])) {
|
||||||
|
$sql .= ' AND EXISTS (
|
||||||
|
SELECT 1 FROM `' . _DB_PREFIX_ . 'category_lang` pcl
|
||||||
|
WHERE pcl.id_category = c.id_parent
|
||||||
|
AND pcl.id_lang = ' . (int) $this->idLang . '
|
||||||
|
AND pcl.link_rewrite = "' . pSQL($matches['parent']) . '"
|
||||||
|
)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' LIMIT 1';
|
||||||
|
|
||||||
|
$idCategory = (int) \Db::getInstance()->getValue($sql);
|
||||||
|
|
||||||
|
if ($idCategory) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'category',
|
||||||
|
'id_entity' => $idCategory,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'category',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find CMS page by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findCmsByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
$idCms = (int) $matches['id'];
|
||||||
|
if ($this->cmsExists($idCms)) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'cms',
|
||||||
|
'id_entity' => $idCms,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'cms',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$idCms = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT c.id_cms
|
||||||
|
FROM `' . _DB_PREFIX_ . 'cms` c
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'cms_shop` cs ON cs.id_cms = c.id_cms AND cs.id_shop = ' . (int) $this->idShop . '
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'cms_lang` cl ON cl.id_cms = c.id_cms AND cl.id_lang = ' . (int) $this->idLang . ' AND cl.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE cl.link_rewrite = "' . pSQL($matches['name']) . '"
|
||||||
|
AND c.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idCms) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'cms',
|
||||||
|
'id_entity' => $idCms,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'cms',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find CMS category by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findCmsCategoryByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'cms_category',
|
||||||
|
'id_entity' => (int) $matches['id'],
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'cms',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$idCmsCategory = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT cc.id_cms_category
|
||||||
|
FROM `' . _DB_PREFIX_ . 'cms_category` cc
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'cms_category_shop` ccs ON ccs.id_cms_category = cc.id_cms_category AND ccs.id_shop = ' . (int) $this->idShop . '
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'cms_category_lang` ccl ON ccl.id_cms_category = cc.id_cms_category AND ccl.id_lang = ' . (int) $this->idLang . ' AND ccl.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE ccl.link_rewrite = "' . pSQL($matches['name']) . '"
|
||||||
|
AND cc.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idCmsCategory) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'cms_category',
|
||||||
|
'id_entity' => $idCmsCategory,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'cms',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find manufacturer by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findManufacturerByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'manufacturer',
|
||||||
|
'id_entity' => (int) $matches['id'],
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'manufacturer',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$idManufacturer = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT m.id_manufacturer
|
||||||
|
FROM `' . _DB_PREFIX_ . 'manufacturer` m
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'manufacturer_shop` ms ON ms.id_manufacturer = m.id_manufacturer AND ms.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE LOWER(REPLACE(REPLACE(m.name, " ", "-"), "\'", "")) = "' . pSQL($matches['name']) . '"
|
||||||
|
AND m.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idManufacturer) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'manufacturer',
|
||||||
|
'id_entity' => $idManufacturer,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'manufacturer',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find supplier by pattern match
|
||||||
|
*
|
||||||
|
* @param array $matches
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function findSupplierByMatch(array $matches)
|
||||||
|
{
|
||||||
|
if (!empty($matches['id'])) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'supplier',
|
||||||
|
'id_entity' => (int) $matches['id'],
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'supplier',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matches['name'])) {
|
||||||
|
$idSupplier = (int) \Db::getInstance()->getValue('
|
||||||
|
SELECT s.id_supplier
|
||||||
|
FROM `' . _DB_PREFIX_ . 'supplier` s
|
||||||
|
INNER JOIN `' . _DB_PREFIX_ . 'supplier_shop` ss ON ss.id_supplier = s.id_supplier AND ss.id_shop = ' . (int) $this->idShop . '
|
||||||
|
WHERE LOWER(REPLACE(REPLACE(s.name, " ", "-"), "\'", "")) = "' . pSQL($matches['name']) . '"
|
||||||
|
AND s.active = 1
|
||||||
|
LIMIT 1
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($idSupplier) {
|
||||||
|
return [
|
||||||
|
'entity_type' => 'supplier',
|
||||||
|
'id_entity' => $idSupplier,
|
||||||
|
'id_lang' => $this->idLang,
|
||||||
|
'controller' => 'supplier',
|
||||||
|
'id_product_attribute' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if product exists
|
||||||
|
*
|
||||||
|
* @param int $idProduct
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function productExists($idProduct)
|
||||||
|
{
|
||||||
|
return (bool) \Db::getInstance()->getValue(
|
||||||
|
'SELECT 1 FROM `' . _DB_PREFIX_ . 'product_shop`
|
||||||
|
WHERE id_product = ' . (int) $idProduct . '
|
||||||
|
AND id_shop = ' . (int) $this->idShop . '
|
||||||
|
AND active = 1'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if category exists
|
||||||
|
*
|
||||||
|
* @param int $idCategory
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function categoryExists($idCategory)
|
||||||
|
{
|
||||||
|
return (bool) \Db::getInstance()->getValue(
|
||||||
|
'SELECT 1 FROM `' . _DB_PREFIX_ . 'category_shop`
|
||||||
|
WHERE id_category = ' . (int) $idCategory . '
|
||||||
|
AND id_shop = ' . (int) $this->idShop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if CMS page exists
|
||||||
|
*
|
||||||
|
* @param int $idCms
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function cmsExists($idCms)
|
||||||
|
{
|
||||||
|
return (bool) \Db::getInstance()->getValue(
|
||||||
|
'SELECT 1 FROM `' . _DB_PREFIX_ . 'cms_shop`
|
||||||
|
WHERE id_cms = ' . (int) $idCms . '
|
||||||
|
AND id_shop = ' . (int) $this->idShop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get controller name for entity type
|
||||||
|
*
|
||||||
|
* @param string $entityType
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getControllerForEntityType($entityType)
|
||||||
|
{
|
||||||
|
$controllers = [
|
||||||
|
'product' => 'product',
|
||||||
|
'category' => 'category',
|
||||||
|
'cms' => 'cms',
|
||||||
|
'cms_category' => 'cms',
|
||||||
|
'manufacturer' => 'manufacturer',
|
||||||
|
'supplier' => 'supplier',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $controllers[$entityType] ?? 'index';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate PrestaShop-compatible route definitions for moduleRoutes hook
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function generateModuleRoutes()
|
||||||
|
{
|
||||||
|
$routes = [];
|
||||||
|
$entityTypes = UrlPatternManager::getSupportedEntityTypes();
|
||||||
|
$routePrefix = str_replace('_', '', $this->tablePrefix);
|
||||||
|
|
||||||
|
foreach ($entityTypes as $entityType) {
|
||||||
|
if (!$this->patternManager->isEnabled($entityType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $this->patternManager->getPattern($entityType);
|
||||||
|
$routeRule = $this->patternToRouteRule($pattern['pattern'], $pattern);
|
||||||
|
|
||||||
|
$controller = $this->getControllerForEntityType($entityType);
|
||||||
|
|
||||||
|
$languages = \Language::getLanguages(true, $this->idShop);
|
||||||
|
foreach ($languages as $lang) {
|
||||||
|
$routeKey = $routePrefix . '_' . $entityType . '_' . $lang['iso_code'];
|
||||||
|
|
||||||
|
$routes[$routeKey] = [
|
||||||
|
'controller' => $controller,
|
||||||
|
'rule' => $lang['iso_code'] . '/' . $routeRule,
|
||||||
|
'keywords' => $this->getRouteKeywords($entityType),
|
||||||
|
'params' => [
|
||||||
|
'fc' => 'module',
|
||||||
|
'module' => $this->moduleName,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert pattern to PrestaShop route rule
|
||||||
|
*
|
||||||
|
* @param string $pattern
|
||||||
|
* @param array $config
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function patternToRouteRule($pattern, array $config)
|
||||||
|
{
|
||||||
|
$conversions = [
|
||||||
|
'{id}' => '{id}',
|
||||||
|
'{name}' => '{rewrite}',
|
||||||
|
'{rewrite}' => '{rewrite}',
|
||||||
|
'{category}' => '{category}',
|
||||||
|
'{categories}' => '{categories}',
|
||||||
|
'{brand}' => '{manufacturer}',
|
||||||
|
'{supplier}' => '{supplier}',
|
||||||
|
'{ean13}' => '{ean13}',
|
||||||
|
'{reference}' => '{reference}',
|
||||||
|
'{parent}' => '{parent}',
|
||||||
|
'{parents}' => '{parents}',
|
||||||
|
'{id_product_attribute}' => '{id_product_attribute}',
|
||||||
|
'{attributes}' => '{attributes}',
|
||||||
|
];
|
||||||
|
|
||||||
|
$rule = $pattern;
|
||||||
|
foreach ($conversions as $from => $to) {
|
||||||
|
$rule = str_replace($from, $to, $rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule = preg_replace('/\{attribute:[^}]+\}/', '', $rule);
|
||||||
|
$rule = preg_replace('/\{[^}]+\}/', '', $rule);
|
||||||
|
|
||||||
|
if (!empty($config['suffix'])) {
|
||||||
|
$rule .= $config['suffix'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule = preg_replace('#/+#', '/', $rule);
|
||||||
|
$rule = trim($rule, '/');
|
||||||
|
|
||||||
|
return $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get route keywords for entity type
|
||||||
|
*
|
||||||
|
* @param string $entityType
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRouteKeywords($entityType)
|
||||||
|
{
|
||||||
|
$baseKeywords = [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_' . $entityType],
|
||||||
|
'rewrite' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*', 'param' => 'rewrite'],
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($entityType) {
|
||||||
|
case 'product':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_product'],
|
||||||
|
'category' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'categories' => ['regexp' => '[_a-zA-Z0-9\pL\pS\/-]*'],
|
||||||
|
'ean13' => ['regexp' => '[0-9]*'],
|
||||||
|
'reference' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'manufacturer' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'supplier' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'id_product_attribute' => ['regexp' => '[0-9]*', 'param' => 'id_product_attribute'],
|
||||||
|
'attributes' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'price' => ['regexp' => '[0-9\-\.]*'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'tags' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
case 'category':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_category'],
|
||||||
|
'parent' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'parents' => ['regexp' => '[_a-zA-Z0-9\pL\pS\/-]*'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
case 'cms':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_cms'],
|
||||||
|
'category' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'categories' => ['regexp' => '[_a-zA-Z0-9\pL\pS\/-]*'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
case 'cms_category':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_cms_category'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
case 'manufacturer':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_manufacturer'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
case 'supplier':
|
||||||
|
return array_merge($baseKeywords, [
|
||||||
|
'id' => ['regexp' => '[0-9]+', 'param' => 'id_supplier'],
|
||||||
|
'meta_title' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9\pL\pS-]*'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $baseKeywords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user