[СДЕЛАЙ САМ] Авторизация и регистрация по СМС

Всем привет. Я ни на что не претендую, прекрасно понимаю, что вариантов решения данной задачи много, и мой, наверное, не лучший, но меня попросили написать решение и эту инструкцию, поэтому поехали.
Нам понадобится:
  1. Console
  2. CMP Generator
  3. AjaxForm
  4. miniShop2
!!! ВАЖНО!!! Корзина и оформление заказа должны быть разными страницами.

Начнём с чанка корзины. Добавим туда ссылку на страницу оформления заказа для авторизованных пользователей и кнопку открытия формы авторизации/регистрации для неавторизованных пользователей.
{if $_modx->user.id > 0}           
    <a href="{13 | url}">  <!-- 13 это id ресурса с формой оформления заказа -->
         <button type="submit" class="knopka2">
             Оформить заказ
       </button>
    </a>          
{else}
    <button type="button" data-toggle="modal" data-target="#modalSMSAuth" class="knopka2">
        Оформить заказ
    </button>
{/if}

Сюда же добавим модальное окно с формой авторизации/регистрации
<!-- MODAL -->
<div class="modal" id="modalSMSAuth" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal title</h5>
        <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close">X</button>
      </div>
      <div class="modal-body row">
            {'!AjaxForm' | snippet:[
                'snippet' =>'smsAuth',
                'form' =>'@FILE chunks/forms/checkcodeForm.html',
                'redirectTo' => 12 
            ]}
      </div>
    </div>
  </div>
</div>
<!-- /MODAL -->

Чанк с формой авторизации/регистрации checkcodeForm.html выглядит так
<form class="col-12">
  <div class="row">
    <label class="col-12">
      <input type="tel" class="d-block input_sms_nam" name="phone" placeholder="+7(999)888-77-66">
      <span class="error error_phone"></span>
    </label>
    <label class="col-12">
        <small class="modal__text">Нет пароля или Вы его забыли? 
 Оставьте поле пустым, и мы отправим новый пароль в СМС</small>
      <input type="text" class="d-block input_sms_cod" name="code" placeholder="Пароль">
      <span class="error error_code"></span>
    </label>
    <div class="col-lg-6 offset-lg-3">
      <button class="btn btn-primary">Войти</button>
    </div>
  </div>
</form>

Чтобы закончить с клиентской частью, создаем файл с названием, например, sms-auth.js, с таким контентом
// Маска ввода номера телефона
let phoneMask = document.querySelectorAll('input[type="tel"]');
for (let i = 0; i < phoneMask.length; i++) {
    phoneMask[i].addEventListener('keydown', function (event) {
        if (!(event.key == 'ArrowLeft' || event.key == 'ArrowRight' || event.key == 'Backspace' || event.key == 'Tab')) {
            event.preventDefault()
        }
        let mask = '+7 (111) 111-11-11'; // Задаем маску

        if (/[0-9\+\ \-\(\)]/.test(event.key)) {

            let currentString = this.value;
            let currentLength = currentString.length;

            if (/[0-9]/.test(event.key)) {
                for (let i = currentLength; i < mask.length; i++) {
                    let number = event.key;
                    if (mask[i] == '1') {
                        if (i == 4 && number != '9') {
                            number = '';
                        }
                        this.value = currentString + number;
                        break;
                    }
                    currentString += mask[i];
                }
            }
        }
    });
}

// Обработка ответа сервера
$(document).on('af_complete', function (e, response) {
    if(response.data.redirectTo){
        setTimeout(function(){
            document.location.href = response.data.redirectTo; // просто делаем редирект на нужную страницу
        },2000);
    } 
});

И внесём небольшую правку в исходники ajaxForm, а именно закомментируем в файле assets/components/ajaxform/js/default.js 66 строку, там должно быть это
form[0].reset();

