Финансовая поддержка VPS, бесплатные дополнения и docs Донаты оплачивают сервер и двигают бесплатные дополнения MODX и документацию. Поддержать

Всего 126 093 комментария

Алексей Корчагин
Алексей Корчагин
Доброго времени суток.
Подскажите, как правильно составлять наборы при выгрузке товаров через mSync? Делать прямые запросы на добавление этих связей? Собираюсь делать в событии где известен id ресурса.
Тодор
Тодор
  • Расширяешь класс msOrderHandler (поключение)
  • Копируете из старого submit
  • Заменяешь там
    $user_id = $this->ms2->getCustomerId();
    на
    $user_id = $this->getReceiverId();
  • Добавляешь новый метод getReceiverId в новообразованный класс и меняешь его как хочешь
Иван Бочкарев
Иван Бочкарев
Привет, Алексей.

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 дней, а не количеством просмотров. Архив уменьшает объём основной таблицы, сохраняя агрегаты для аналитики.
Наумов Алексей
Наумов Алексей
Привет! Дай распрошу)
как ботов отслеживаешь? Пару раз сталкивался, интересны подходы.
И вот что думаешь, большой каталог + большое кол-во пользователей не убьет таблицу просмотров в БД? Как ни крути именно просмотров может быть ну очень много, особенно если не всех ботов получится отсечь.
Дмитрий
Дмитрий
Во, вот так хочу, что бы было

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;
    }
Дмитрий
Дмитрий
Здравствуйте, компонент куплен, на основной домен ставится, на 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
Нет
Виталий
Виталий
Баг с обновлениями подтвержден в ишьюсах модуля — github.com/modmore/importX/issues/64
Решения, кроме частичного, предложенного в ветке этого ишьюса (https://github.com/modmore/importX/issues/64#issuecomment-201342383) до сих пор нет.
Имхо, автору не стоило данный функционал вообще упоминать, как рабочий. Только потратил кучу времени на тестирование
Виталий
Виталий
Баг с обновлениями подтвержден в ишьюсах модуля — github.com/modmore/importX/issues/64
Решения, кроме частичного, предложенного в ветке этого ишьюса (https://github.com/modmore/importX/issues/64#issuecomment-201342383) до сих пор нет.
Имхо, автору не стоило данный функционал вообще упоминать, как рабочий. Только потратил кучу времени на тестирование
Виталий
Виталий
Предварительный анализ показал, что этот модуль при обновлении не работает нормально с магазином (под вопросом как с другими ресурсами).
Идет затирание целого ряда других полей у товаров, включая родительский ресурс, менеджеры и другие поля.

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

Вобще, функционал данного модуля для импорта-обновления уже существующих ресурсов мягко сказать странный. Нафига, для обновления 2 полей товара/ресурса мне прописывать его алиас, родительский ресурс и другие поля, которые вообще трогать не планировал? По идее, все, что надо — это айдишник ресурса и содержимое обновляемых полей.