diff --git a/.rector.php b/.rector.php
index 9ec431be384..93f6803053c 100644
--- a/.rector.php
+++ b/.rector.php
@@ -45,7 +45,7 @@
false,
false,
false,
- false,
+ true,
false,
false,
false,
diff --git a/app/Mage.php b/app/Mage.php
index dd016e03030..af8e1224b38 100644
--- a/app/Mage.php
+++ b/app/Mage.php
@@ -781,7 +781,7 @@ public static function run($code = '', $type = 'store', $options = [])
*
* @param array $options
*/
- protected static function _setIsInstalled($options = [])
+ private static function _setIsInstalled($options = [])
{
if (isset($options['is_installed']) && $options['is_installed']) {
self::$_isInstalled = true;
@@ -793,7 +793,7 @@ protected static function _setIsInstalled($options = [])
*
* @param array $options
*/
- protected static function _setConfigModel($options = [])
+ private static function _setConfigModel($options = [])
{
if (isset($options['config_model']) && class_exists($options['config_model'])) {
$alternativeConfigModelName = $options['config_model'];
diff --git a/app/code/core/Mage/Bundle/Model/Product/Type.php b/app/code/core/Mage/Bundle/Model/Product/Type.php
index b460eb8d201..028a1a916ae 100644
--- a/app/code/core/Mage/Bundle/Model/Product/Type.php
+++ b/app/code/core/Mage/Bundle/Model/Product/Type.php
@@ -317,7 +317,7 @@ public function save($product = null)
$selection['selection_id'] = $selectionModel->getSelectionId();
- if ($selectionModel->getSelectionId()) {
+ if ($selectionModel->getSelectionId() && !$selectionModel->isDeleted()) {
$excludeSelectionIds[] = $selectionModel->getSelectionId();
$usedProductIds[] = $selectionModel->getProductId();
}
diff --git a/app/code/core/Mage/Catalog/Helper/Product/Url.php b/app/code/core/Mage/Catalog/Helper/Product/Url.php
index 80f726dc636..4355433ce36 100644
--- a/app/code/core/Mage/Catalog/Helper/Product/Url.php
+++ b/app/code/core/Mage/Catalog/Helper/Product/Url.php
@@ -85,6 +85,10 @@ class Mage_Catalog_Helper_Product_Url extends Mage_Core_Helper_Url
'צ' => 'c', 'ק' => 'q', 'ר' => 'r', 'ש' => 'w', 'ת' => 't', '™' => 'tm',
];
+ protected array $_convertTableShort = ['@' => 'at', '©' => 'c', '®' => 'r', '™' => 'tm'];
+
+ protected array $_convertTableCustom = [];
+
/**
* Check additional instruction for conversion table in configuration
*/
@@ -93,7 +97,9 @@ public function __construct()
$convertNode = Mage::getConfig()->getNode('default/url/convert');
if ($convertNode) {
foreach ($convertNode->children() as $node) {
- $this->_convertTable[(string) $node->from] = (string) $node->to;
+ if (property_exists($node, 'from') && property_exists($node, 'to')) {
+ $this->_convertTableCustom[(string) $node->from] = (string) $node->to;
+ }
}
}
}
@@ -105,7 +111,17 @@ public function __construct()
*/
public function getConvertTable()
{
- return $this->_convertTable;
+ return $this->_convertTable + $this->_convertTableShort + $this->_convertTableCustom;
+ }
+
+ public function getConvertTableCustom(): array
+ {
+ return $this->_convertTableCustom;
+ }
+
+ public function getConvertTableShort(): array
+ {
+ return $this->_convertTableShort + $this->_convertTableCustom;
}
/**
@@ -113,6 +129,8 @@ public function getConvertTable()
*
* @param string $string
* @return string
+ * @deprecated
+ * @see Mage_Catalog_Model_Url::formatUrlKey()
*/
public function format($string)
{
diff --git a/app/code/core/Mage/Catalog/Model/Attribute/Backend/Urlkey/Abstract.php b/app/code/core/Mage/Catalog/Model/Attribute/Backend/Urlkey/Abstract.php
index 5b9f39353f2..4d37db0d85e 100644
--- a/app/code/core/Mage/Catalog/Model/Attribute/Backend/Urlkey/Abstract.php
+++ b/app/code/core/Mage/Catalog/Model/Attribute/Backend/Urlkey/Abstract.php
@@ -40,6 +40,11 @@ public function beforeSave($object)
$urlKey = $object->getName();
}
+ if (method_exists($object, 'setLocale')) {
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $object->getStoreId());
+ $object->setLocale($locale);
+ }
+
$object->setData($attributeName, $object->formatUrlKey($urlKey));
return $this;
diff --git a/app/code/core/Mage/Catalog/Model/Category.php b/app/code/core/Mage/Catalog/Model/Category.php
index c7f913cee6c..4a1e89bc017 100644
--- a/app/code/core/Mage/Catalog/Model/Category.php
+++ b/app/code/core/Mage/Catalog/Model/Category.php
@@ -163,6 +163,9 @@ class Mage_Catalog_Model_Category extends Mage_Catalog_Model_Abstract
*/
protected $_urlModel;
+
+ protected ?string $locale = null;
+
/**
* Initialize resource mode
*/
@@ -501,9 +504,10 @@ public function getUrlModel()
public function getCategoryIdUrl()
{
Varien_Profiler::start('REGULAR: ' . __METHOD__);
- $urlKey = $this->getUrlKey() ? $this->getUrlKey() : $this->formatUrlKey($this->getName());
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $this->getStoreId());
+ $urlKey = $this->getUrlKey() ? $this->getUrlKey() : $this->setLocale($locale)->formatUrlKey($this->getName());
$url = $this->getUrlInstance()->getUrl('catalog/category/view', [
- 's' => $urlKey,
+ 's' => $urlKey,
'id' => $this->getId(),
]);
Varien_Profiler::stop('REGULAR: ' . __METHOD__);
@@ -518,11 +522,18 @@ public function getCategoryIdUrl()
*/
public function formatUrlKey($str)
{
- $str = Mage::helper('catalog/product_url')->format($str);
- $urlKey = preg_replace('#[^0-9a-z]+#i', '-', $str);
- $urlKey = strtolower($urlKey);
- $urlKey = trim($urlKey, '-');
- return $urlKey;
+ return $this->getUrlModel()->setLocale($this->getLocale())->formatUrlKey($str);
+ }
+
+ public function getLocale(): ?string
+ {
+ return $this->locale;
+ }
+
+ public function setLocale(?string $locale)
+ {
+ $this->locale = $locale;
+ return $this;
}
/**
diff --git a/app/code/core/Mage/Catalog/Model/Category/Url.php b/app/code/core/Mage/Catalog/Model/Category/Url.php
index 7c2994041e2..968393c65bf 100644
--- a/app/code/core/Mage/Catalog/Model/Category/Url.php
+++ b/app/code/core/Mage/Catalog/Model/Category/Url.php
@@ -15,34 +15,13 @@
*/
/**
- * Catalog category url
+ * Catalog Url model
*
* @category Mage
* @package Mage_Catalog
*/
-class Mage_Catalog_Model_Category_Url
+class Mage_Catalog_Model_Category_Url extends Mage_Catalog_Model_Url
{
- /**
- * Url instance
- *
- * @var Mage_Core_Model_Url
- */
- protected $_url;
-
- /**
- * Factory instance
- *
- * @var Mage_Catalog_Model_Factory
- */
- protected $_factory;
-
- /**
- * Url rewrite instance
- *
- * @var Mage_Core_Model_Url_Rewrite
- */
- protected $_urlRewrite;
-
/**
* Initialize Url model
*/
@@ -113,32 +92,4 @@ protected function _getRequestPath(Mage_Catalog_Model_Category $category)
}
return false;
}
-
- /**
- * Retrieve Url instance
- *
- * @return Mage_Core_Model_Url
- */
- public function getUrlInstance()
- {
- if ($this->_url === null) {
- /** @var Mage_Core_Model_Url $model */
- $model = $this->_factory->getModel('core/url');
- $this->_url = $model;
- }
- return $this->_url;
- }
-
- /**
- * Retrieve Url rewrite instance
- *
- * @return Mage_Core_Model_Url_Rewrite
- */
- public function getUrlRewrite()
- {
- if ($this->_urlRewrite === null) {
- $this->_urlRewrite = $this->_factory->getUrlRewriteInstance();
- }
- return $this->_urlRewrite;
- }
}
diff --git a/app/code/core/Mage/Catalog/Model/Product.php b/app/code/core/Mage/Catalog/Model/Product.php
index ac9586b2447..e702cb4dd35 100644
--- a/app/code/core/Mage/Catalog/Model/Product.php
+++ b/app/code/core/Mage/Catalog/Model/Product.php
@@ -339,6 +339,8 @@ class Mage_Catalog_Model_Product extends Mage_Catalog_Model_Abstract
*/
protected $_reviewSummary = [];
+ protected ?string $locale = null;
+
/**
* Initialize resources
*/
@@ -1681,7 +1683,18 @@ public function getUrlInStore($params = [])
*/
public function formatUrlKey($str)
{
- return $this->getUrlModel()->formatUrlKey($str);
+ return $this->getUrlModel()->setLocale($this->getLocale())->formatUrlKey($str);
+ }
+
+ public function getLocale(): ?string
+ {
+ return $this->locale;
+ }
+
+ public function setLocale(?string $locale)
+ {
+ $this->locale = $locale;
+ return $this;
}
/**
diff --git a/app/code/core/Mage/Catalog/Model/Product/Url.php b/app/code/core/Mage/Catalog/Model/Product/Url.php
index 018d17a2716..d03d36db4e1 100644
--- a/app/code/core/Mage/Catalog/Model/Product/Url.php
+++ b/app/code/core/Mage/Catalog/Model/Product/Url.php
@@ -20,31 +20,10 @@
* @category Mage
* @package Mage_Catalog
*/
-class Mage_Catalog_Model_Product_Url extends Varien_Object
+class Mage_Catalog_Model_Product_Url extends Mage_Catalog_Model_Url
{
public const CACHE_TAG = 'url_rewrite';
- /**
- * URL instance
- *
- * @var Mage_Core_Model_Url
- */
- protected $_url;
-
- /**
- * URL Rewrite Instance
- *
- * @var Mage_Core_Model_Url_Rewrite
- */
- protected $_urlRewrite;
-
- /**
- * Factory instance
- *
- * @var Mage_Catalog_Model_Factory
- */
- protected $_factory;
-
/**
* @var Mage_Core_Model_Store
*/
@@ -59,32 +38,6 @@ public function __construct(array $args = [])
$this->_store = !empty($args['store']) ? $args['store'] : Mage::app()->getStore();
}
- /**
- * Retrieve URL Instance
- *
- * @return Mage_Core_Model_Url
- */
- public function getUrlInstance()
- {
- if ($this->_url === null) {
- $this->_url = Mage::getModel('core/url');
- }
- return $this->_url;
- }
-
- /**
- * Retrieve URL Rewrite Instance
- *
- * @return Mage_Core_Model_Url_Rewrite
- */
- public function getUrlRewrite()
- {
- if ($this->_urlRewrite === null) {
- $this->_urlRewrite = $this->_factory->getUrlRewriteInstance();
- }
- return $this->_urlRewrite;
- }
-
/**
* 'no_selection' shouldn't be a valid image attribute value
*
@@ -132,21 +85,6 @@ public function getProductUrl($product, $useSid = null)
return $this->getUrl($product, $params);
}
- /**
- * Format Key for URL
- *
- * @param string $str
- * @return string
- */
- public function formatUrlKey($str)
- {
- $urlKey = preg_replace('#[^0-9a-z]+#i', '-', Mage::helper('catalog/product_url')->format($str));
- $urlKey = strtolower($urlKey);
- $urlKey = trim($urlKey, '-');
-
- return $urlKey;
- }
-
/**
* Retrieve Product Url path (with category if exists)
*
@@ -281,7 +219,6 @@ protected function _getRequestPath($product, $categoryId)
if ($rewrite->getId()) {
return $rewrite->getRequestPath();
}
-
return false;
}
}
diff --git a/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Urlkey.php b/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Urlkey.php
index dde773c6866..ba08ad49f5d 100644
--- a/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Urlkey.php
+++ b/app/code/core/Mage/Catalog/Model/Resource/Product/Attribute/Backend/Urlkey.php
@@ -37,6 +37,10 @@ public function beforeSave($object)
$urlKey = $object->getName();
}
+ if (method_exists($object, 'setLocale')) {
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $object->getStoreId());
+ $object->setLocale($locale);
+ }
$object->setData($attributeName, $object->formatUrlKey($urlKey));
return $this;
diff --git a/app/code/core/Mage/Catalog/Model/Url.php b/app/code/core/Mage/Catalog/Model/Url.php
index 61cf0de059a..595bad6cd77 100644
--- a/app/code/core/Mage/Catalog/Model/Url.php
+++ b/app/code/core/Mage/Catalog/Model/Url.php
@@ -14,13 +14,15 @@
* @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
+use Symfony\Component\String\Slugger\AsciiSlugger;
+
/**
* Catalog url model
*
* @category Mage
* @package Mage_Catalog
*/
-class Mage_Catalog_Model_Url
+class Mage_Catalog_Model_Url extends Varien_Object
{
/**
* Number of characters allowed to be in URL path
@@ -100,6 +102,62 @@ class Mage_Catalog_Model_Url
*/
protected static $_categoryForUrlPath;
+ protected ?string $locale = null;
+
+ /**
+ * Url instance
+ *
+ * @var Mage_Core_Model_Url
+ */
+ protected $_url;
+
+ /**
+ * Url rewrite instance
+ *
+ * @var Mage_Core_Model_Url_Rewrite
+ */
+ protected $_urlRewrite;
+
+ /**
+ * Factory instance
+ *
+ * @var Mage_Catalog_Model_Factory
+ */
+ protected $_factory;
+
+ /**
+ * @var AsciiSlugger[]
+ */
+ protected ?array $slugger = null;
+
+ /**
+ * Retrieve Url instance
+ *
+ * @return Mage_Core_Model_Url
+ */
+ public function getUrlInstance()
+ {
+ if ($this->_url === null) {
+ /** @var Mage_Core_Model_Url $model */
+ $model = $this->_factory->getModel('core/url');
+ $this->_url = $model;
+ }
+ return $this->_url;
+ }
+
+ /**
+ * Retrieve Url rewrite instance
+ *
+ * @return Mage_Core_Model_Url_Rewrite
+ */
+ public function getUrlRewrite()
+ {
+ if ($this->_urlRewrite === null) {
+ $this->_urlRewrite = $this->_factory->getUrlRewriteInstance();
+ }
+ return $this->_urlRewrite;
+ }
+
/**
* Adds url_path property for non-root category - to ensure that url path is not empty.
*
@@ -257,11 +315,9 @@ public function refreshRewrites($storeId = null)
protected function _refreshCategoryRewrites(Varien_Object $category, $parentPath = null, $refreshProducts = true)
{
if ($category->getId() != $this->getStores($category->getStoreId())->getRootCategoryId()) {
- if ($category->getUrlKey() == '') {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getName());
- } else {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getUrlKey());
- }
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $category->getStoreId());
+ $urlKey = $category->getUrlKey() == '' ? $category->getName() : $category->getUrlKey();
+ $urlKey = $this->getCategoryModel()->setLocale($locale)->formatUrlKey($urlKey);
$idPath = $this->generatePath('id', null, $category);
$targetPath = $this->generatePath('target', null, $category);
@@ -320,11 +376,10 @@ protected function _refreshProductRewrite(Varien_Object $product, Varien_Object
if ($category->getId() == $category->getPath()) {
return $this;
}
- if ($product->getUrlKey() == '') {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getName());
- } else {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getUrlKey());
- }
+
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $product->getStoreId());
+ $urlKey = $product->getUrlKey() == '' ? $product->getName() : $product->getUrlKey();
+ $urlKey = $this->getProductModel()->setLocale($locale)->formatUrlKey($urlKey);
$idPath = $this->generatePath('id', $product, $category);
$targetPath = $this->generatePath('target', $product, $category);
@@ -366,7 +421,7 @@ protected function _refreshProductRewrite(Varien_Object $product, Varien_Object
}
/**
- * Refresh products for catwgory
+ * Refresh products for category
*
* @return $this
*/
@@ -672,7 +727,7 @@ public function getUnusedPathByUrlKey($storeId, $requestPath, $idPath, $urlKey)
}
/**
- * Retrieve product rewrite sufix for store
+ * Retrieve product rewrite suffix for store
*
* @param int $storeId
* @return string
@@ -683,7 +738,7 @@ public function getProductUrlSuffix($storeId)
}
/**
- * Retrieve category rewrite sufix for store
+ * Retrieve category rewrite suffix for store
*
* @param int $storeId
* @return string
@@ -710,11 +765,9 @@ public function getCategoryRequestPath($category, $parentPath)
$existingRequestPath = $this->_rewrites[$idPath]->getRequestPath();
}
- if ($category->getUrlKey() == '') {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getName());
- } else {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getUrlKey());
- }
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $category->getStoreId());
+ $urlKey = $category->getUrlKey() == '' ? $category->getName() : $category->getUrlKey();
+ $urlKey = $this->getCategoryModel()->setLocale($locale)->formatUrlKey($urlKey);
$categoryUrlSuffix = $this->getCategoryUrlSuffix($storeId);
if ($parentPath === null) {
@@ -766,11 +819,10 @@ protected function _deleteOldTargetPath($requestPath, $idPath, $storeId)
*/
public function getProductRequestPath($product, $category)
{
- if ($product->getUrlKey() == '') {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getName());
- } else {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getUrlKey());
- }
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $product->getStoreId());
+ $urlKey = $product->getUrlKey() == '' ? $product->getName() : $product->getUrlKey();
+ $urlKey = $this->getProductModel()->setLocale($locale)->formatUrlKey($urlKey);
+
$storeId = $category->getStoreId();
$suffix = $this->getProductUrlSuffix($storeId);
$idPath = $this->generatePath('id', $product, $category);
@@ -881,11 +933,9 @@ public function generatePath($type = 'target', $product = null, $category = null
if ($type === 'request') {
// for category
if (!$product) {
- if ($category->getUrlKey() == '') {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getName());
- } else {
- $urlKey = $this->getCategoryModel()->formatUrlKey($category->getUrlKey());
- }
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $category->getStoreId());
+ $urlKey = $category->getUrlKey() == '' ? $category->getName() : $category->getUrlKey();
+ $urlKey = $this->getCategoryModel()->setLocale($locale)->formatUrlKey($urlKey);
$categoryUrlSuffix = $this->getCategoryUrlSuffix($category->getStoreId());
if ($parentPath === null) {
@@ -912,11 +962,10 @@ public function generatePath($type = 'target', $product = null, $category = null
Mage::throwException(Mage::helper('core')->__('A category object is required for determining the product request path.')); // why?
}
- if ($product->getUrlKey() == '') {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getName());
- } else {
- $urlKey = $this->getProductModel()->formatUrlKey($product->getUrlKey());
- }
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $product->getStoreId());
+ $urlKey = $product->getUrlKey() == '' ? $product->getName() : $product->getUrlKey();
+ $urlKey = $this->getProductModel()->setLocale($locale)->formatUrlKey($urlKey);
+
$productUrlSuffix = $this->getProductUrlSuffix($category->getStoreId());
if ($category->getLevel() > 1) {
// To ensure, that category has url path either from attribute or generated now
@@ -984,4 +1033,64 @@ protected function _saveRewriteHistory($rewriteData, $rewrite)
return $this;
}
+
+ /**
+ * Format Key for URL
+ *
+ * @param string $str
+ * @return string
+ */
+ public function formatUrlKey($str)
+ {
+ return $this->getSlugger()->slug($str)->lower()->toString();
+ }
+
+ public function getSlugger(): AsciiSlugger
+ {
+ $locale = $this->getLocale();
+ if (is_null($this->slugger) || !array_key_exists($locale, $this->slugger)) {
+ $config = $this->getSluggerConfig($locale);
+ $slugger = new AsciiSlugger('en', $config);
+ $slugger->setLocale($locale);
+
+ $this->slugger[$locale] = $slugger;
+ }
+
+ return $this->slugger[$locale];
+ }
+
+ final public function getSluggerConfig(?string $locale): array
+ {
+ $config = Mage::helper('catalog/product_url')->getConvertTableShort();
+
+ if ($locale) {
+ $convertNode = Mage::getConfig()->getNode('default/url/convert/' . $locale);
+ if ($convertNode instanceof Mage_Core_Model_Config_Element) {
+ $localeConfig = [];
+ /** @var Mage_Core_Model_Config_Element $node */
+ foreach ($convertNode->children() as $node) {
+ if (property_exists($node, 'from') && property_exists($node, 'to')) {
+ $localeConfig[(string) $node->from] = (string) $node->to;
+ }
+ }
+ $config = [$locale => $config + $localeConfig];
+ }
+ }
+
+ return $config;
+ }
+
+ public function getLocale(): ?string
+ {
+ return $this->locale;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setLocale(?string $locale)
+ {
+ $this->locale = $locale;
+ return $this;
+ }
}
diff --git a/app/code/core/Mage/Core/etc/config.xml b/app/code/core/Mage/Core/etc/config.xml
index d7ce041219d..1af6d04697a 100644
--- a/app/code/core/Mage/Core/etc/config.xml
+++ b/app/code/core/Mage/Core/etc/config.xml
@@ -507,6 +507,60 @@
+
+
+
+
+
+ prozent
+
+
+
+ und
+
+
+
+
+
+ percent
+
+
+
+ and
+
+
+
+
+
+ pour cent
+
+
+
+ et
+
+
+
+
+
+ per cento
+
+
+
+ e
+
+
+
+
+
+ por ciento
+
+
+
+ et
+
+
+
+
diff --git a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php
index 902a887ca53..f9eaf4d7847 100644
--- a/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php
+++ b/app/code/core/Mage/ImportExport/Model/Import/Entity/Product.php
@@ -1560,7 +1560,8 @@ protected function _prepareAttributes($rowData, $rowScope, $attributes, $rowSku,
$attrValue = gmdate(Varien_Date::DATETIME_PHP_FORMAT, strtotime($attrValue));
} elseif ($attribute->getAttributeCode() === 'url_key') {
if (empty($attrValue)) {
- $attrValue = $product->formatUrlKey($product->getName());
+ $locale = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $product->getStoreId());
+ $attrValue = $product->setLocale($locale)->formatUrlKey($product->getName());
}
} elseif ($backModel) {
$attribute->getBackend()->beforeSave($product);
diff --git a/app/code/core/Mage/Sales/Model/Quote/Item.php b/app/code/core/Mage/Sales/Model/Quote/Item.php
index 3fa2278aeb4..aed737200e3 100644
--- a/app/code/core/Mage/Sales/Model/Quote/Item.php
+++ b/app/code/core/Mage/Sales/Model/Quote/Item.php
@@ -516,12 +516,16 @@ public function compare($item)
// dispose of some options params, that can cramp comparing of arrays
if (is_string($itemOptionValue) && is_string($optionValue)) {
try {
- /** @var Unserialize_Parser $parser */
+ /**
+ * @var Mage_Core_Helper_UnserializeArray $parser
+ * @var Mage_Core_Helper_String $stringHelper
+ */
$parser = Mage::helper('core/unserializeArray');
+ $stringHelper = Mage::helper('core/string');
- $_itemOptionValue =
- is_numeric($itemOptionValue) ? $itemOptionValue : $parser->unserialize($itemOptionValue);
- $_optionValue = is_numeric($optionValue) ? $optionValue : $parser->unserialize($optionValue);
+ // only ever try to unserialize, if it looks like a serialized array
+ $_itemOptionValue = $stringHelper->isSerializedArrayOrObject($itemOptionValue) ? $parser->unserialize($itemOptionValue) : $itemOptionValue;
+ $_optionValue = $stringHelper->isSerializedArrayOrObject($optionValue) ? $parser->unserialize($optionValue) : $optionValue;
if (is_array($_itemOptionValue) && is_array($_optionValue)) {
$itemOptionValue = $_itemOptionValue;
diff --git a/composer.json b/composer.json
index 04e2a765d6d..9d754ab7731 100644
--- a/composer.json
+++ b/composer.json
@@ -38,7 +38,9 @@
"symfony/polyfill-php81": "^1.31",
"symfony/polyfill-php82": "^1.31",
"symfony/polyfill-php83": "^1.31",
- "symfony/polyfill-php84": "^1.31"
+ "symfony/polyfill-php84": "^1.31",
+ "symfony/string": "^5.4",
+ "symfony/translation-contracts": "^2.5"
},
"require-dev": {
"ext-xmlreader": "*",
diff --git a/composer.lock b/composer.lock
index 090ee37b85b..6fd160cdc5a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4aaaae1bdb9cd2d0f253b63e8c1eee65",
+ "content-hash": "af2f1a89f41c33d8f756c32152ac217d",
"packages": [
{
"name": "colinmollenhour/cache-backend-redis",
@@ -2372,6 +2372,84 @@
}
],
"time": "2024-11-10T20:33:58+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v2.5.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b0073a77ac0b7ea55131020e87b1e3af540f4664",
+ "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5"
+ },
+ "suggest": {
+ "symfony/translation-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/translation-contracts/tree/v2.5.3"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-23T13:51:25+00:00"
}
],
"packages-dev": [
@@ -7261,7 +7339,7 @@
],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
diff --git a/tests/unit/Mage/Catalog/Helper/Product/UrlTest.php b/tests/unit/Mage/Catalog/Helper/Product/UrlTest.php
index 784b02ca909..9aa95935e2d 100644
--- a/tests/unit/Mage/Catalog/Helper/Product/UrlTest.php
+++ b/tests/unit/Mage/Catalog/Helper/Product/UrlTest.php
@@ -33,16 +33,40 @@ public function setUp(): void
}
/**
+ * @covers Mage_Catalog_Helper_Product_Url::getConvertTable()
* @group Mage_Catalog
* @group Mage_Catalog_Helper
*/
public function testGetConvertTable(): void
{
- $this->assertIsArray($this->subject->getConvertTable());
- $this->assertCount(317, $this->subject->getConvertTable());
+ $result = $this->subject->getConvertTable();
+ $this->assertCount(317, $result);
}
/**
+ * @covers Mage_Catalog_Helper_Product_Url::getConvertTableCustom()
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Helper
+ */
+ public function testGetConvertTableCustom(): void
+ {
+ $result = $this->subject->getConvertTableCustom();
+ $this->assertCount(0, $result);
+ }
+
+ /**
+ * @covers Mage_Catalog_Helper_Product_Url::getConvertTableShort()
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Helper
+ */
+ public function testGetConvertTableShort(): void
+ {
+ $result = $this->subject->getConvertTableShort();
+ $this->assertCount(4, $result);
+ }
+
+ /**
+ * @covers Mage_Catalog_Helper_Product_Url::format()
* @dataProvider provideFormat
* @group Mage_Catalog
* @group Mage_Catalog_Helper
@@ -56,13 +80,17 @@ public function provideFormat(): Generator
{
yield 'null' => [
'',
- null
+ null,
+ ];
+ yield 'string' => [
+ 'string',
+ 'string',
];
- yield '&' => [
- 'and',
- '&',
+ yield 'umlauts' => [
+ 'string with aou',
+ 'string with ÄÖÜ',
];
- yield '@' => [
+ yield 'at' => [
'at',
'@',
];
diff --git a/tests/unit/Mage/Catalog/Model/CategoryTest.php b/tests/unit/Mage/Catalog/Model/CategoryTest.php
index f4451161abe..05d71ef69cc 100644
--- a/tests/unit/Mage/Catalog/Model/CategoryTest.php
+++ b/tests/unit/Mage/Catalog/Model/CategoryTest.php
@@ -17,13 +17,18 @@
namespace OpenMage\Tests\Unit\Mage\Catalog\Model;
+use Generator;
use Mage;
use Mage_Catalog_Model_Category;
+use Mage_Catalog_Model_Category_Url;
use Mage_Catalog_Model_Resource_Product_Collection;
+use Mage_Catalog_Model_Url;
use PHPUnit\Framework\TestCase;
class CategoryTest extends TestCase
{
+ public const TEST_STRING = 'a & B, x%, ä, ö, ü';
+
public Mage_Catalog_Model_Category $subject;
public function setUp(): void
@@ -85,4 +90,50 @@ public function testAfterCommitCallback(): void
{
$this->assertInstanceOf(Mage_Catalog_Model_Category::class, $this->subject->afterCommitCallback());
}
+
+ /**
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testGetUrlModel(): void
+ {
+ $this->assertInstanceOf(Mage_Catalog_Model_Url::class, $this->subject->getUrlModel());
+ $this->assertInstanceOf(Mage_Catalog_Model_Category_Url::class, $this->subject->getUrlModel());
+ }
+
+ /**
+ * @dataProvider provideFormatUrlKey
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ * @runInSeparateProcess
+ */
+// public function testGetCategoryIdUrl($expectedResult, ?string $locale): void
+// {
+// $this->subject->setName(self::TEST_STRING);
+// $this->subject->setLocale($locale);
+// $this->assertSame($expectedResult, $this->subject->getCategoryIdUrl());
+// }
+
+ /**
+ * @dataProvider provideFormatUrlKey
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testFormatUrlKey($expectedResult, ?string $locale): void
+ {
+ $this->subject->setLocale($locale);
+ $this->assertSame($expectedResult, $this->subject->formatUrlKey(self::TEST_STRING));
+ }
+
+ public function provideFormatUrlKey(): Generator
+ {
+ yield 'null locale' => [
+ 'a-b-x-a-o-u',
+ null,
+ ];
+ yield 'de_DE' => [
+ 'a-und-b-x-prozent-ae-oe-ue',
+ 'de_DE',
+ ];
+ }
}
diff --git a/tests/unit/Mage/Catalog/Model/ProductTest.php b/tests/unit/Mage/Catalog/Model/ProductTest.php
index 240cc9a56b9..df1d4243de9 100644
--- a/tests/unit/Mage/Catalog/Model/ProductTest.php
+++ b/tests/unit/Mage/Catalog/Model/ProductTest.php
@@ -24,10 +24,13 @@
use Mage_Catalog_Model_Product_Type_Abstract;
use Mage_Catalog_Model_Product_Url;
use Mage_Catalog_Model_Resource_Product_Collection;
+use Mage_Catalog_Model_Url;
use PHPUnit\Framework\TestCase;
class ProductTest extends TestCase
{
+ public const TEST_STRING = 'a & B, x%, ä, ö, ü';
+
public Mage_Catalog_Model_Product $subject;
public function setUp(): void
@@ -60,6 +63,7 @@ public function testGetResourceCollection(): void
*/
public function testGetUrlModel(): void
{
+ $this->assertInstanceOf(Mage_Catalog_Model_Url::class, $this->subject->getUrlModel());
$this->assertInstanceOf(Mage_Catalog_Model_Product_Url::class, $this->subject->getUrlModel());
}
@@ -164,4 +168,27 @@ public function testAfterCommitCallback(): void
{
$this->assertInstanceOf(Mage_Catalog_Model_Product::class, $this->subject->afterCommitCallback());
}
+
+ /**
+ * @dataProvider provideFormatUrlKey
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testFormatUrlKey($expectedResult, ?string $locale): void
+ {
+ $this->subject->setLocale($locale);
+ $this->assertSame($expectedResult, $this->subject->formatUrlKey(self::TEST_STRING));
+ }
+
+ public function provideFormatUrlKey(): Generator
+ {
+ yield 'null locale' => [
+ 'a-b-x-a-o-u',
+ null,
+ ];
+ yield 'de_DE' => [
+ 'a-und-b-x-prozent-ae-oe-ue',
+ 'de_DE',
+ ];
+ }
}
diff --git a/tests/unit/Mage/Catalog/Model/UrlTest.php b/tests/unit/Mage/Catalog/Model/UrlTest.php
index aad8f4a00e1..7e04d8d7113 100644
--- a/tests/unit/Mage/Catalog/Model/UrlTest.php
+++ b/tests/unit/Mage/Catalog/Model/UrlTest.php
@@ -22,10 +22,13 @@
use Mage_Catalog_Model_Url;
use Mage_Core_Exception;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\String\Slugger\AsciiSlugger;
use Varien_Object;
class UrlTest extends TestCase
{
+ public const TEST_STRING = '--a & B, x% @ ä ö ü ™--';
+
public Mage_Catalog_Model_Url $subject;
public function setUp(): void
@@ -93,10 +96,12 @@ public function provideGeneratePathData(): Generator
'id' => '999',
'store_id' => '1',
'url_key' => '',
+ 'name' => 'category',
]);
$product = new Varien_Object([
- 'id' => '999'
+ 'id' => '999',
+ 'name' => 'product',
]);
yield 'test exception' => [
@@ -106,7 +111,7 @@ public function provideGeneratePathData(): Generator
null,
];
yield 'request' => [
- '-.html',
+ 'product.html',
'request',
$product,
$category,
@@ -130,4 +135,92 @@ public function provideGeneratePathData(): Generator
$category,
];
}
+ /**
+ * @dataProvider provideFormatUrlKey
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testFormatUrlKey($expectedResult, string $locale): void
+ {
+ $this->subject->setLocale($locale);
+ $this->assertSame($expectedResult, $this->subject->formatUrlKey(self::TEST_STRING));
+ }
+
+ public function provideFormatUrlKey(): Generator
+ {
+ yield 'de_DE' => [
+ 'a-und-b-x-prozent-at-ae-oe-ue-tm',
+ 'de_DE',
+ ];
+ yield 'en_US' => [
+ 'a-and-b-x-percent-at-a-o-u-tm',
+ 'en_US',
+ ];
+ yield 'es_ES' => [
+ 'a-et-b-x-por-ciento-at-a-o-u-tm',
+ 'es_ES',
+ ];
+ yield 'fr_FR' => [
+ 'a-et-b-x-pour-cent-at-a-o-u-tm',
+ 'fr_FR',
+ ];
+ yield 'it_IT' => [
+ 'a-e-b-x-per-cento-at-a-o-u-tm',
+ 'it_IT',
+ ];
+ }
+
+ /**
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testGetSlugger(): void
+ {
+ $this->assertInstanceOf(AsciiSlugger::class, $this->subject->getSlugger());
+ }
+
+ /**
+ * @dataProvider provideGetSluggerConfig
+ * @group Mage_Catalog
+ * @group Mage_Catalog_Model
+ */
+ public function testGetSluggerConfig($expectedResult, string $locale): void
+ {
+ $result = $this->subject->getSluggerConfig($locale);
+
+ $this->assertArrayHasKey($locale, $result);
+
+ $this->assertArrayHasKey('%', $result[$locale]);
+ $this->assertArrayHasKey('&', $result[$locale]);
+
+ $this->assertSame($expectedResult[$locale]['%'], $result[$locale]['%']);
+ $this->assertSame($expectedResult[$locale]['&'], $result[$locale]['&']);
+
+ $this->assertSame('at', $result[$locale]['@']);
+ }
+
+ public function provideGetSluggerConfig(): Generator
+ {
+ yield 'de_DE' => [
+ ['de_DE' => [
+ '%' => 'prozent',
+ '&' => 'und',
+ ]],
+ 'de_DE',
+ ];
+ yield 'en_US' => [
+ ['en_US' => [
+ '%' => 'percent',
+ '&' => 'and',
+ ]],
+ 'en_US',
+ ];
+ yield 'fr_FR' => [
+ ['fr_FR' => [
+ '%' => 'pour cent',
+ '&' => 'et',
+ ]],
+ 'fr_FR',
+ ];
+ }
}
diff --git a/tests/unit/Mage/Core/Helper/UnserializeArrayTest.php b/tests/unit/Mage/Core/Helper/UnserializeArrayTest.php
new file mode 100644
index 00000000000..8803897b14a
--- /dev/null
+++ b/tests/unit/Mage/Core/Helper/UnserializeArrayTest.php
@@ -0,0 +1,70 @@
+subject = Mage::helper('core/unserializeArray');
+ }
+
+ /**
+ * @dataProvider provideUnserialize
+ * @group Mage_Core
+ * @group Mage_Core_Helper
+ */
+ public function testUnserialize($expectedTesult, $string): void
+ {
+ try {
+ $this->assertSame($expectedTesult, $this->subject->unserialize($string));
+ } catch (Exception $exception) {
+ $this->assertSame($expectedTesult, $exception->getMessage());
+ }
+ }
+
+ public function provideUnserialize(): Generator
+ {
+ yield 'null' => [
+ 'Error unserializing data.',
+ null,
+ ];
+ yield 'empty string' => [
+ 'Error unserializing data.',
+ '',
+ ];
+ yield 'random string' => [
+ 'unserialize(): Error at offset 0 of 3 bytes',
+ 'abc',
+ ];
+ yield 'valid' => [
+ ['key' => 'value'],
+ 'a:1:{s:3:"key";s:5:"value";}',
+ ];
+ }
+}