Для отправки СМС будем использовать api SMSЦентр. Скачиваем файл smsc_api.php и в начале файла меняем
// SMSC.RU API (smsc.ru) версия 3.8 (03.07.2019)
define("SMSC_LOGIN", "login");            // логин клиента
define("SMSC_PASSWORD", "password");    // пароль
define("SMSC_POST", 0);                    // использовать метод POST
define("SMSC_HTTPS", 0);                // использовать HTTPS протокол
define("SMSC_CHARSET", "windows-1251");    // кодировка сообщения: utf-8, koi8-r или windows-1251 (по умолчанию)
define("SMSC_DEBUG", 0);                // флаг отладки
define("SMTP_FROM", "api@smsc.ru");     // e-mail адрес отправителя
на
// Подключаем Modx API
define('MODX_API_MODE', true);
require_once $_SERVER['DOCUMENT_ROOT'] . '/index.php';

// SMSC.RU API (smsc.ru) версия 3.8 (03.07.2019)

define("SMSC_LOGIN", $modx->getOption('smsc_login'));		// логин клиента
define("SMSC_PASSWORD", $modx->getOption('smsc_password'));	// пароль
define("SMSC_POST", $modx->getOption('smsc_post'));			// использовать метод POST
define("SMSC_HTTPS", $modx->getOption('smsc_https'));		// использовать HTTPS протокол
define("SMSC_CHARSET", $modx->getOption('smsc_charset'));	// кодировка сообщения: utf-8, koi8-r или windows-1251 (по умолчанию)
define("SMSC_DEBUG", $modx->getOption('smsc_debug'));		// флаг отладки
define("SMTP_FROM", $modx->getOption('smsc_smtp_from'));    // e-mail адрес отправителя
Все параметры указанные в методах $modx->getOption надо создать в системных настройках, думаю это понятно.

Теперь можно создать сниппет, который будет запускать отправку смс, назовём его codeSender
<?php
$file = MODX_CORE_PATH . 'components/codesender/smsc_api.php';
if(!file_exists($file)){
    $modx->log(1, 'FILE NOT FOUND');
}
include_once $file;

if(!isset($translit)){
    $translit = 1; // по умолчанию отправляем транслит, там можно впихнуть больше текста
}
$send = send_sms($number, $message, $translit);

return $send;
Чтобы можно было проверять отправленные коды, нужно их куда-то сохранять. Для этого создадим в базе данных новую таблицу modx_codesender на 5 полей с настройками как на скриншоте ниже

Теперь надо рассказать modx как с этой таблицей работать, для этого воспользуемся дополнением CMPGenerator. Открываем его интерфейс и нажимаем Create Package. Появившуюся форму заполняем как наследующем скрине

Потом жмём сохранить. Переходим в Console и выполняем вот такой код
$modx->addExtensionPackage('codesender', '[[++core_path]]components/codesender/model/');
Очищаем кэш и проверяем всё ли у нас получилось, выполнив следующий код
$sms = $modx->newObject('Codesender', array('username' => '79998887766', 'code' => '0000', 'text' => 'Test'));
$sms->save();
Если ошибок нет, значит всё хорошо, если есть внимательно проверяем пути к файлам.

