Baymard Institute: 61 рекомендация для e-commerce, о которых стоит знать

На 7-8 году постоянной работы с MiniShop я узнал, что существует Baymard Institute — датская исследовательская компания, с 2009 года занимающаяся исключительно UX интернет-магазинов. 200,000+ часов реального тестирования с пользователями, 4,400+ модерируемых сессий, 771 гайдлайн. 71% компаний из Fortune 500 e-commerce используют их рекомендации. Фактически главный мировой источник данных о том, как должен быть устроен интернет-магазин.

Подписка у них платная ($500), но в блоге и открытых статьях достаточно материала, чтобы переосмыслить многие привычные решения. Я собрал самое интересное — то, что применимо к нашим проектам на MODX.

Николай Савин
04 марта 2026, 21:06
modx.pro
3
3 075
+10

Обновление полей ресурсов на основе csv-файла иморта (modx 2.8.8 + minishop 2)

Задача была обновить поля у ресурсов (ресурсы: категории, товары; поля: основные, дополнительные (тв)).
Обновление изображений (галерея) в задачу не входило.
Возможно, кому-нибудь пригодится. Если будут корректировки/замечания, буду только рад.
Виталий
17 февраля 2026, 03:09
modx.pro
3
2 660
+5

Видеогалерея на MIGX. Может кому понадобится (решено)

*Помогите толком разобраться, как редактировать поля title, desc, image, duration, которые находятся внутри json массива у видео в виде отдельных полей, а не кривыми ручками

