Настраиваемая сортировка атрибутов продукта Magento 1.9.1

24

Как я уже упоминал, кажется, проблема с magento 1.9.1 и сортировкой атрибутов настраиваемых продуктов. Опции настраиваемого продукта теперь ВСЕГДА зависят от идентификатора продукта простого продукта. Порядок параметров атрибута игнорируется.

Я вернулся к magento 1.9.0.1. Может быть, кто-то может определить, как производится сортировка в 1.9.1. Было бы здорово для всех, кто использует настраиваемые продукты, чтобы это исправить.

Если кто-то хочет это увидеть, вы можете сделать это здесь, в демонстрационном магазине magento. Я не смог правильно отсортировать размеры.

Reinsch
источник

Ответы:

25

Примечание: мое внимание было обращено на то, что это решение не работает для Magento 1.9.2. Чтобы сэкономить время впустую, я бы хотел отметить это в верхней части этого поста. Если я разработаю свое собственное решение или найду решение другого, которое работает для 1.9.2, я обновлю этот пост в то время.

Примечание . Решение, изложенное здесь, расширяет файл классов блоков в основной библиотеке Magento. Я рассмотрел исходный код Magento до этого подхода и решил, что не было хорошего события, чтобы наблюдать, чтобы избежать этого подхода. Если в будущей версии Magento эта проблема сортировки будет решена, вы можете отменить эти изменения ниже, просто отключив расширение в его файле app / etc / modules XML.

Шаг 1: создайте файл app / etc / modules / FirstScribe_CatalogOptionSortFix.xml

Содержание:

<?xml version="1.0"?>
<config>
    <modules>
        <FirstScribe_CatalogOptionSortFix>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Catalog />
            </depends>
        </FirstScribe_CatalogOptionSortFix>
    </modules>
</config>

Примечание. Для шагов 2 и 3 при необходимости создайте каталоги для этих файлов. Например, у вас уже может быть каталог app / code / local или нет, в зависимости от того, какие расширения вы уже установили на своем сайте.

Шаг 2. Создайте файл app / code / local / FirstScribe / CatalogOptionSortFix / etc / config.xml

Содержание:

<?xml version="1.0"?>
<!--
/**
 * Magento 1.9.1.0 has a bug in that the configurable options are sorted by
 * ID rather than position for the Configurable Product's front end view script.
 * This extension addresses this problem.
 *
 * @category    FirstScribe
 * @package     FirstScribe_CatalogOptionSortFix
 * @version     2014.12.15
 */
-->
<config>
    <modules>
        <FirstScribe_CatalogOptionSortFix>
            <version>1.0.0</version>
        </FirstScribe_CatalogOptionSortFix>
    </modules>
    <global>
        <blocks>
            <catalog>
                <rewrite>
                    <product_view_type_configurable>FirstScribe_CatalogOptionSortFix_Block_Product_View_Type_Configurable</product_view_type_configurable>
                </rewrite>
            </catalog>
        </blocks>
    </global>
</config>

Шаг 3: Создайте файл app / code / local / FirstScribe / CatalogOptionSortFix / Block / Product / View / Type / Configurable.php

Содержание:

<?php
/**
 * Magento 1.9.1.0 has a bug in that the configurable options are sorted by
 * ID rather than position for the Configurable Product's front end view script.
 * This extension addresses this problem.
 *
 * @category    FirstScribe
 * @package     FirstScribe_CatalogOptionSortFix
 * @version     2014.12.15
 */
class FirstScribe_CatalogOptionSortFix_Block_Product_View_Type_Configurable extends Mage_Catalog_Block_Product_View_Type_Configurable
{
    /**
     * @var Magento_Db_Adapter_Pdo_Mysql
     */
    protected $_read;

    /**
     * @var string
     */
    protected $_tbl_eav_attribute_option;

