Всего 125 658 комментариев

Иван Бочкарев
19 февраля 2026, 09:27
+1
Привет, Алексей.

1. Как определяем ботов
Проверка идёт по User-Agent в ms3rv_is_bot() (helpers.php). Используется regex по типичным маркерам краулеров:
bot|crawl|slurp|spider|mediapartners|googlebot|bingbot|yandex|baiduspider|duckduckbot|teoma|ahrefs|semrush|mj12bot|dotbot|rogerbot|screaming|petalbot|bytespider
Проверка выполняется до сохранения в БД — при block_bots=true (по умолчанию) запросы от таких User-Agent не пишутся. Это не идеально (подделка UA, новые боты), но отсекает большую часть краулеров.

2. Большой каталог и много пользователей
Есть несколько механизмов, чтобы таблица не росла бесконечно:

Ограничение дублей. UNIQUE по (user_id, session_id, product_id) — для каждой пары «пользователь + товар» хранится одна строка. Повторный просмотр того же товара только обновляет viewed_at, новых строк не добавляется.
TTL и автоочистка. По умолчанию ttl_days=90 и auto_cleanup_enabled=true. Плагин на OnWebPageInit раз в день удаляет записи старше 90 дней.
Месячное архивирование. При archive_enabled=true (по умолчанию) данные за прошедший месяц агрегируются в ms3recentlyviewed_monthly (product_id, view_count, unique_users), а детальные строки из основной таблицы удаляются. Объём основной таблицы остаётся ограниченным.

Итог: Рост таблицы ограничен числом уникальных пар «пользователь × товар» за последние 90 дней, а не количеством просмотров. Архив уменьшает объём основной таблицы, сохраняя агрегаты для аналитики.
Наумов Алексей
19 февраля 2026, 07:51
+2
Привет! Дай распрошу)
как ботов отслеживаешь? Пару раз сталкивался, интересны подходы.
И вот что думаешь, большой каталог + большое кол-во пользователей не убьет таблицу просмотров в БД? Как ни крути именно просмотров может быть ну очень много, особенно если не всех ботов получится отсечь.
Дмитрий
18 февраля 2026, 21:55
0
minishop2-4.4.2-pl
Артур Шевченко
18 февраля 2026, 21:22
0
Версия минишопа какая?
Дмитрий
18 февраля 2026, 18:50
0
Во, вот так хочу, что бы было