Видеогалерея на MIGX

  1. Устанавливаем VideoGallery
  2. Создаем tv с именем video тип ввода VideoGallery
  3. Создаем tv с именем videogallery. Тип ввода migx, конфигурация videogallery, выбираем шаблон для отображения
  4. Создаем сниппет VideoJsonToPlaceholders
    ! ВНИМАНИЕ в п.2 удалите пробел между & quot;

    <?php
    // Получаем параметры
    $json = $modx->getOption('json', $scriptProperties, '');
    $prefix = $modx->getOption('prefix', $scriptProperties, 'json.');
    
    // 1. Проверяем наличие данных
    if (empty($json)) {
        return 'Ошибка: JSON‑строка пуста';
    }
    
    // 2. Удаляем все & quot; из строки ! Внимание в следующей строке удалите пробел между & quot;
    $json = str_replace('& quot;', '', $json);
    
    // 3. Исправляем экранированные слеши
    $json = str_replace('\\/', '/', $json);
    
    // 4. Парсим JSON
    $data = json_decode($json, true);
    
    if (json_last_error() !== JSON_ERROR_NONE) {
        return 'Ошибка JSON: ' . json_last_error_msg() . ' (строка: ' . htmlspecialchars($json) . ')';
    }
    
    // 5. Проверяем, что результат — массив
    if (!is_array($data)) {
        return 'Ошибка: JSON не содержит массив';
    }
    
    // 6. Функция для извлечения корневого адреса (с защитой от повторного объявления)
    if (!function_exists('getRootUrl')) {
        function getRootUrl($url) {
            if (!filter_var($url, FILTER_VALIDATE_URL)) {
                return null;
            }
    
            $parsed = parse_url($url);
            if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
                return null;
            }
    
            $root = $parsed['scheme'] . '://' . $parsed['host'];
    
            if (isset($parsed['port'])) {
                $isDefaultHttp = ($parsed['scheme'] === 'http' && $parsed['port'] === 80);
                $isDefaultHttps = ($parsed['scheme'] === 'https' && $parsed['port'] === 443);
                if (!$isDefaultHttp && !$isDefaultHttps) {
                    $root .= ':' . $parsed['port'];
                }
            }
    
            return $root;
        }
    }
    
    // 7. Устанавливаем плейсхолдеры с обработкой
    foreach ($data as $key => $value) {
        // Приводим строки к UTF‑8
        if (is_string($value)) {
            $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
        }
    
        // Если это поле video и содержит URL — извлекаем корневой адрес
        if ($key === 'video' && !empty($value)) {
            $rootUrl = getRootUrl($value);
            if ($rootUrl) {
                $modx->setPlaceholder($prefix . 'video_root', $rootUrl);
            }
            // Сохраняем исходный URL
            $modx->setPlaceholder($prefix . 'video', $value);
        }
    
        // Преобразуем videoDuration из PT... в оптимальный формат
        if ($key === 'videoDuration') {
            if (empty($value) || !is_string($value)) {
                $value = '0 с';
            } else {
                preg_match('/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/', $value, $matches);
                $hours   = isset($matches[1]) ? (int)$matches[1] : 0;
                $minutes = isset($matches[2]) ? (int)$matches[2] : 0;
                $seconds = isset($matches[3]) ? (int)$matches[3] : 0;
    
                if ($hours > 0) {
                    $value = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
                } elseif ($minutes > 0) {
                    $value = sprintf('%02d:%02d', $minutes, $seconds);
                } else {
                    $value = $seconds . ' с';
                }
            }
        }
    
        // Подставляем значения по умолчанию для критических полей
        switch ($key) {
            case 'title':
                if (empty($value)) {
                    $value = 'Без названия';
                }
                break;
            case 'desc':
                if (empty($value)) {
                    $value = 'Описание отсутствует';
                }
                break;
        }
    
        // Устанавливаем плейсхолдер (если он ещё не установлен через парсинг)
        if (!in_array($key, ['video', 'video_root'])) {
            $modx->setPlaceholder($prefix . $key, $value);
        }
    }
    
    return '';
  5. Создаем новое MIGx поле videogallery
  6. Экспортируем туда конфиг
    {
      "formtabs": [
        {
          "caption": "Видеогалерея",
          "print_before_tabs": "0",
          "fields": [
            {
              "field": "video",
              "caption": "Видео",
              "inputTV": "video"
            }
          ],
          "pos": 1
        }
      ],
      "contextmenus": "",
      "actionbuttons": "",
      "columnbuttons": "",
      "filters": "",
      "extended": {
        "actionbuttonsperrow": 4,
        "gridload_mode": 1,
        "has_jointable": "yes"
      },
      "permissions": {},
      "fieldpermissions": "",
      "columns": [
        {
          "header": "Рендер",
          "dataIndex": "render",
          "width": 1,
          "sortable": "false",
          "show_in_grid": 0,
          "renderer": "this.renderChunk",
          "renderchunktpl": "[[!VideoJsonToPlaceholders? &json=`[[+video]]` &prefix=`video.`]]"
        },
        {
          "header": "Название",
          "dataIndex": "video_title",
          "sortable": "false",
          "show_in_grid": 1,
          "renderer": "this.renderChunk",
          "renderchunktpl": "[[+video.title]]"
        },
        {
          "header": "Видео",
          "dataIndex": "video_video",
          "sortable": "false",
          "show_in_grid": 1,
          "renderer": "this.renderChunk",
          "renderchunktpl": "<iframe src=\"[[+video.video]]\" width=\"160\" height=\"90\" frameborder=\"0\"></iframe>"
        },
        {
          "header": "Превью",
          "dataIndex": "video_image",
          "sortable": "false",
          "show_in_grid": 1,
          "renderer": "this.renderChunk",
          "renderchunktpl": "<img src=\"[[+video.image]]\" width=\"160\">"
        },
        {
          "header": "Время",
          "dataIndex": "video_duration",
          "width": 100,
          "sortable": "false",
          "show_in_grid": 1,
          "renderer": "this.renderChunk",
          "renderchunktpl": "[[+video.videoDuration]]"
        },
        {
          "header": "videoId",
          "dataIndex": "video_videoId",
          "sortable": "false",
          "show_in_grid": "0",
          "renderer": "this.renderChunk",
          "renderchunktpl": "[[+video.videoId]]"
        },
        {
          "header": "Описание",
          "dataIndex": "video_description",
          "sortable": "false",
          "show_in_grid": 1,
          "renderer": "this.renderChunk",
          "renderchunktpl": "[[+video.desc]]"
        }
      ],
      "category": ""
    }
SYAN
09 февраля 2026, 01:00
modx.pro
3
2 475
0

Предложение по развитию сообщества: Создание каталога портфолио/реализованных кейсов на MODX с демо лучших практик

Хочу предложить к обсуждению идею, которая, на мой взгляд, способна вывести развитие сообщества MODX на новый уровень и дать мощный импульс для популяризации движка среди заказчиков и разработчиков.

Предложение к админам сайта — можно ли сделать на сайте вашем Каталог реализованных сайтов или Портфолио?
Суть предложения: Создать на сайте раздел «Каталог реализованных проектов» или «Портфолио».
Это будет структурированная база данных готовых сайтов, где разработчики и студии смогут публиковать информацию о своих работах, включая:
  • Ссылку на готовый проект;
  • Описание кейса и задач внедрения;
  • Примеры работ (фото, видео);
  • Перечень использованных решений и компонентов (с привязкой к Modstore).