    /**
     * Composes configuration for js
     *
     * @version 2014.12.15 - Addition of this line:
     *    $info['options'] = $this->_sortOptions($info['options']);
     *
     * @return string
     */
    public function getJsonConfig()
    {
        $attributes = array();
        $options    = array();
        $store      = $this->getCurrentStore();
        $taxHelper  = Mage::helper('tax');
        $currentProduct = $this->getProduct();

        $preconfiguredFlag = $currentProduct->hasPreconfiguredValues();
        if ($preconfiguredFlag) {
            $preconfiguredValues = $currentProduct->getPreconfiguredValues();
            $defaultValues       = array();
        }

        foreach ($this->getAllowProducts() as $product) {
            $productId  = $product->getId();

            foreach ($this->getAllowAttributes() as $attribute) {
                $productAttribute   = $attribute->getProductAttribute();
                $productAttributeId = $productAttribute->getId();
                $attributeValue     = $product->getData($productAttribute->getAttributeCode());
                if (!isset($options[$productAttributeId])) {
                    $options[$productAttributeId] = array();
                }

                if (!isset($options[$productAttributeId][$attributeValue])) {
                    $options[$productAttributeId][$attributeValue] = array();
                }
                $options[$productAttributeId][$attributeValue][] = $productId;
            }
        }

        $this->_resPrices = array(
            $this->_preparePrice($currentProduct->getFinalPrice())
        );

        foreach ($this->getAllowAttributes() as $attribute) {
            $productAttribute = $attribute->getProductAttribute();
            $attributeId = $productAttribute->getId();
            $info = array(
                    'id'        => $productAttribute->getId(),
                    'code'      => $productAttribute->getAttributeCode(),
                    'label'     => $attribute->getLabel(),
                    'options'   => array()
            );

            $optionPrices = array();
            $prices = $attribute->getPrices();
            if (is_array($prices)) {
                foreach ($prices as $value) {
                    if(!$this->_validateAttributeValue($attributeId, $value, $options)) {
                        continue;
                    }
                    $currentProduct->setConfigurablePrice(
                            $this->_preparePrice($value['pricing_value'], $value['is_percent'])
                    );
                    $currentProduct->setParentId(true);
                    Mage::dispatchEvent(
                            'catalog_product_type_configurable_price',
                            array('product' => $currentProduct)
                    );
                    $configurablePrice = $currentProduct->getConfigurablePrice();

                    if (isset($options[$attributeId][$value['value_index']])) {
                        $productsIndex = $options[$attributeId][$value['value_index']];
                    } else {
                        $productsIndex = array();
                    }

                    $info['options'][] = array(
                            'id'        => $value['value_index'],
                            'label'     => $value['label'],
                            'price'     => $configurablePrice,
                            'oldPrice'  => $this->_prepareOldPrice($value['pricing_value'], $value['is_percent']),
                            'products'  => $productsIndex,
                    );
                    $optionPrices[] = $configurablePrice;
                }
            }

            // CALL SORT ORDER FIX
            $info['options'] = $this->_sortOptions($info['options']);

            /**
             * Prepare formated values for options choose
             */
            foreach ($optionPrices as $optionPrice) {
                foreach ($optionPrices as $additional) {
                    $this->_preparePrice(abs($additional-$optionPrice));
                }
            }
            if($this->_validateAttributeInfo($info)) {
                $attributes[$attributeId] = $info;
            }

            // Add attribute default value (if set)
            if ($preconfiguredFlag) {
                $configValue = $preconfiguredValues->getData('super_attribute/' . $attributeId);
                if ($configValue) {
                    $defaultValues[$attributeId] = $configValue;
                }
            }
        }

        $taxCalculation = Mage::getSingleton('tax/calculation');
        if (!$taxCalculation->getCustomer() && Mage::registry('current_customer')) {
            $taxCalculation->setCustomer(Mage::registry('current_customer'));
        }

        $_request = $taxCalculation->getDefaultRateRequest();
        $_request->setProductClassId($currentProduct->getTaxClassId());
        $defaultTax = $taxCalculation->getRate($_request);

        $_request = $taxCalculation->getRateRequest();
        $_request->setProductClassId($currentProduct->getTaxClassId());
        $currentTax = $taxCalculation->getRate($_request);

        $taxConfig = array(
                'includeTax'        => $taxHelper->priceIncludesTax(),
                'showIncludeTax'    => $taxHelper->displayPriceIncludingTax(),
                'showBothPrices'    => $taxHelper->displayBothPrices(),
                'defaultTax'        => $defaultTax,
                'currentTax'        => $currentTax,
                'inclTaxTitle'      => Mage::helper('catalog')->__('Incl. Tax')
        );

        $config = array(
                'attributes'        => $attributes,
                'template'          => str_replace('%s', '#{price}', $store->getCurrentCurrency()->getOutputFormat()),
                'basePrice'         => $this->_registerJsPrice($this->_convertPrice($currentProduct->getFinalPrice())),
                'oldPrice'          => $this->_registerJsPrice($this->_convertPrice($currentProduct->getPrice())),
                'productId'         => $currentProduct->getId(),
                'chooseText'        => Mage::helper('catalog')->__('Choose an Option...'),
                'taxConfig'         => $taxConfig
        );

        if ($preconfiguredFlag && !empty($defaultValues)) {
            $config['defaultValues'] = $defaultValues;
        }

        $config = array_merge($config, $this->_getAdditionalConfig());    

        return Mage::helper('core')->jsonEncode($config);
    }