public function getCustomerId() {
        $customer = null;

        $response = $this->invokeEvent('msOnBeforeGetOrderCustomer', [
            'order' => $this->order,
            'customer' => $customer,
        ]);
        if (!$response['success']) {
            return $response['message'];
        }

        if (!$customer) {
            $data = $this->order->get();
            $email = $data['email'] ?? '';
            $receiver = $data['receiver'] ?? '';
            $phone = $data['phone'] ?? '';
            
            // Функция для приведения телефона к международному формату +КодСтраныНомер
            $formatPhone = function($phone) {
                if (empty($phone)) return '';
                
                // Удаляем все нецифровые символы
                $phone = preg_replace('/[^0-9]/', '', $phone);
                
                // Определяем код страны по умолчанию (Россия)
                $defaultCountryCode = '7';
                
                // Если номер начинается с 8 (Россия)
                if (substr($phone, 0, 1) == '8') {
                    $phone = $defaultCountryCode . substr($phone, 1);
                }
                // Если номер без кода (10 цифр) - добавляем код по умолчанию
                elseif (strlen($phone) == 10) {
                    $phone = $defaultCountryCode . $phone;
                }
                // Если номер начинается с 7 (уже есть код России)
                elseif (substr($phone, 0, 1) == '7' && strlen($phone) == 11) {
                    // Оставляем как есть
                }
                // Если номер с другим кодом страны
                elseif (strlen($phone) > 11 || (strlen($phone) == 11 && substr($phone, 0, 1) != '7')) {
                    // Оставляем как есть (уже содержит код страны)
                }
                
                // Добавляем знак + в начало
                return '+' . $phone;
            };
            
            // Приводим телефон к единому формату
            $formattedPhone = $formatPhone($phone);
            
            // Обновляем телефон в данных заказа
            if ($formattedPhone !== $phone) {
                $data['phone'] = $formattedPhone;
                $this->order->set($data);
                $phone = $formattedPhone;
            }
            
            // Формируем receiver, если он пустой
            if (empty($receiver)) {
                if (!empty($phone)) {
                    $receiver = preg_replace('/[^0-9]/', '', $phone);
                } elseif (!empty($email)) {
                    $receiver = substr($email, 0, strpos($email, '@'));
                } else {
                    $receiver = uniqid('user_', false);
                }
            }
            
            // Формируем email, если он пустой
            if (empty($email)) {
                if (!empty($phone)) {
                    $cleanPhone = preg_replace('/[^0-9]/', '', $phone);
                    $email = 'user_' . $cleanPhone . '@' . $this->modx->getOption('http_host');
                } else {
                    $email = $receiver . '@' . $this->modx->getOption('http_host');
                }
            }

            // Если пользователь авторизован
            if ($this->modx->user->isAuthenticated()) {
                $profile = $this->modx->user->Profile;
                
                if (!$profile->get('email')) {
                    $profile->set('email', $email);
                }
                
                if (!empty($phone) && $profile->get('mobilephone') != $phone) {
                    $profile->set('mobilephone', $phone);
                }
                
                if (!empty($receiver) && $profile->get('fullname') != $receiver) {
                    $profile->set('fullname', $receiver);
                }
                
                $profile->save();
                $customer = $this->modx->user;
                
            } else {
                // Поиск существующего пользователя
                $c = $this->modx->newQuery('modUser');
                $c->leftJoin('modUserProfile', 'Profile');
                
                $filter = [];
                
                if (!empty($phone)) {
                    $filter['modUser.username'] = $phone;
                    $filter['OR:Profile.mobilephone:='] = $phone;
                }
                
                if (!empty($email)) {
                    $filter['OR:Profile.email:='] = $email;
                }
                
                $c->where($filter);
                $c->select('modUser.id');
                
                // Пытаемся найти пользователя
                if (!$customer = $this->modx->getObject('modUser', $c)) {
                    // Создаем нового пользователя
                    $userData = [
                        'username' => !empty($phone) ? $phone : $email,
                        'password' => md5(rand()),
                        'active' => 1
                    ];
                    
                    $customer = $this->modx->newObject('modUser', $userData);
                    
                    $profileData = [
                        'fullname' => $receiver,
                    ];
                    
                    if (!empty($email)) {
                        $profileData['email'] = $email;
                    }
                    
                    if (!empty($phone)) {
                        $profileData['mobilephone'] = $phone;
                    }
                    
                    $profile = $this->modx->newObject('modUserProfile', $profileData);
                    $customer->addOne($profile);
                    
                    // Добавляем настройку языка
                    $setting = $this->modx->newObject('modUserSetting');
                    $setting->fromArray([
                        'key' => 'cultureKey',
                        'value' => $this->modx->getOption('cultureKey', null, 'en', true),
                    ], '', true);
                    $customer->addMany($setting);
                    
                    if (!$customer->save()) {
                        $customer = null;
                    } elseif ($groups = $this->modx->getOption('ms2_order_user_groups', null, false)) {
                        $groupRoles = array_map('trim', explode(',', $groups));
                        foreach ($groupRoles as $groupRole) {
                            $groupRole = explode(':', $groupRole);
                            $roleId = null;
                            if (count($groupRole) > 1 && !empty($groupRole[1])) {
                                $roleId = is_numeric($groupRole[1]) ? (int)$groupRole[1] : $groupRole[1];
                            }
                            $customer->joinGroup($groupRole[0], $roleId);
                        }
                    }
                } else {
                    // Пользователь найден - обновляем данные
                    $profile = $customer->getOne('Profile');
                    if ($profile) {
                        $changed = false;
                        
                        if (!empty($phone) && $profile->get('mobilephone') != $phone) {
                            $profile->set('mobilephone', $phone);
                            $changed = true;
                        }
                        
                        if (!empty($email) && $profile->get('email') != $email) {
                            $profile->set('email', $email);
                            $changed = true;
                        }
                        
                        if (!empty($receiver) && $profile->get('fullname') != $receiver) {
                            $profile->set('fullname', $receiver);
                            $changed = true;
                        }
                        
                        if ($changed) {
                            $profile->save();
                        }
                    }
                }
            }
        }

        $response = $this->invokeEvent('msOnGetOrderCustomer', [
            'order' => $this->order,
            'customer' => $customer,
        ]);
        if (!$response['success']) {
            return $response['message'];
        }

        return $customer instanceof modUser ? $customer->get('id') : 0;
    }
