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

Николай Савин
1 час назад
0
Не нужно меня поддерживать в данном конкретном случае. Прошу убрать лишнее
Дмитрий
2 часа назад
0
В общем убил целый день, но… так у меня ничего и не вышло.
не могу зарегистрировать класс.

содержание самого файла my_msorderhandler.class.php:

<?php
if (!class_exists('msOrderHandler')) {  // проверяем интерфейс заказа
    require_once MODX_CORE_PATH . 'components/minishop2/handlers/msorderhandler.class.php';
}

class my_OrderHandler extends msOrderHandler {  // implements msOrderInterface не нужен
    public function submit($data = [])
        {
            
            $response = $this->ms2->invokeEvent('msOnSubmitOrder', [
                'data' => $data,
                'order' => $this,
            ]);
            if (!$response['success']) {
                return $this->error($response['message']);
            }
            if (!empty($response['data']['data'])) {
                $this->set($response['data']['data']);
            }
    
            $response = $this->getDeliveryRequiresFields();
            if ($this->ms2->config['json_response']) {
                $response = json_decode($response, true);
            }
            if (!$response['success']) {
                return $this->error($response['message']);
            }
            $requires = $response['data']['requires'];
    
            $errors = [];
            foreach ($requires as $v) {
                if (!empty($v) && empty($this->order[$v])) {
                    $errors[] = $v;
                }
            }
            if (!empty($errors)) {
                return $this->error('ms2_order_err_requires', $errors);
            }
    
            $user_id = $this->getReceiverId();
            if (empty($user_id) || !is_int($user_id)) {
                return $this->error(is_string($user_id) ? $user_id : 'ms2_err_user_nf');
            }
    
            $cart_status = $this->ms2->cart->status();
            if (empty($cart_status['total_count'])) {
                return $this->error('ms2_order_err_empty');
            }
    
            $delivery_cost = $this->getCost(false, true);
            $cart_cost = $this->getCost(true, true) - $delivery_cost;
            $num = $this->getNewOrderNum();
    
            /** @var msOrder $msOrder */
            $msOrder = $this->storageHandler->getForSubmit(
                compact('user_id', 'num', 'cart_cost', 'cart_status', 'delivery_cost')
            );
    
            $response = $this->ms2->invokeEvent('msOnBeforeCreateOrder', [
                'msOrder' => $msOrder,
                'order' => $this,
            ]);
            if (!$response['success']) {
                return $this->error($response['message']);
            }
    
            if ($msOrder->save()) {
                $response = $this->ms2->invokeEvent('msOnCreateOrder', [
                    'msOrder' => $msOrder,
                    'order' => $this,
                ]);
                if (!$response['success']) {
                    return $this->error($response['message']);
                }
    
                if ($this->storage === 'session') {
                    $this->ms2->cart->clean();
                    $this->clean();
                }
                if (empty($_SESSION['minishop2']['orders'])) {
                    $_SESSION['minishop2']['orders'] = [];
                }
                $_SESSION['minishop2']['orders'][] = $msOrder->get('id');
    
                // Trying to set status "new"
                $status_new = $this->modx->getOption('ms2_status_new', null, 1);
                $response = $this->ms2->changeOrderStatus($msOrder->get('id'), $status_new);
                if ($response !== true) {
                    return $this->error($response, ['msorder' => $msOrder->get('id')]);
                }
    
                // Reload order object after changes in changeOrderStatus method
                /** @var msOrder $msOrder */
                $msOrder = $this->modx->getObject('msOrder', ['id' => $msOrder->get('id')]);
    
                /** @var msPayment $payment */
                $payment = $this->modx->getObject(
                    'msPayment',
                    ['id' => $msOrder->get('payment'), 'active' => 1]
                );
                if ($payment) {
                    $response = $payment->send($msOrder);
                    if ($this->config['json_response']) {
                        @session_write_close();
                        echo is_array($response) ? json_encode($response) : $response;
                        die();
                    }
                    if (!empty($response['data']['redirect'])) {
                        $this->modx->sendRedirect($response['data']['redirect']);
                    }
                    if (!empty($response['data']['msorder'])) {
                        $redirect = $this->modx->context->makeUrl(
                            $this->modx->resource->id,
                            ['msorder' => $response['data']['msorder']]
                        );
                        $this->modx->sendRedirect($redirect);
                    }
                    $this->modx->sendRedirect($this->modx->context->makeUrl($this->modx->resource->id));
                } else {
                    if ($this->config['json_response']) {
                        return $this->success('', ['msorder' => $msOrder->get('id')]);
                    }
                    $redirect = $this->modx->context->makeUrl(
                        $this->modx->resource->id,
                        ['msorder' => $msOrder->get('id')]
                    );
                    $this->modx->sendRedirect($redirect);
                }
                return $this->success();
            }
    
            return $this->error();
        }
        
        
        public function getCustomerId() {
    error_log("🔥🔥🔥 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;
    }
        
}

согласно священописанию в консоле делаю так:
<?php
if ($miniShop2 = $modx->getService('miniShop2')) {
  $miniShop2->addService('order', 'my_OrderHandler',
      '{core_path}components/minishop2/custom/order/my_msorderhandler.class.php');
}
Пробую, и ничего не меняется. На пару с ИИ добрел до системной настройки, с ключем ms2_order_handler_class и там указал свой класс. Перестало работать вообще. Т.е. тыкаю на кнопку «сделать заказ» на самом сайте и ничего не происходит.
Из чего делаю вывод, что класс мой не зарегистрировался.

Дальше меняю настройку ms2_services.
она у меня выглядела так:
{«cart»:[],«order»:[«my_OrderHandler»],«payment»:[],«delivery»:{«mydelivery»:"{core_path}components\/minishop2\/custom\/delivery\/my_msdeliveryhandler.class.php"}}

т.к. я уже ранее подрубал собственную службу доставки, и она вроде как исправно работает, делаю по образу и подобию с order, хотя ИИ визжит, что так делать нельзя, и у этой настройки должно быть только название класса.

В логах живет вот такая ошибка:

(ERROR @ /home/c/cz42644/test-iflower/public_html/core/components/minishop2/model/minishop2/minishop2.class.php: 484) [miniShop2] Could not load custom class at "/home/c/cz42644/test-iflower/public_html/my_OrderHandler"
Т.е. я так понимаю, он ищет файл с моей службой почему-то не так где надо, по сути в корне. И как ему указать, где искать — не ведаю.

Вообщем, итог такой, что либо ничего не меняется, либо не работает вовсе.

Что и где я делаю не так?
Артур Шевченко
2 часа назад
0
Вот тебе моё мнение, через полгода-год заказчикам будем всё равно на чём ты будешь делать сайт, гораздо больше их будет волновать вопрос: умеешь ли ты пользоваться ИИ-агентам? Именно ИИ позволяет ускорится в два-три раза и твой стэк уже не имеет значение, важно будет только умение решать бизнес-задачи и ставить задачи ИИ-агенту. Так что рекомендую начинать осваивать именно эти технологии.
Артур Шевченко
3 часа назад
0
minishop3 недавно вышел, он ещё не прошёл обкатку и без опыта в разработке переезжать, наверное, не стоит. Многое из того, что тебе нужно @Николай Савин уже либо сделал, либо это у него в планах. Поддержать Николая можно донатом.

Отправить на карту СберБанка messenger.online.sberbank.ru/sl/kPLh3bewMs93eW9Hl
Отправить на карту Тинькофф www.tinkoff.ru/sl/2V9U9RrcJZP
Отправить на YooMoney yoomoney.ru/fundraise/RPkkYwNcL7A.230131
QIWI к сожалению недоступен до восстановления их банковской лицензии
Казахстан Каспи по номеру +7 701 282 77 37
Если Вы находитесь в Казахстане и хотите сделать пожертвование на счет ИП, можем сделать официальные документы (при условии адекватной суммы).

Крипта
USDT TRON (TRC20) TU3gzTp2Rt3wPcts1xiXtwWiu7riC8s56q
Bitcoin — BTC 1LxpXEBBCEQYoswu8tGo6TEU1C8JfmRT18
The Open Network — TON UQAeMteI6GWxyCan_QOXX8sKnDAr5ApF-9FK7f-g2qxVlz-e

Со своей стороны обещаю скорый выпуск интеграции со СДЕК.
Олег Захаров
5 часов назад
0
Компонент очень нужный и мне кажется будет востребован.
У меня тут задача стоит сделать что-то подобное на сайте на движке на MODX 2.8 — там есть старые другие решения.
Но задумываюсь о глобальном переносе на 3-ю версию.
Олег Захаров
5 часов назад
0
ну я подумал что возможно ты сам лично уже где-то у себя на заказе рабочем внедрил и есть рабочий сайт
Олег Захаров
6 часов назад
0
А какой компонент для личного кабинета и авторизации используется?
Андрей
6 часов назад
+1
вот нашел еще проще решение для текущего пользователя, без id
$profile = $modx->user->getOne('Profile');
return $profile->get('extended')[$input];
и вызов
[[!userField? &input=`tv_name`]]
но все равно хотелось бы понять причины и может есть штатное решение.
Олег Захаров
7 часов назад
0
тут пришла мысль что никто не захочет просто так делиться своим опытом за бесплатно. Можно было бы сделать статьи и кейсы платными? Типа хочешь прочитать инструкцию или кейс по настройке того или иного компонента — ведь кто-то потратил время на написание инструкции — стоимость 100 руб.
Тут желательно конечно не переусердствовать. Но и обидно не будет тратить время на написание кейсов и инструкций.
А чтобы было понятно что больше всего интересует людей — было бы неплохо сделать список вопросов, на которые нужны ответы — типа голосование или например в описании компонентов добавить голосование за необходимость написания кейсов и готовность оплатить или сбор средств на написание инструкций. Понятно что есть стандартные инструкции, но они не всегда понятные.
И часто бывают брошенные компоненты. Можно было бы собирать средства на его дальнейшее развитие.
Николай Савин
8 часов назад
0
А ты думаешь люди прям десятками уже интернет-магазины на моих решениях клепают? Это очень оптимистично ))
Постараюсь на этой неделе добить и выложить демо-сайт. Там будет и обычный miniShop3 и поиск с фильтрами, и демонстрация вариантов в том числе.
Олег Захаров
8 часов назад
0
Можете прислать примеры живых сайтов не решении? На которых данное решение уже установлено?
Дмитрий
Сегодня в 10:59
0
и вот еще какой вопрос…