    /**
     * Sort the options based off their position.
     *
     * @param array $options
     * @return array
     */
    protected function _sortOptions($options)
    {
        if (count($options)) {
            if (!$this->_read || !$this->_tbl_eav_attribute_option) {
                $resource = Mage::getSingleton('core/resource');

                $this->_read = $resource->getConnection('core_read');
                $this->_tbl_eav_attribute_option = $resource->getTableName('eav_attribute_option');
            }

            // Gather the option_id for all our current options
            $option_ids = array();
            foreach ($options as $option) {
                $option_ids[] = $option['id'];

                $var_name  = 'option_id_'.$option['id'];
                $$var_name = $option;
            }

            $sql    = "SELECT `option_id` FROM `{$this->_tbl_eav_attribute_option}` WHERE `option_id` IN('".implode('\',\'', $option_ids)."') ORDER BY `sort_order`";
            $result = $this->_read->fetchCol($sql);

            $options = array();
            foreach ($result as $option_id) {
                $var_name  = 'option_id_'.$option_id;
                $options[] = $$var_name;
            }
        }

        return $options;
    }
}

Шаг 4: Если включено, обновите тип кэша Magento «Конфигурация» в разделе «Система» -> «Управление кэшем» панели администратора.

Обзор расширений

  1. Расширьте класс Mage_Catalog_Block_Product_View_Type_Configurable.
  2. Добавьте метод сортировки опций по их positionзначению, извлекая эту информацию из базы данных.
  3. Перепишите метод getJsonConfig, чтобы он вызывал нашу новую функцию после сбора параметров для атрибута.
Даррен Фелтон
источник
2
работает фантастически, и я рад, что решение не повлияет на будущие обновления - большое спасибо вам за жизнеспособное решение.
dawhoo
Привет @Meogi, хотя кажется, что твое исправление идеально подходит для значений атрибутов, мы уверены, что все в порядке для самих блоков выбора продукта? Заметил проблему с упорядочиванием их так, как они установлены в наборе атрибутов. Например, у нас «Color» перетаскивали выше «Size» в наборе атрибутов, однако 1.9.1 поменял их (игнорировал порядок). Единственный способ исправить это - редактировать сам продукт и перетаскивать заказ в пределах настраиваемого. Может быть, это был просто мошеннический продукт, который был случайно переупорядочен вручную?
Джо
1
@Joe Если я не ошибаюсь, перетаскивание атрибута выше / ниже в наборе атрибутов не влияет на порядок их отображения на странице сведений о внешнем продукте. Вместо этого вы должны перейти в Каталог -> Атрибуты -> Управление атрибутами, найти свой атрибут и отредактировать значение «Положение». Это повлияет как на порядок отображения настраиваемых атрибутов на странице продукта, так и на многоуровневую навигацию. Порядок настраиваемых параметров также можно изменить для каждого продукта, перейдя на вкладку «Связанные продукты» в админке и перетащив туда атрибуты вверх / вниз.
Даррен Фелтон
1
@Meogi - это только для позиции блока многослойной навигации, когда вы включаете «Использование в многослойной навигации».
Джо
@ Джо, я вижу, ну тогда я не знаю, как изменить настройки по умолчанию (возможно, это всегда было его размещение в наборе атрибутов, я не уверен). Я могу констатировать, что при установке Magento 1.9.1.0 мне все еще удавалось установить порядок по своему выбору, нажав / перетащив вверх / вниз на вкладке «Связанные продукты» настраиваемого продукта. Где они перечислены между формой быстрого создания и сеткой товаров внизу.
Даррен Фелтон
11