Можем переходить к сниппету smsAuth, основные моменты я прокомментировал.
<?php
if(!($username = preg_replace('/[^0-9]/', '', trim($_POST['phone'])))){ // проверяем введен ли номер телефона
    return  $AjaxForm->error('Исправьте ошибки в форме',  array('phone' => 'Введите номер телефона'));
}
$user = $modx->getObject('modUser', array('username'=> $username)); // получаем пользователя
$inputCode = (int)$_POST['code']; // получаем введенный код
$codesender = $modx->getObject('Codesender', array('username' => $username)); // получаем данные об отправленном коде из БД
if(!$inputCode){ // если код не ввели
    if($codesender){ // и если мы его уже отправили
        $timeOut = 120; // задаем задержку повторной отправки 
        $sendTime = strtotime($codesender->get('date')); // получаем дату предыдущей отправки кода
        $now = time(); // получаем текущее время
        if(($now - $sendTime) < $timeOut){ // не даем слишком часто запрашивать пароль
            return $AjaxForm->error('Повторно запросить код Вы сможете через ' .($timeOut - ($now - $sendTime)) . ' cек.' ,   array());
        }
    }
    $inputCode = $modx->runSnippet('genPassword', array('length' => 6, 'type' => 'code')); // генерируем код
    $text =  'Пароль для сайта ' . $modx->getOption('http_host').': ' . $inputCode; // формируем сообщение
    $send = $modx->runSnippet('codeSender', array('number' => $username, 'message' => $text)); //отправляем СМС с кодом      
        
    // если отправка не удалась
    if(count($send) == 2){
        switch ($send[1]){
            case -7:
                $message = 'Неверный формат номера телефона. Код подтверждения не отправлен.';
            break;
            case -8:
                $message = 'Сообщение на указанный номер не может быть доставлено. Введите другой номер и повторите попытку';
            break;
            default:
                $message = 'Код подтверждения не отправлен. Введите другой номер и повторите попытку';
            break;
        }
        return  $AjaxForm->error($message,  array());
    }
        
    // записываем код в БД
    $sms = $modx->newObject('Codesender', array('username' => $username, 'code' => $inputCode, 'text' => $text));
    $sms->save();
    if($user){
        // обновляем пароль пользователя
        $user->set('password', $inputCode); 
        $user->save();
    }    
    return $AjaxForm->success('Пароль успешно отправлен', array());
}

$params = array('username' => $username, 'password' => $inputCode, 'group' => 2, 'active' => true, 'blocked' => false); // массив с данными для авторизации/регистрации
if(!$user){ // проверяем существует ли пользователь
    $dbcode = (int)$codesender->get('code'); // получаем отправленный код
    if($inputCode != $dbcode){ // проверяем совпадают ли коды
        return  $AjaxForm->error('Исправьте ошибки в форме',   array('code'=> 'Введен неверный пароль'));
    }
    $codesender->remove(); // удаляем код из БД если проверка прошла успешно

    // если нет - регистрируем
    $user = $modx->runSnippet('Registration', array('params' => $params)); 
    $user->set('password', $inputCode);
    $user->save();
}

$response = $modx->runSnippet('Authorization', array('params' => $params));
        
if(!$response['success']){
    return  $AjaxForm->error($response['message'],  array());
}
$redirectId = $scriptProperties['redirectTo'] ?: 1;
$redirectUrl = $modx->makeUrl($redirectId, 'web', '', 'full');
return $AjaxForm->success($response['message'], array('redirectTo' => $redirectUrl));

Тут вы вероятно заметили, что есть сниппет genPassword, изнутри он выглядит так
<?php
if(!$length){
    $length = $modx->getOption('password_min_length');
}
if(!$type){
    $type = 'pass';
}
//$modx->log(1, 'LENGTH ' . $length);
//$modx->log(1, 'TYPE ' . $type);
/* Более сложный генератор паролей с любыми символами */
$password = "";
/* Массив со всеми возможными символами в пароле */
switch ($type){
    case 'pass':
        $arr = array(
            'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l',
            'm', 'n', 'o', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x',
            'y', 'z', 'A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V',
            'W', 'X', 'Y', 'Z', '1', '2',
            '3', '4', '5', '6', '7', '8',
            '9', '0', '#', '!', "?", "&"
            );
        break;
    case 'hash':
        $arr = array(
            'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l',
            'm', 'n', 'o', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x',
            'y', 'z', 'A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V',
            'W', 'X', 'Y', 'Z', '1', '2',
            '3', '4', '5', '6', '7', '8',
            '9', '0'
            );
        break;    
    case 'code':
        $arr = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
    break;
}