в документации прописано вот так:

if (!class_exists('msDeliveryInterface')) {
  require_once dirname(dirname(dirname(__FILE__))) . '/model/minishop2/msdeliveryhandler.class.php';
}

class msDeliveryHandlerMsk extends msDeliveryHandler implements msDeliveryInterface {
пример подключения собственного класса доставки.

ИИ говорит мне, что интерфейс не нужно подключать для msOrderHandler

т.е. этот кусок получается лишний в моем случае?
if (!class_exists('msDeliveryInterface')) {
  require_once dirname(dirname(dirname(__FILE__))) . '/model/minishop2/msdeliveryhandler.class.php';
}
Наумов Алексей
Сегодня в 10:08
0
Добрый день! Я этот компонент давно делал, и еще лет 5 не возвращался к нему… он работоспособен, все в этом плане нормально (ну по крайней мере с php не выше 8 версии я думаю).

Что касается интеграции с mSync — вам придется самим думать как это сделать, руками создавать записи в БД. Никаких других механизмов нет.

Смотрите схему БД, там всё просто: github.com/createit-ru/msProductKits/blob/master/core/components/msproductkits/model/schema/msproductkits.mysql.schema.xml
Дмитрий
Сегодня в 09:44
0
Спасибо! Но есть ряд вопросов, в рамках того, что я сильно не сведущ…
я уже как-то подключал свой обработчик доставки.
имеет ли значение в какой папке будет лежать новый файл my_msOrderHandler

метод это когда начинается с «public function»?

Т.е. посути в новом файле должно остаться метод submit с теми изменениями что вы написали и мой метод, который я постил в предыдущем комментарии?
Алексей Корчагин
Сегодня в 07:22
0
Доброго времени суток.
Подскажите, как правильно составлять наборы при выгрузке товаров через mSync? Делать прямые запросы на добавление этих связей? Собираюсь делать в событии где известен id ресурса.
Тодор
Вчера в 19:45
0
  • Расширяешь класс msOrderHandler (поключение)
  • Копируете из старого submit
  • Заменяешь там
    $user_id = $this->ms2->getCustomerId();
    на
    $user_id = $this->getReceiverId();
  • Добавляешь новый метод getReceiverId в новообразованный класс и меняешь его как хочешь
Андрей
Вчера в 10:22
0
Вижу, спасибо.
Ошибочно решил, что если есть в документации minishop2, то в старых версиях есть и сам код не посмотрел.
Иван Бочкарев
Вчера в 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 дней, а не количеством просмотров. Архив уменьшает объём основной таблицы, сохраняя агрегаты для аналитики.