Просто добавив два моих цента, два других ответа хорошо показали мне направление исправления, но я решил напасть на источник, а не на точку представления блока.

Вы можете достичь того же результата, расширив метод Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collectionмодели _loadPrices(), в котором, несмотря на название, было сделано изменение (предположительно для производительности), в результате чего атрибуты упорядочены по идентификатору, а не по релевантности.

Похоже, что это изменение было сделано для того, чтобы избежать вложенных foreachоператоров, но в свою очередь также теряет правильный порядок. Это решение слегка модифицирует обновленную логику для отслеживания параметров атрибута, а затем выполняет другой цикл на основе исходного порядка, чтобы фактически выполнить добавление.

Вот скорректированное прохождение, похожее на ответ Меоги выше :


Шаг 1: Зарегистрируйте новый модуль

Примечание: если у вас уже есть, повторно используйте существующий.

# File: app/etc/modules/YourCompany_AttributeFix.xml
<?xml version="1.0"?>
<config>
    <modules>
        <YourCompany_AttributeFix>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Catalog />
            </depends>
        </YourCompany_AttributeFix>
    </modules>
</config>

Шаг 2: Создайте конфигурацию модуля

# File: app/code/local/YourCompany/AttributeFix/etc/config.xml
<?xml version="1.0"?>
<config>
    <modules>
        <YourCompany_AttributeFix>
            <version>0.1.0</version>
        </YourCompany_AttributeFix>
    </modules>    
    <global>
        <models>
            <catalog_resource>
                <rewrite>
                    <product_type_configurable_attribute_collection>YourCompany_AttributeFix_Model_Resource_Product_Type_Configurable_Attribute_Collection</product_type_configurable_attribute_collection>
                </rewrite>
            </catalog_resource>
        </models>
    </global>
</config>

Шаг 3: Добавьте расширение модели ресурса

# File: app/code/local/YourCompany/AttributeFix/Model/Resource/Product/Type/Configurable/Attribute/Collection.php
/**
 * Catalog Configurable Product Attribute Collection - overridden to re-enable the attribute option
 * sorting by relevance rather than by ID as changed in the Magento core class
 */