for ($i = 0; $i < $length; $i++){
    $password .= $arr[mt_rand(0, count($arr) - 1)]; // Берём случайный элемент из массива
}

return $password;

Регистрацию я сделал так (Registration)
<?php
$user = $modx->newObject('modUser'); // создаем пользователя
$user->set('username', $params['username']); // задаем имя пользователя
$user->set('password', $params['password']); // задаем пароль

$profile = $modx->newObject('modUserProfile'); // создаем профиль
if(!$params['email']){
    $params['email'] = $params['username'] . '@' . $_SERVER['HTTP_HOST']; 
}
   
if(!$params['phone']){
    $params['phone'] = preg_replace('/(\d)(\d{3})(\d{3})(\d{2})(\d{2})$/', '+7(\2)\3-\4-\5', $params['username']);
}    

$params['extended'] = $extended;
// инициализируем поля
$profile->fromArray($params);
$user->addOne($profile); // добавляем профиль к пользователю
$profile->save(); // сохраняем профиль
$user->save(); // сохраняем пользователя
$user->joinGroup($params['group'],1); // добавляем пользователя в группу только после сохранения объекта пользователя
setcookie('user_id', $user->get('id'));
if(!$user){
    return false;
}
return $user;

Авторизацию вот так (Authorization)
<?php
$loginData = array(
    'username' => $params['username'],
    'password' => $params['password'],
    'rememberme' => 1,
    'login_context' =>  'web'
);
$loginResponse = $modx->runProcessor('/security/login', $loginData);
if ($loginResponse->isError()) {
    $modx->log(modX::LOG_LEVEL_ERROR, 'login error. Username: '.$params['username'].', Message: '.$loginResponse->getMessage());
    return array('success' => false, 'message' => $loginResponse->getMessage());
}
return array('success' => true, 'message' => 'Вы успешно авторизованы');

Теперь нужно сделать так, чтобы minishop2 использовал уже зарегистрированного пользователя в качестве получателя заказа. Для этого немного расширим класс core/components/minishop2/model/minishop2/msorderhandler.class.php, а точнее метод submit. Для этого создаем файл core/elements/code/customOrderHandler.class.php с вот таким содержимым
<?php
if(!class_exists('msOrderInterface')) {
    require_once dirname(dirname(dirname(__FILE__))) . '/components/minishop2/model/minishop2/msorderhandler.class.php';
}