Почему это важно и какие выгоды это даст сообществу:
  1. Популяризация и демонстрация возможностей MODX. Потенциальные заказчики часто судят о системе по работам, которые они могут увидеть. Каталог живых проектов станет лучшей витриной возможностей движка, доказывающей его гибкость и мощность.
  2. Продвижение разработчиков и студий. Для исполнителей это бесплатный (или опционально платный) инструмент для демонстрации компетенций, поиска новых заказчиков и укрепления репутации.
  3. Помощь коллегам и новичкам. Каталог станет базой знаний «Лучших практик». Анализируя, какие компоненты и как используются в реальных проектах, сообщество будет быстрее развиваться, а новички — учиться на готовых работах.
  4. Синергия с Modstore. Было бы идеально организовать привязку кейсов к страницам компонентов на Modstore. Посетитель, изучая дополнение, мог бы сразу перейти на реальные сайты, где оно используется, и увидеть его в деле. Это повысит ценность самого маркетплейса. И даже было бы неплохо иметь возможность обратиться к специалистам кто умеет ставить и настраивать то или иное дополнение. Очень частая проблема надо поставить и найти кто поможет быстро сделать настройку.
Олег Захаров
20 февраля 2026, 14:36
modx.pro
780
+1

MiniShop3: итоги февраля и версия 1.6.0

За февраль выпустили четыре релиза — с 1.4.0 до 1.6.0. Переработали авторизацию, добавили уведомления, переписали личный кабинет. 242 файла, 36 закрытых задач. Расскажу по порядку, начиная с самого крупного.

Николай Савин
03 марта 2026, 22:08
modx.pro
443
+12

ms3Variants - Реализация вариантов одного товара в MiniShop3


Компонент для управления вариантами товаров в MiniShop3. Позволяет создавать товары с различными комбинациями опций (цвет, размер и др.), каждая из которых имеет собственную цену, остатки и изображение.
Николай Савин
10 февраля 2026, 20:59
modx.pro
1
413
+14

ms3RecentlyViewed - Недавно просмотренные товары для MiniShop3



Компонент для MiniShop3: вывод блока «Недавно просмотренные товары».
Список хранится в браузере (localStorage или cookie) или в БД для авторизованных пользователей и автоматически заполняется при посещении страниц товаров.

Иван Бочкарев
18 февраля 2026, 21:18
modx.pro
392
+9

Унифицированное отображение разноформатных изображений без обрезки (решено)

Периодически возникает ситуация, когда шаблон ориентирован на горизонтальные изображения

//уменьшение основного изображение без обрезки
{set $image=$image | phpthumbon: 'w=600&h=300&zc=0&q=100'}
//создание фонового изображение с размытием и яркостью
{set $bg_image= $image | phpthumbon: 'w=600&h=300&zc=1&fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9 &fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9&fltr[]=blur|9&fltr[]=brit|15'}
//создание вотермарка из основного изображения
{set $filter = 'fltr[]=wmi|' ~ $image ~ '|C|100'}
//генерация нового изображения с фоном
{set $image=$bg_image|phpthumbon:$filter}

В комментарии вариант с использованием сниппета

Далее, несколько может не самых удачных примеров для понимания принципа

SYAN
02 марта 2026, 19:23
modx.pro
1
384
+3

Обновление содержимого полей товаров через ImportX

Всем привет.

Хочу обновить у товаров на основании данных сеошника поля сео-заголовок и сео-описание. Поля реализованы как дополнительные поля.
Импорт планирую делать через importX.
Импорт имеет вид:
id;alias;tv1;tv2
304;alias-tovara;Какой-то текст заголовка для сео;Какой-то текст описания для сео
Все на локальной машине вроде проходит нормально, но в процессе возникли вопросы.
Виталий
13 февраля 2026, 15:43
modx.pro
360
0

CrawlerDetect — бесплатная защита MODX‑сайтов от ботов, создано с любовью к сообществу



Представляю CrawlerDetect — бесплатный компонент для MODX, созданный с заботой о сообществе. Он использует движок JayBizzle/Crawler-Detect, который надёжно распознаёт тысячи известных ботов и краулеров.
Иван Бочкарев
20 февраля 2026, 07:31
modx.pro
333
+12