class YourCompany_AttributeFix_Model_Resource_Product_Type_Configurable_Attribute_Collection
    extends Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection
{
    /**
     * Load attribute prices information
     *
     * @return Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection
     */
    protected function _loadPrices()
    {
        if ($this->count()) {
            $pricings = array(
                0 => array()
            );

            if ($this->getHelper()->isPriceGlobal()) {
                $websiteId = 0;
            } else {
                $websiteId = (int)Mage::app()->getStore($this->getStoreId())->getWebsiteId();
                $pricing[$websiteId] = array();
            }

            $select = $this->getConnection()->select()
                ->from(array('price' => $this->_priceTable))
                ->where('price.product_super_attribute_id IN (?)', array_keys($this->_items));

            if ($websiteId > 0) {
                $select->where('price.website_id IN(?)', array(0, $websiteId));
            } else {
                $select->where('price.website_id = ?', 0);
            }

            $query = $this->getConnection()->query($select);

            while ($row = $query->fetch()) {
                $pricings[(int)$row['website_id']][] = $row;
            }

            $values = array();

            foreach ($this->_items as $item) {
                $productAttribute = $item->getProductAttribute();
                if (!($productAttribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract)) {
                    continue;
                }
                $options = $productAttribute->getFrontend()->getSelectOptions();

                $optionsByValue = array();
                foreach ($options as $option) {
                    $optionsByValue[$option['value']] = $option['label'];
                }

                /**
                 * Modification to re-enable the sorting by relevance for attribute options
                 * @author Robbie Averill <robbie.averill@kathmandu.co.nz>
                 */
                $toAdd = array();
                foreach ($this->getProduct()->getTypeInstance(true)
                             ->getUsedProducts(array($productAttribute->getAttributeCode()), $this->getProduct())
                         as $associatedProduct) {

                    $optionValue = $associatedProduct->getData($productAttribute->getAttributeCode());

                    if (array_key_exists($optionValue, $optionsByValue)) {
                        $toAdd[] = $optionValue;
                    }
                }

                // Add the attribute options, but in the relevant order rather than by ID
                foreach (array_intersect_key($optionsByValue, array_flip($toAdd)) as $optionValueKey => $optionValue) {
                    // If option available in associated product
                    if (!isset($values[$item->getId() . ':' . $optionValue])) {
                        // If option not added, we will add it.
                        $values[$item->getId() . ':' . $optionValueKey] = array(
                            'product_super_attribute_id' => $item->getId(),
                            'value_index'                => $optionValueKey,
                            'label'                      => $optionsByValue[$optionValueKey],
                            'default_label'              => $optionsByValue[$optionValueKey],
                            'store_label'                => $optionsByValue[$optionValueKey],
                            'is_percent'                 => 0,
                            'pricing_value'              => null,
                            'use_default_value'          => true
                        );
                    }
                }
                /**
                 * End attribute option order modification
                 * @author Robbie Averill <robbie.averill@kathmandu.co.nz>
                 */
            }

            foreach ($pricings[0] as $pricing) {
                // Addding pricing to options
                $valueKey = $pricing['product_super_attribute_id'] . ':' . $pricing['value_index'];
                if (isset($values[$valueKey])) {
                    $values[$valueKey]['pricing_value']     = $pricing['pricing_value'];
                    $values[$valueKey]['is_percent']        = $pricing['is_percent'];
                    $values[$valueKey]['value_id']          = $pricing['value_id'];
                    $values[$valueKey]['use_default_value'] = true;
                }
            }

            if ($websiteId && isset($pricings[$websiteId])) {
                foreach ($pricings[$websiteId] as $pricing) {
                    $valueKey = $pricing['product_super_attribute_id'] . ':' . $pricing['value_index'];
                    if (isset($values[$valueKey])) {
                        $values[$valueKey]['pricing_value']     = $pricing['pricing_value'];
                        $values[$valueKey]['is_percent']        = $pricing['is_percent'];
                        $values[$valueKey]['value_id']          = $pricing['value_id'];
                        $values[$valueKey]['use_default_value'] = false;
                    }
                }
            }

            foreach ($values as $data) {
                $this->getItemById($data['product_super_attribute_id'])->addPrice($data);
            }
        }
        return $this;
    }
}

Шаг 4. Очистите кеш


Для справки , фактическое изменение базового класса в a git diffбудет ниже (не редактируйте напрямую основные файлы!):

diff --git a/app/code/core/Mage/Catalog/Model/Resource/Product/Type/Configurable/Attribute/Collection.php b/app/code/core/Mage/Catalog/Model/Resource/Product/Type/Configurable/Attribute/Collection.php
index 135d9d3..4d2a59b 100644
--- a/app/code/core/Mage/Catalog/Model/Resource/Product/Type/Configurable/Attribute/Collection.php
+++ b/app/code/core/Mage/Catalog/Model/Resource/Product/Type/Configurable/Attribute/Collection.php
@@ -254,6 +254,11 @@ class Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection
                     $optionsByValue[$option['value']] = $option['label'];
                 }

+                /**
+                 * Modification to re-enable the sorting by relevance for attribute options
+                 * @author Robbie Averill <robbie.averill@kathmandu.co.nz>
+                 */
+                $toAdd = array();
                 foreach ($this->getProduct()->getTypeInstance(true)
                              ->getUsedProducts(array($productAttribute->getAttributeCode()), $this->getProduct())
                          as $associatedProduct) {
@@ -261,22 +266,31 @@ class Mage_Catalog_Model_Resource_Product_Type_Configurable_Attribute_Collection
                     $optionValue = $associatedProduct->getData($productAttribute->getAttributeCode());

                     if (array_key_exists($optionValue, $optionsByValue)) {
-                        // If option available in associated product
-                        if (!isset($values[$item->getId() . ':' . $optionValue])) {
-                            // If option not added, we will add it.
-                            $values[$item->getId() . ':' . $optionValue] = array(
-                                'product_super_attribute_id' => $item->getId(),
-                                'value_index'                => $optionValue,
-                                'label'                      => $optionsByValue[$optionValue],
-                                'default_label'              => $optionsByValue[$optionValue],
-                                'store_label'                => $optionsByValue[$optionValue],
-                                'is_percent'                 => 0,
-                                'pricing_value'              => null,
-                                'use_default_value'          => true
-                            );
-                        }
+                        $toAdd[] = $optionValue;
                     }
                 }