Дмитрий
18 февраля 2026, 18:18
0
хм… хочу что бы при формировании заказа минишоп генерировал username не email пользователя, а номер телефона
Артур Шевченко
18 февраля 2026, 14:14
0
Никак. Если расскажешь задачу, возможно, подскажут другое решение.
Артур Шевченко
18 февраля 2026, 14:12
+1
Версия miniShop2, old_status там появился очень недавно
Дмитрий
17 февраля 2026, 10:07
0
Здравствуйте, компонент куплен, на основной домен ставится, на dev. не ставится,
Could not generate encryption key
Vehicle 04b9f528f736384b46f7132420614ef0 in transport msproductremains-2.1.28-pl failed to install and indicated the process should be aborted.
Невозможно установить пакет с подписью: msproductremains-2.1.28-pl

Но пробовал на основном домене, при установленном пакете, сделать заказ — вылетает ошибка и заказ не проходит /core/components/gallery/elements/tv/output/
в системных настройках

mspr_active_bcstatus
Нет
2026-02-17, 9:58 am

Проверка до добавления в корзину
mspr_active_before_add
Нет
2026-02-17, 9:47 am

Проверка до сохранения заказа
mspr_active_before_order
Нет
Олег
14 февраля 2026, 18:07
0
Добрый вечер!
Кто смог настроить оплату в этом компоненте?

вставил по инструкции в чанк tpl.msoneclick.send ссылку с параметром [[+payment_link]], но он (параметр) пустой.
происходит просто редирект на главную страницу
Виталий
14 февраля 2026, 13:53
0
Баг с обновлениями подтвержден в ишьюсах модуля — github.com/modmore/importX/issues/64
Решения, кроме частичного, предложенного в ветке этого ишьюса (https://github.com/modmore/importX/issues/64#issuecomment-201342383) до сих пор нет.
Имхо, автору не стоило данный функционал вообще упоминать, как рабочий. Только потратил кучу времени на тестирование
Виталий
14 февраля 2026, 13:53
0
Баг с обновлениями подтвержден в ишьюсах модуля — github.com/modmore/importX/issues/64
Решения, кроме частичного, предложенного в ветке этого ишьюса (https://github.com/modmore/importX/issues/64#issuecomment-201342383) до сих пор нет.
Имхо, автору не стоило данный функционал вообще упоминать, как рабочий. Только потратил кучу времени на тестирование
Виталий
13 февраля 2026, 21:44
0
Предварительный анализ показал, что этот модуль при обновлении не работает нормально с магазином (под вопросом как с другими ресурсами).
Идет затирание целого ряда других полей у товаров, включая родительский ресурс, менеджеры и другие поля.

Очень странная картинка — по идее, при обновлении полей, должны изменяться данные только тех полей, которые были в csv. А в результате идет затирание целого ряда полей, которых даже не прописывали в csv.

Вобще, функционал данного модуля для импорта-обновления уже существующих ресурсов мягко сказать странный. Нафига, для обновления 2 полей товара/ресурса мне прописывать его алиас, родительский ресурс и другие поля, которые вообще трогать не планировал? По идее, все, что надо — это айдишник ресурса и содержимое обновляемых полей.
Максим
12 февраля 2026, 22:44
0
Сочетание клавиш CTRL+ENTER не работает? Или только у меня… Приходится каждый раз нажимать «плэй»
Сергей Фещуков
11 февраля 2026, 07:30
0
Тут скоррее вопрос к msProductRemains, нужно его научить смотреть remains из своей таблицы
Да, верно. Ранее этого поля не было в стандартном minishop2, поэтому всё и работало без проблем.
Есть момент, зачем-то это поле сделали типа int, в некоторых ситуациях разработчики хотят иметь дробный остаток (сталкивался с таким, поэтому сделал в своём компонент тип float для остатков). Надеюсь разработчики minishop2 услышать комментарий из commit на это изменение
maybe better float than int?
Сергей Карпович
10 февраля 2026, 21:41
0
Это таблица компонента msProductRemains
он использует поле remains, но в Minishop2 4.0.0 тоже появилось это поле, и что то пошло не так. Может оно всегда было, но не участвовало в схеме, хз.

Тут скоррее вопрос к msProductRemains, нужно его научить смотреть remains из своей таблицы
Николай Савин
10 февраля 2026, 20:39
0
Это что за таблица такая modx_ms2_product_remains? В miniShop2 такой нет. Наверное отдельный компонент.