[СДЕЛАЙ САМ] Ajax Регистрация, Авторизация, Сброс пароля, Редактирование профиля

Задача: сделать ajax регистрацию, авторизацию, сброс пароля и редактирование пользователя, используя минимум сторонних компонентов.
Почему минимум? Ну часть из тех компонентов, что реализуют подобный функционал, платная (Office, ajaxLogin) и бюджеты есть не всегда на них есть. Некоторые (ajaxLogin) в данный момент не доступны из-за смерти автора. Общий недостаток большинства компонентов это то, что они загружают дополнительные скрипты и стили, что порой приводит к необходимости дополнительно оптимизировать сайт. Компонент Login не работает по ajax. Однако это можно обойти , но runSnippet запустит много всего и не факт что это всё нужно в данный момент. Поэтому предлагаю своё решение.
Нам понадобится:
1. AjaxForm;
2. FormIt;
3. Этот репозиторий.

Порядок действий:
1. Скопировать core/elements/snippets/ajaxidentification.class.php.
2. Создать в админке сниппеты
2.1 AjaxIdentification (код лежит в core/elements/snippets/hooks/AjaxIdentification.php);
2.2 userExists (код лежит в core/elements/snippets/validators/userExists.php);
2.3 userNotExists (код лежит в core/elements/snippets/validators/userNotExists.php;
2.4 ActivateUser (код лежит в core/elements/snippets/ActivateUser.php) // этот можно вызывать прям из файла если хотите.
3. В вывоз AjaxForm в параметр hooks первым значением добавить AjaxIdentification, сюда же добавить параметр method ( register — регистрация, login — авторизация, forgot — восстановление доступа, update — обновление данных).
4. В js на событие af_complete добавить редирект на нужные страницы. Ссылки можно получать
из мета-тегов с именами типа loginSuccessUrl.
Всё. Далее немного потока сознания и пояснений.


Делать редирект после регистрации или авторизации, передавая ссылку через мета-тег, мне кажется неудобным. Поскольку AjaxForm, к сожалению, не предоставляет простого способа изменить класс обработчик, как это сделано в miniShop2, нужно немного подправить исходники. А именно нужно заменить в ajaxform.class.php метод handleFormit() на код приведённый ниже
public function handleFormIt(array $scriptProperties = array())
    {
        $plPrefix = isset($scriptProperties['placeholderPrefix'])
            ? $scriptProperties['placeholderPrefix']
            : 'fi.';

        $errors = array();
        foreach ($scriptProperties['fields'] as $k => $v) {
            if (isset($this->modx->placeholders[$plPrefix . 'error.' . $k])) {
                $errors[$k] = $this->modx->placeholders[$plPrefix . 'error.' . $k];
            }
        }

        if (!empty($this->modx->placeholders[$plPrefix . 'error.recaptcha'])) {
            $errors['recaptcha'] = $this->modx->placeholders[$plPrefix . 'error.recaptcha'];
        }

        if (!empty($this->modx->placeholders[$plPrefix . 'error.recaptchav2_error'])) {
            $errors['recaptcha'] = $this->modx->placeholders[$plPrefix . 'error.recaptchav2_error'];
        }

        if (!empty($errors)) {
            $message = !empty($this->modx->placeholders[$plPrefix . 'validation_error_message'])
                ? $this->modx->placeholders[$plPrefix . 'validation_error_message']
                : 'af_err_has_errors';
            $status = 'error';
        } else {
            $message = isset($scriptProperties['successMessage'])
                ? $scriptProperties['successMessage']
                : 'af_success_submit';
            $status = 'success';
            
            // вот что я добавил
            if($scriptProperties['redirectId']){
                $redirectUrl = $this->modx->makeUrl($scriptProperties['redirectId'], '', '', 'full');
                $errors['redirectUrl'] = $redirectUrl;
                $errors['redirectTimeout'] = $scriptProperties['redirectTimeout'] ?: 2000;
            }
        }
        return $this->$status($message, $errors);
    }
Эта манипуляция позволит в js на событие af_complete в массиве response.data получить два параметра redirectUrl (ссылка для перехода) и redirectTimeout (время задержки до перехода для функции setTimeout). Сами данные мы будем передавать через вызов AjaxForm в параметрах redirectId (id ресурса для перехода) и redirectTimeout.

Теперь немного о классе AjaxIdentification. Я хотел использовать процессоры и только их, но так и не смог решить проблему с правами доступа для процессора security/user/update.Поэтому метод update работает без процессора.

Поля extended должны иметь в имени префикс extended_ или любой другой, но тогда в вызов нужно добавить параметр extendedFieldPrefix со значением префикса.

Если требуется ручная проверка аккаунта перед предоставлением доступа, укажите в вывозе параметр moderate со значением true.

Если требуется подтверждение почты и активация пользователя только после этого укажите параметр activation=1, при этом в $_POST должен быть указан email, а параметр moderate должен быть false. Так же в вызов нужно добавить второй хук FormItAutoResponder. Время действия ссылки можно передать в параметре activationUrlTime в секундах. По умолчанию 10800 секунд или 3 часа. Значение id ресурса на который будет вести ссылка активации из письма можно передать в параметре activationResourceId, по умолчанию 1.

Плавно переходим к сниппету ActivateUser. Его нужно вызывать на странице активации. Он вернёт true если пользователь активирован и false в противном случае. Дальнейшие действия на ваше усмотрение.

Далее два валидатора, которые применяются к полю из которого берётся username ( параметр usernameField, по умолчанию username):
1. userExists возвращает true если пользователь НЕ существует. Этот валидатор используется при регистрации.
2. userNotExists возвращает true если пользователь существует. Это валидатор используется при восстановлении пароля, чтобы не отправлять письмо несуществующим пользователя.

Ещё немного о параметрах.
'authenticateContexts' — список контекстов разделённых запятыми в которые нужно авторизовать пользователя.
'passwordField' — имя поля с паролем, по умолчанию password.
'autoLogin' — если true и не нужна активация и модерация пользователь после регистрации будет авторизован, по умолчанию false.
'usergroups' — список id групп разделенный запятыми, в которые следует добавить пользователя при регистрации.
'usergroupsField' — позволяет пользователю самостоятельно выбрать группы, если данный параметр указан, предыдущий будет проигнорирован.
В двух последних параметрах допустима запись вида '2:1:0,3:1:1', где первое число это id группы, второе id роли ( по умолчанию 0 — superuser, 1 — member), третье rank — не знаю для чего, но в процессоре было, я оставил.

В репозитории есть примеры всех вызовов и чанков, в том числе и писем.

Напоследок, сам репозиторий это форк с AjaxForm из которого я выпилил jQuery, переписал весь js и ещё кое-что. Кому интересно читайте тут .
Артур
16 мая 2022, 13:33
modx.pro
1 295
+1
Поблагодарить автора Отправить деньги

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

Павел Бигель
16 мая 2022, 13:39
+1
?
    Aleksandr Huz
    16 мая 2022, 16:03
    +2
    3. AjaxForm+Register — я так делал, способ простой, но грубый.
    Теперь есть сложный и еще грубее, поздравляю!

    Умный в гору не пойдет, умный гору обойдет!
    Какая-то непонятная инструкция, какие-то непонятные манипуляции с исходниками. Только из статьи понятно, что это ajax регистрация/авторизация, а должно быть в заголовке.

    Советую потратить побольше времени и сделать нормальный компонент.
      Артур
      16 мая 2022, 19:08
      +1
      Спасибо, действительно забыл в заголовке про Ajax. И потрачу больше времени и сделаю компонент.
        Aleksandr Huz
        16 мая 2022, 19:11
        0
        Не ошибается тот, кто ничего не делает.
        Рад, что нормально воспринимаешь критику.
          Артур
          16 мая 2022, 19:17
          0
          Я ради критики сюда это и выкладываю. Иначе сложно понять, где я косячу. Жаль что, подробные разборы моего кода бывают редко, но это объяснимо. С миру по нитки что-нибудь сошью)))
            Николай Савин
            17 мая 2022, 09:01
            +1
            Артур, ты если ради критики выкладываешь — то лучше бы это делать в разделе вопросы.
            Раздел готовые решения предполагает собственно готовое решение. Причем понятное случайному посетителю (хорошо бы).
            Я три раза перечитал текст, но так и не понял какую задачу ты решал, в чем ее решение, и почему это решение такое. Просто поток мыслей — без какой-либо цели.
              Артур
              17 мая 2022, 12:13
              0
              Ты прав, изложил я свои мысли коряво. Поэтому заметку переписал полностью. Надеюсь теперь суть задачи и решение более очевидны.
                Николай Савин
                17 мая 2022, 12:26
                0
                Да по тексту стало сильно понятнее. Но по-прежнему не понятно зачем такой инструментарий использовать. Особенно при формулировке
                используя минимум сторонних компонентов.
                Куда более правильно было был написать малюсенький JS скрипт, который слушает форму, отправляет условные логин-пароль на сервер. А Сервер в свою очередь тоже, используя простенький PHP скрипт заводит запись в базе. Ну или логинит.

                Зачем сюда подключать твои здоровенные комбайны, да еще форкнутые, ума не приложу. Это ж изобретение велосипеда, которых тут на форуме наверное с несколько десятков наберется.
                Ты бы лучше свою деятельность направил на реанимацию ajaxLogin
                  Артур
                  17 мая 2022, 12:56
                  0
                  Куда более правильно было был написать малюсенький JS скрипт, который слушает форму, отправляет условные логин-пароль на сервер. А Сервер в свою очередь тоже, используя простенький PHP скрипт заводит запись в базе. Ну или логинит.
                  AjaxForm оно и есть, а FormIt реализует возможность валидации и отправки писем.

                  Зачем сюда подключать твои здоровенные комбайны
                  Это ты конечно мне польстил))) Здоровенный)))

                  Это ж изобретение велосипеда
                  Не спорю и не навязываю. Мой велосипед не хуже других, так что пусть будет, мне на память.

                  Ты бы лучше свою деятельность направил на реанимацию ajaxLogin
                  Я работаю на комбайном форм, который совместит в себе AjaxForm, Quiz, Login и Formalicious. Постараюсь ближайшие месяцы закончить. А это временный вариант.
                Роман
                17 мая 2022, 13:25
                +2
                Может сделать раздел «Песочница». =)
                  Артур
                  17 мая 2022, 13:35
                  0
                  Мне кажется «Готовые решения» и так отчасти песочница. Я же вот дал ссылку на репозиторий, копируй себе и твори что хочешь))) Другие тоже так или иначе выкладывают в этом разделе код, с которым можно делать что угодно.
        Василий Столейков
        17 мая 2022, 07:57
        0
        2. ajaxLogin — платный и из-за смерти автора он более недоступен.
        Может я пропустил информацию об этом, но всё равно печально слышать такие новости, я про смерть автора…
          vectorserver
          19 мая 2022, 12:51
          -2
          Слишком много гемора!
          Вот пример, JS + LOGIN (https://vectorserver.ru/test/ajaxform-login.html )
          Вызов:
          [[!AjaxLogin_vectorserver? &snippet=`Login`]]
          
          [[!AjaxLogin_vectorserver? &snippet=`Login` &id=`q`]]
          
          [[!AjaxLogin_vectorserver? &snippet=`Register` &id=`123`]]
          Код сниппета AjaxLogin_vectorserver:
          <?php
          /* @global $modx*/
          /** @var TYPE_NAME $scriptproperties */
          /** @var TYPE_NAME $snippet */
          
          /** @var TYPE_NAME $id */
          $id = $id??$modx->resource-id;
          $uniqid = "form_".md5($snippet.$id);
          $url = $modx->resource->uri;
          
          
          $js = "<script>
              let $uniqid = document.getElementById('{$uniqid}').getElementsByTagName('form')[0];
              if($uniqid){
                  $uniqid.addEventListener('submit', (e) => {
                      
                  
                      fetch($uniqid.action, {
                         method: 'POST',
                         body: new FormData($uniqid)
                      }).then(function (response) {
                          // The API call was successful!
                          return response.text();
                      }).then(function (data) {
                          // This is the HTML from our response
                          var parser = new DOMParser();
                          var doc = parser.parseFromString(data, \"text/html\");
                          $uniqid.innerHTML = doc.getElementById('{$uniqid}').innerHTML;
                      }).catch(function (err) {
                          // There was an error
                          console.warn('Something went wrong.', err);
                      });;
                  
                   // on form submission, prevent default
                  e.preventDefault();
                });
              }
            
          </script>";
          
          $runSnippet = "<div id='{$uniqid}' class='AjaxLogin_vectorserver'>{$modx->runSnippet($snippet)}</div>\n{$js}";
          return $runSnippet;
            Артур
            19 мая 2022, 13:43
            0
            А как показать уведомление? Как редирект сделать? Заполнение профиля работать будет? Если да, то после сохранения поля очистятся или нет?
              vectorserver
              19 мая 2022, 14:48
              0
              Допилил редирект если он нужен:
              Параметры:
              • &id: Уникальный ID, если есть конфликты форм
              • &snippet:Login,ForgotPassword,UpdateProfile,Register
              • &redirect:[[~1]]
              • &chunkForm: имя чанка или html код (Для ForgotPassword,UpdateProfile,Register не используются в параметрах tpl, для этого сделан параметр)

              //Login
              [[!AjaxLogin_vectorserver? &snippet=`Login`]]
              
              //ForgotPassword
              [[!AjaxLogin_vectorserver? 
              &snippet=`ForgotPassword` 
              &id=`q`
              &redirect=`[[~1]]`
              ]]
              
              
              <h2>UpdateProfile</h2>
              [[!AjaxLogin_vectorserver?
                  &snippet=`UpdateProfile` 
                  &useExtended=`0`
                  &chunkForm=`[[$UProfile]]`
              ]]
              Код сниппета:
              <?php
              /* @global $modx*/
              /** @var TYPE_NAME $scriptproperties */
              /** @var TYPE_NAME $snippet */
              
              /** @var TYPE_NAME $id */
              $chunkForm = $chunkForm??'';
              //Уникальный ID
              $id = $id??$modx->resource->id;
              $uniqid = "form_".md5($snippet.$id);
              
              //Если нужен редирект 
              $redirect = $redirect??false;
              
              //JS TMP
              $js = "<script>
                  let $uniqid = document.getElementById('{$uniqid}').getElementsByTagName('form')[0];
                  
                  if($uniqid){
                      $uniqid.addEventListener('submit', (e) => {
                          
                          let redirectTo = '{$redirect}';
                      
                          fetch($uniqid.action, {
                             method: 'POST',
                             body: new FormData($uniqid)
                          }).then(function (response) {
                              // The API call was successful!
                              return response.text();
                          }).then(function (data) {
                              // This is the HTML from our response
                              var parser = new DOMParser();
                              var doc = parser.parseFromString(data, \"text/html\");
                              $uniqid.innerHTML = doc.getElementById('{$uniqid}').innerHTML;
                              if(redirectTo){
                                  window.location = '{$redirect}';
                              }
                          }).catch(function (err) {
                              // There was an error
                              console.warn('Something went wrong.', err);
                          });
                      
                       // on form submission, prevent default
                      e.preventDefault();
                    });
                  }
                
              </script>";
              
              $runSnippet = "<div id='{$uniqid}' class='AjaxLogin_vectorserver'>{$modx->runSnippet($snippet)}{$chunkForm}</div>\n{$js}";
              return $runSnippet;
                Артур
                19 мая 2022, 15:07
                0
                Понятно. Можно проще. Не писать свой сниппет для отправки ajax а использовать AjaxForm где в параметре snippet передать имя своего сниппета, а в параметре, например, method передать название сниппета из компонента Login. Таким образом сохраняется функционал обоих компонентов и свой js можно не писать. Но я по-прежнему не уверен, что использовать runSnippet оптимальное решение.
                vectorserver
                19 мая 2022, 14:49
                0
                Уведомления берутся стандартные из чанков LOGIN
                  Артур
                  19 мая 2022, 14:58
                  0
                  Это прекрасно, а выводить их как? Прикручивать плагин или писать свой вариант для показа уведомлений?
                Николай Савин
                19 мая 2022, 13:59
                +1
                За JS в PHP жирный минус. Зачем?! А главное Зачем?!
                  vectorserver
                  19 мая 2022, 14:03
                  0
                  Это не готовое решение, а пример реализации!
                    Николай Савин
                    19 мая 2022, 14:05
                    0
                    Ну тогда уж приводите пример в чистом JS, тем более раз вы показываете в контексте «Как делать правильно»
                      Артур
                      19 мая 2022, 14:09
                      0
                      Твой пример очень похож на вот этот, с той разницей, что пример по ссылке гораздо легче понять.
                    Павел Бигель
                    20 мая 2022, 15:28
                    +1
                    я думал хуже чем Артур сделать было нереально, но ты справился
                      Артур
                      20 мая 2022, 19:09
                      0
                      Всё познаётся в сравнении)))
                    R2m0x94 (Vasily)
                    23 мая 2022, 16:03
                    0
                    Я делал через Office+HybridAuth, только немного пришлось доработать обработчик на активацию
                      Артур
                      23 мая 2022, 18:01
                      0
                      Думаю и сюда можно HybridAuth прикрутить, но у меня такой задачи не было.
                      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                      26