class customOrderHandler extends msOrderHandler implements msOrderInterface{
    /**
     * @param array $data
     *
     * @return array|string
     */
    public function submit($data = array())
    {
        $response = $this->ms2->invokeEvent('msOnSubmitOrder', array(
            '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 = array();
        foreach ($requires as $v) {
            if (!empty($v) && empty($this->order[$v])) {
                $errors[] = $v;
            }
        }
        if (!empty($errors)) {
            return $this->error('ms2_order_err_requires', $errors);
        }

        // custom code
        $user_id = $_COOKIE['user_id'] ?: $this->ms2->getCustomerId();
        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;
        $createdon = date('Y-m-d H:i:s');
        /** @var msOrder $order */
        $order = $this->modx->newObject('msOrder');
        $order->fromArray(array(
            'user_id' => $user_id,
            'createdon' => $createdon,
            'num' => $this->getNum(),
            'delivery' => $this->order['delivery'],
            'payment' => $this->order['payment'],
            'cart_cost' => $cart_cost,
            'weight' => $cart_status['total_weight'],
            'delivery_cost' => $delivery_cost,
            'cost' => $cart_cost + $delivery_cost,
            'status' => 0,
            'context' => $this->ms2->config['ctx'],
        ));

        // Adding address
        /** @var msOrderAddress $address */
        $address = $this->modx->newObject('msOrderAddress');
        $address->fromArray(array_merge($this->order, array(
            'user_id' => $user_id,
            'createdon' => $createdon,
        )));
        $order->addOne($address);

        // Adding products
        $cart = $this->ms2->cart->get();
        $products = array();
        foreach ($cart as $v) {
            if ($tmp = $this->modx->getObject('msProduct', array('id' => $v['id']))) {
                $name = $tmp->get('pagetitle');
            } else {
                $name = '';
            }
            /** @var msOrderProduct $product */
            $product = $this->modx->newObject('msOrderProduct');
            $product->fromArray(array_merge($v, array(
                'product_id' => $v['id'],
                'name' => $name,
                'cost' => $v['price'] * $v['count'],
            )));
            $products[] = $product;
        }
        $order->addMany($products);

        $response = $this->ms2->invokeEvent('msOnBeforeCreateOrder', array(
            'msOrder' => $order,
            'order' => $this,
        ));
        if (!$response['success']) {
            return $this->error($response['message']);
        }

        if ($order->save()) {
            $response = $this->ms2->invokeEvent('msOnCreateOrder', array(
                'msOrder' => $order,
                'order' => $this,
            ));
            if (!$response['success']) {
                return $this->error($response['message']);
            }

            $this->ms2->cart->clean();
            $this->clean();
            if (empty($_SESSION['minishop2']['orders'])) {
                $_SESSION['minishop2']['orders'] = array();
            }
            $_SESSION['minishop2']['orders'][] = $order->get('id');

            // Trying to set status "new"
            $response = $this->ms2->changeOrderStatus($order->get('id'), 1);
            if ($response !== true) {
                return $this->error($response, array('msorder' => $order->get('id')));
            }

            // Reload order object after changes in changeOrderStatus method
            $order = $this->modx->getObject('msOrder', array('id' => $order->get('id')));

            /** @var msPayment $payment */
            if ($payment = $this->modx->getObject('msPayment',
                array('id' => $order->get('payment'), 'active' => 1))
            ) {
                $response = $payment->send($order);
                if ($this->config['json_response']) {
                    @session_write_close();
                    exit(is_array($response) ? json_encode($response) : $response);
                } else {
                    if (!empty($response['data']['redirect'])) {
                        $this->modx->sendRedirect($response['data']['redirect']);
                    } elseif (!empty($response['data']['msorder'])) {
                        $this->modx->sendRedirect(
                            $this->modx->context->makeUrl(
                                $this->modx->resource->id,
                                array('msorder' => $response['data']['msorder'])
                            )
                        );
                    } else {
                        $this->modx->sendRedirect($this->modx->context->makeUrl($this->modx->resource->id));
                    }

                    return $this->success();
                }
            } else {
                if ($this->ms2->config['json_response']) {
                    return $this->success('', array('msorder' => $order->get('id')));
                } else {
                    $this->modx->sendRedirect(
                        $this->modx->context->makeUrl(
                            $this->modx->resource->id,
                            array('msorder' => $response['data']['msorder'])
                        )
                    );

                    return $this->success();
                }
            }
        }

        return $this->error();
    }
}
В Console запускаем следующий код
if ($miniShop2 = $modx->getService('miniShop2')) {
    $miniShop2->addService('order', 'CustomOrderHandler',
        '{core_path}elements/code/customOrderHandler.class.php'
    );
}
В системных настройках меняем значение ms2_order_handler_class на customOrderHandler.

На этом всё!
Артур Шевченко
17 мая 2021, 00:31
modx.pro
5
4 804
+1
Поблагодарить автора Отправить деньги

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

Eugene Generalov
17 мая 2021, 07:41
0
Хорошая статья.
От себя могу добавить, сниппет genPassword можно не писать. У класса modUser есть метод generatePassword. В который тоже можно передать длину пароля
    Артур Шевченко
    17 мая 2021, 07:53
    0
    Спасибо за комментарий, но причина появления сниппета genPassword не в длине пароля, а в его составе, в своём сниппите я могу генерировать числовой код, а если использовать стандартный механизм там будут разные символы, а не только числа. Людям удобнее вводить числа, как показала практика и надёжность пароля им не важна. Ещё я думаю, что наверное после авторизации надо менять пароль ещё раз на более сложный, чтоб никто другой войти не смог. Хотя возможно это излишняя предосторожность.
    Павел Бигель
    17 мая 2021, 10:48
    +1
    Простыня кода, три строчки текста, никаких объяснений, нулевая информативность.
      Артур Шевченко
      17 мая 2021, 11:55
      0
      Что именно тебе непонятно в коде?
        Павел Бигель
        17 мая 2021, 12:29
        +6
        Мне как раз все понятно.
        {if $_modx->user.id > 0}           
            <a href="[[~13]]">  <!-- 13 это id ресурса с формой оформления заказа -->
                 <button type="submit" class="knopka2">
                     Оформить заказ
               </button>
            </a>          
        {else}
            <button type="button" data-toggle="modal" data-target="#modalSMSAuth" class="knopka2">
                Оформить заказ
            </button>
        {/if}
        Помесь стандартного и fenom синтаксиса это дурной тон.

        {'!AjaxForm' | snippet:[
                        'snippet' =>'custConfirm',
                        'form' =>'@FILE chunks/forms/confirmForm.html',
                        'validate'=>'phone:required'
                    ]} 
                    {'!AjaxForm' | snippet:[
                        'snippet' =>'custCheckCode',
                        'customValidators' => 'validateCode'
                        'form' =>'@FILE chunks/forms/checkcodeForm.html',
                        'validate'=>'phone:required,code:validateCode',
                        'redirectTo' => 13 
                    ]}
        Два некешированных вызова AjaxForm. А че ж не 5? Эту задачу можно решить одним вызовом сниппета.

        <?php
        $file = MODX_CORE_PATH . 'components/codesender/smsc_api.php';
        if(!file_exists($file)){
            $modx->log(1, 'FILE NOT FOUND');
        }
        include_once $file;
        
        if(!isset($translit)){
            $translit = 1; // по умолчанию отправляем транслит, там можно впихнуть больше текста
        }
        $send = send_sms($number, $message, $translit);
        
        return $send;
        Такого рода задачи решаются не сниппетами. Сниппеты это вообще про другое.
        Более того, ты же ниже создаешь пакет, какая проблема примутить к нему класс-контроллер?

        Тут вы вероятно заметили, что есть сниппет genPassword, изнутри он выглядит так
        Опять же а) Не решаются такие задачи сниппетами б) mt_rand не является безопасным для задач уровня генерации паролей. Ты бы это знал, если бы писал это сам.

        Итак, выходим на финишную прямую. Сниппет custCheckCode отвечает за проверку кода, авторизацию и регистрацию пользователей
        Снова, такие задачи не решаются сниппетами.

        // если ошибок нет
            if($_POST['phone']){
                $username = preg_replace('/[^0-9]/', '', trim($_POST['phone']));
                $params = array('username' => $username, 'password' => $_POST['code'], 'group' => 2, 'active' => true, 'blocked' => false);
                $params = array_merge($_POST, $params);
                $user = $modx->getObject('modUser', array('username' => $username));
                if(!$user){
                    $modx->log(1, 'Регистрация');
                    $user = $modx->runSnippet('Registration', array('params' => $params));
                }
                $user->set('password', $_POST['code']);
                $user->save();
                $response = $modx->runSnippet('Authorization', array('params' => $params));
                if(!$response['success']){
                    return  $AjaxForm->error($response['message'],  array());
                }
                $redirectId= $scriptProperties['redirectTo'] ?: 1; // при вызове сниппета мы передаём id ресурса, на который нужно сделать редирект, если не передаем, то редирект будет на главную.
                $redirectUrl = $modx->makeUrl($redirectId , 'web', '', 'full'); 
                return $AjaxForm->success($response['message'], array('redirectTo' => $redirectUrl)); 
            }
        }
        Из поста данные не валидируются. Пахнет безопасностью! Либо впадлу, либо не знаешь что это такое.
        Тут кстати гораздо логичнее была бы проверка на false.

        Кастомный валидатор validateCode содержит такой код:
        Опять никакой валидации. Еще и сырой запрос (вызов которого не оправдан ничем)

        Регистрацию я сделал так (Registration)
        Ни единой проверки на то что модель не сохранилась. Неверное сохранение аггеративных моделей.

        Итого: куски сомнительного кода не несущие абсолютно никакой ценности.
        Я бы ни слова ни сказал, даже плюсик бы залепил если бы тут было хоть какое-то объяснение происходящего.
        Почему так, а не иначе, почему это лучше, а это хуже.

        Итого ты не учишь, а даешь людям копировать плохие практики.
        Абсолютно заслуженный минус
          Артур Шевченко
          17 мая 2021, 13:00
          0
          Люди предупреждены. А за ревью спасибо.
            Артур Шевченко
            17 мая 2021, 13:02
            0
            Из поста данные не валидируются.
            Хочешь сказать FormIt их не проверяет?
              Павел Бигель
              17 мая 2021, 13:04
              0
              Валидирует.
              У тебя валидация уровня required, я туда могу тебе прислать все что угодно.
            Денис Мижеревич
            17 мая 2021, 13:05
            +1
            Раскритиковали…
            Напишите и выложите свой подобный кейс, а то желающих поделиться разработкой не много
              Павел Бигель
              17 мая 2021, 13:09
              +1
              Без проблем!
              100$ и я готов написать решение многократно элегантнее этого с пояснением каждого шага для новичков. Готовы проспонсировать статью?
              Дорого?
              Можете использовать компонент Office который стоит половину стоимости и реализует задачу гораздо лучше
              modstore.pro/packages/users/office
            Артур Шевченко
            17 мая 2021, 13:08
            0
            Итого ты не учишь, а даешь людям копировать плохие практики.
            1. Не тот у меня уровень, чтобы кого-то учить.
            2. Я во вступлении написал, что это не лучший из возможных вариантов.
            3. Есть ты и твои комментарии, кто-то проникнется и не станет это копировать, кто-то допилит, а кому-то и так сойдет.
              Павел Бигель
              17 мая 2021, 14:04
              +1
              1. Ну так назови цикл статьей «Скопируй сам». Почему «Сделай»?
              2. Это не лучший, а прям наихудший способ. Кстати если так хотелось прикрутить авторизацию, то можно было доработать Login.
                Артур Шевченко
                17 мая 2021, 14:58
                0
                Это твоё личное мнение, мне нравится моё решение, оно простое, понятное и работает. Да оно далеко от идеала, но это не отменяет всего вышесказанного.
        Алекксандр
        01 сентября 2021, 19:33
        0
        Извиняюсь. Только что столкнулся с modx. Пытаюсь решить задачу авторизации через СМС.
        Вполне логичный и читабельный проект, только хотелось поподробнее про пути к файлам.(где их создавать?).
          Артур Шевченко
          07 сентября 2021, 23:26
          0
          Про какие пути?
            Алекксандр
            08 сентября 2021, 11:44
            0
            Если ошибок нет, значит всё хорошо, если есть внимательно проверяем ПУТИ К ФАЙЛАМ. Про эти.
              Артур Шевченко
              08 сентября 2021, 11:49
              0
              Я думал из контекста понятно, что речь идёт о пути к файла «компонента» codesender, вот про это [[++core_path]]components/codesender/model/
                Алекксандр
                08 сентября 2021, 12:09
                0
                Как видно, не всем понятно из контекста.)))
              Алекксандр
              08 сентября 2021, 11:47
              0
              При проверке выдает ошибку, о том что не может создать экземпляр класса.
                Артур Шевченко
                08 сентября 2021, 11:52
                0
                Что я могу сказать, у меня тоже так было нужно проверять пути, название файлов и классов.
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            25