Выведение опций товара по категориям

Сниппет msProductOptions выводит товары общим массивом. В принципе, никто не запрещает в чанке вывода делать с этим массивом всё что угодно, в том числе и делить по категориям. Однако всё чаще поступает запрос от клиента не только вывести категории, но и соблюсти их сортировку.

Для этих целей мне пришлось msProductOptions переписать. Мой вариант сниппета воспроизводит запрос msProductData::loadOptions с сортировкой по полю rank категории, затем группирует все опции по категориям с соблюдением сортировки самих опций.

На фронт получаем массив вида:
[
    'категория 1' => [
        'option1' => [
            (все поля опции и массив value)
        ],
        'option2' => [
            (все поля опции и массив value)        
        ]
    ],
    'категория 2' => [
        'option3' => [
            (все поля опции и массив value)
        ],
        'option4' => [
            (все поля опции и массив value)        
        ]
    ],
]

В чанке вывода можно сделать например так:
{foreach $data as $category => $options}
    <h4>{$category}</h4>
    <dl>
        {foreach $options as $option}
            <dt>{$option.caption}</dt>
            <dd>{$option.value|join:', '}{$option.measure_unit}</dd>
        {/foreach}
    </dl>
{/foreach}

Выкладываю сюда код сниппета, вдруг кому-то пригодится. Воть:
<?php
/** @var modX $modx */
/** @var array $scriptProperties */

$tpl = $modx->getOption('tpl', $scriptProperties, 'tpl.msOptions');
if (!empty($input) && empty($product)) {
    $product = $input;
}

$product = !empty($product) && $product != $modx->resource->id
    ? $modx->getObject('msProduct', ['id' => $product])
    : $modx->resource;
if (!($product instanceof msProduct)) {
    return "[msProductOptions] The resource with id = {$product->id} is not instance of msProduct.";
}

$ignoreOptions = array_filter(array_map('trim', explode(',', $modx->getOption('ignoreOptions', $scriptProperties, ''))));
$onlyOptions = array_filter(array_map('trim', explode(',', $modx->getOption('onlyOptions', $scriptProperties, ''))));
$groups = array_filter(array_map('trim', explode(',', $modx->getOption('groups', $scriptProperties, ''))));

/** @var msProductData $data */
if ($data = $product->getOne('Data')) {
    $optionKeys = $data->getOptionKeys();
}
if (empty($optionKeys)) {
    return '';
}

$q = $modx->newQuery('msProductOption');
$q->rightJoin('msOption', 'msOption', 'msProductOption.key = msOption.key');
$q->leftJoin('modCategory', 'Category', 'Category.id = msOption.category');
$q->where(['msProductOption.product_id' => $product->get('id')]);
if (!empty($groups)) {
    $q->where([
        ['Category.id:IN' => $groups, 'OR:Category.category:IN' => $groups]
    ]);
}
if (!empty($onlyOptions)) {
    $q->where(['msProductOption.key:IN' => $onlyOptions]);
} elseif (!empty($ignoreOptions)) {
    $q->where(['msProductOption.key:NOT IN' => $ignoreOptions]);
}
$q->select($modx->getSelectColumns('msOption', 'msOption'));
$q->select($modx->getSelectColumns('msProductOption', 'msProductOption', '', ['key'], true));
$q->select('Category.category AS category_name');
$q->sortby('Category.rank');

$tmp = [];
if ($q->prepare() && $q->stmt->execute()) {
    while ($option = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
        if (empty($option['value'])) continue;
        $option['value'] = [$option['value']];
        if (empty($option['category_name'])) {
            $option['category_name'] = 'без категории';
        }
        $key = $option['key'];
        $category = $option['category_name'];
        if (!isset($tmp[$category])) {
            $tmp[$category] = [];
        }
        if (!isset($tmp[$category][$key])) {
            $tmp[$category][$key] = $option;
        } elseif (!empty($tmp[$category][$key]['value']) and is_array($tmp[$category][$key]['value'])) {
            $tmp[$category][$key]['value'] = array_merge($tmp[$category][$key]['value'], $option['value']);
        } else {
            $tmp[$category][$key]['value'] = $option['value'];
        }
    }
}

$output = [];
foreach ($tmp as $category => $options) {
    if (empty($output[$category])) {
        $output[$category] = [];
    }
    foreach ($optionKeys as $optionKey) {
        if (isset($options[$optionKey])) {
            $output[$category][$optionKey] = $options[$optionKey];
        }
    }
}

/** @var pdoTools $pdoTools */
$pdoTools = $modx->getService('pdoTools');

return $pdoTools->getChunk($tpl, ['data' => $output]);

UPD. 30.11.2019 Исправлен код сниппета, который некорректно обрабатывал опции с множественным значением
mngatoff
24 ноября 2019, 06:44
modx.pro
8
2 614
+6

Комментарии: 2

Олег Щавелев
24 ноября 2019, 08:05
0
@mngatoff Отличная тема для обсуждения. И хорошее решение. Спасибо что поделились решением. Я как раз хотел писать о том что можно сделать с опциями. Я думаю актуальный вопрос про групировку, перелинковку и возможно подумать еще на тему подсказок и других ништяков. Вариантов решений много как и возможностей. Обязательно изучу на тестовом стенде и ваш вариант.
    Олег Захаров
    07 апреля 2024, 12:05
    0
    А как задать сортировку опций внутри категорий? Для тех кто не знает и чтобы сэкономить себе время и нервы подсказываю (сам долго тупил и не знал как настроить):
    Чтобы опции внутри категории сортировались в нужном порядке, заходим в редактирование Категории товаров в Minishop и находим вкладку Настройки. Там должны быть опции категории. Перетаскиваем строки вверх вниз формируя нужную сортировку.
    Поле rank у опций категории (таблица modx_ms2_category_options) по умолчанию стоит видимо 0, но после перетаскивания строк формируется последовательность сортировки.
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      2