+
+                // Add the attribute options, but in the relevant order rather than by ID
+                foreach (array_intersect_key($optionsByValue, array_flip($toAdd)) as $optionValueKey => $optionValue) {
+                    // If option available in associated product
+                    if (!isset($values[$item->getId() . ':' . $optionValue])) {
+                        // If option not added, we will add it.
+                        $values[$item->getId() . ':' . $optionValueKey] = array(
+                            'product_super_attribute_id' => $item->getId(),
+                            'value_index'                => $optionValueKey,
+                            'label'                      => $optionsByValue[$optionValueKey],
+                            'default_label'              => $optionsByValue[$optionValueKey],
+                            'store_label'                => $optionsByValue[$optionValueKey],
+                            'is_percent'                 => 0,
+                            'pricing_value'              => null,
+                            'use_default_value'          => true
+                        );
+                    }
+                }
+                /**
+                 * End attribute option order modification
+                 * @author Robbie Averill <robbie.averill@kathmandu.co.nz>
+                 */
             }

             foreach ($pricings[0] as $pricing) {

Это также на GitHub, если кто-то хочет его для справки.

Изменить: я также зарегистрировал это как ошибку с Magento .

Робби Аверилл
источник
1
Потрясающий вклад мой друг. +1 (да, я не должен был использовать эти комментарии, чтобы сказать спасибо, но ты убил его, поэтому я должен хаха)
Даррен Фелтон
Я пробовал это с magento 1.9.2 - кажется, к сожалению, работает. И я не понимаю, почему эта ошибка все еще не исправлена ​​Magento.
Рейнш
Вы убедились, что ваш модуль настроен правильно? Я уверен, что они знают об этом, но, вероятно, потребуется некоторое время для проверки, прежде чем они выпустят патч, так как это очень важная часть системы. Изменить: Вы также можете проверить исправление напрямую (и временно), но скопировать исправление непосредственно в основной класс (временно)
Робби Аверилл
1
@Reinsch Я только что получил письмо от кого-то о несовместимости с CE 1.9.2 - отправил обновление в мой репозиторий Github и протестировал его на CE 1.9.2 с примерами данных Magento, и теперь он работает правильно
Робби Аверилл
1
Отличная работа @RobbieAverill - большое спасибо. Протестировано и подтверждено работа на веб-сайте Magento 1.9.2.1.
zigojacko
3

Это не совсем правильное исправление, но это то, что я сделал временно, чтобы избежать необходимости возвращаться к 1.9.0.1 до тех пор, пока следующий выпуск Magento, надеюсь, не исправит проблему должным образом. Он будет сортировать значения параметров в алфавитном порядке, конечно, вы можете отсортировать все, что захотите, но я не знаю, как получить доступ к порядку сортировки, заданному в бэкэнде, и по алфавиту достаточно для моих целей.

Изменить файл

/app/code/core/Mage/Catalog/Block/Product/View/Type/configurable.php

Смена строки 215

if($this->_validateAttributeInfo($info)) {
   $attributes[$attributeId] = $info;
}

в

usort($info['options'], function ($a,$b)
    {
        return strcmp($a['label'],$b['label']);
    }
);
if($this->_validateAttributeInfo($info)) {
   $attributes[$attributeId] = $info;
}
Стив
источник
2
Пожалуйста, смотрите мой ответ для ответа, который соответственно расширяет основную библиотеку Magento, а не изменяет ее напрямую. Тем не менее, спасибо Стиву за этот ответ, так как он очень помог мне узнать, с чего начать разработку решения, которое я придумал.
Даррен Фелтон
Отлично сработало как очарование в «Энтерпрайзе», даже благодаря тонне, которую вы спасли мой день ..
Бхарат