• Изменено

Все что я буду сегодня делать, разрабатывается на основе плагина, который мы с вами сделали в инструкциях: "Создание плагина через Artisan. Инструкция для новичков. Часть 1", Создание плагина через Artisan. Инструкция для новичков. Часть 2".

Если вы хотите сразу следовать за мной, без предварительной подготовки своего плагина, можете скачать готовый из GitHub.

Разработка AJAX формы обратного звонка

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

Давайте создадим новый компонент, в нашем плагине. В нем мы реализуем весь необходимый функционал, и сделаем настройки для того чтобы можно беспроблемно через CMS страницу указать email для уведомлений.
Вводим в консоли:
php artisan create:component Author.PluginName ComponentName

Я создал компонент с названием Feedback.

Давайте сразу укажем наш новый компонент в plugin.php нашего плагина.

Пример того как я добавил:

public function registerComponents()
{
    return [
        'OctoClub\Tutorial\Components\Catalog'  => 'OctoClubCatalog',
        'OctoClub\Tutorial\Components\Category' => 'OctoClubCategory',
        'OctoClub\Tutorial\Components\Item'     => 'OctoClubItem',
        'OctoClub\Tutorial\Components\Feedback' => 'OctoClubFeedback',
    ];
}

Сохраняем, и переходим в папку с компонентами, и открываем файл Feedback.php.

Здесь мы сразу приступим к defineProperties(), и укажем возможность менять email для уведомлений.
В настройках компонента, это будет обычное текстовое поле, которое будет обязательно для уведомлений.

Пример выполнения:

public function defineProperties()
{
    return [
        'email' => [
            'title'       => 'Email',
            'description' => 'Данный email будет использоваться для отправки всех заявок с формы.',
            'default'     => 'admin@test.ru',
            'required'    => true,
        ],
    ];
}

Здесь ничего необычного, абсолютно все так, как мы делали в первой части инструкции по созданию плагина.

Теперь давайте пропишем наш хандлер onSend(), который будет принимать данные с формы и отправлять по указанному email.

Сразу под функцией defineProperties(), создаем новую: onSend():

public function onSend()
{
    
}

Данная функция будет вызываться с помощью AJAX Data Request, который мы укажем в html нашей формы.

Давайте напишем небольшую логику обработки данных, которые приходят с формы, и если что-то важное будет не заполнено, мы укажем это через стандартное всплывающее уведомление Flash.

Для этого нам нужно в наш компонент подключить классы, которые мы будем использовать.

В шапке нашего компонента добавляем:

use Mail;
use Input;
use Flash;
  • Mail – будем использовать для отправки письма.
  • Input – для получения и данных из формы.
  • Flash – для уведомлений.

Небольшое отступление: я не буду проверять все данные через валидатор, данная форма будет простой, и небольшой. Никаких специальных полей с уникальными данными, которые должен указать пользователь – не будет. Поэтому если вы хотите написать супер сложную валидацию полей, вы можете обратиться к документации, и внедрить примеры оттуда в данный компонент.

Давайте определимся, какие поля пользователь должен будет заполнить в нашей форме:

  • Имя – name
  • Email – email
  • Контактный телефон (опционально) – phone

И поля, которые пользователь не должен заполнять, они будут скрытыми (hidden):

  • Наименование предмета – item_name

Для нашего случая большего и не нужно. Если вы хотите, можете добавить любое количество полей, скрытых или нет.

Поделюсь своим небольшим опытом:
Неплохо бы в input наших полей name="" прописывать не стандартные названия, Имя - name и Email - email. А прописывать их как угодно по-другому. Это такая защита от ботов, которые краулят сайты и находят формы без каптчи, вбивают поля по ключам, которые чаще всего используются в формах.

Если вы назовете Поле Имя - alsdkals, а поле Email – xcvxqwew, будет в десятки раз меньше шансов того, что бот сможет заполнить поле со всеми необходимыми полями.

Данный способ проверен на сайтах, которые до сих пор имеют формы без каптчи, после применения данного "лайфхака", практически со всех форм перестали падать спамовые рассылки.

В данной инструкции я не буду применять данный способ, чтобы вам было легче ориентироваться, но советую по завершению написания формы, все-таки реализовать какую-нибудь "защиту". Боты безжалостны.

Вернемся к нашему хандлеру onSend().

Давайте пропишем небольшую логику на проверку всех самых важных полей, а именно: имя, и email.
нам важно получить имя и его почту человека, даже если все остальное по какой-либо причине не будет указано, мы хотя-бы сможем связаться с человеком.

Прописываем эту небольшую логику в тело функции:

if(!Input::has('name') || empty(Input::get('name'))){
    throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Вы должны указать свое имя' ]);
}

if(!Input::has('email') || empty(Input::get('email'))){
    throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Вы должны указать email для связи' ]);
}

После этого, мы формируем массив полученных данных, который мы будем позднее использовать в шаблоне нашего письма.

$data = [
    'name'       => e(Input::get('name')),
    'email'      => e(Input::get('email')),
    'item_name'  => e(Input::get('item_name')),
];

if(Input::has('phone') && !empty(Input::get('phone'))){
    $data['phone'] = e(Input::get('phone'));
}

Здесь все очень просто. Мы делаем массив $data, где ключи будут использоваться в шаблоне как переменные. Функция e() – очищает полученные данные из формы от любого исполняемого кода в escaped string.

У нас есть все данные, осталось просто добавить отправку письма, делаем:

$email = $this->property('email');
Mail::send('octoclub.tutorial::mail.feedback', $data, function($message) use ($email) {
    $message->to($email, 'Admin Person');
});

// Проверка успешно ли ушло письмо
if (count(Mail::failures()) == 0){
    Flash::success( 'Форма успешно отправлена!' );
} else {
    throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Произошла ошибка, попробуйте позже' ]);
}

Как вы заметили, я использую в функции send на первый взгляд не очень понятную строку 'octoclub.tutorial::mail.feedback'. Все очень просто, это мы указываем шаблон письма. Мы создадим данный шаблон следующим шагом.

В итоге, вся функция выглядит так:

    public function onSend()
    {
        // Проверка
        if(!Input::has('name') || empty(Input::get('name'))){
            throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Вы должны указать свое имя' ]);
        }
    
        if(!Input::has('email') || empty(Input::get('email'))){
            throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Вы должны указать email для связи' ]);
        }
    
        // Составление массива
        $data = [
            'name'       => e(Input::get('name')),
            'email'      => e(Input::get('email')),
            'item_name'  => e(Input::get('item_name')),
        ];
    
        if(Input::has('phone') && !empty(Input::get('phone'))){
            $data['phone'] = e(Input::get('phone'));
        }
    
        // Отправка уведомления
        $email = $this->property('email');
        Mail::send('octoclub.tutorial::mail.feedback', $data, function($message) use ($email) {
            $message->to($email, 'Admin Person');
        });
    
        // Проверка успешно ли ушло письмо
        if (count(Mail::failures()) == 0){
            Flash::success( 'Форма успешно отправлена!' );
        } else {
            throw new \AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Произошла ошибка, попробуйте позже' ]);
        }
    }

Разработка шаблона письма

Давайте создадим теперь наш шаблон письма. Перейдем в настройки админки, на страницу "Шаблоны почты", жмем на кнопку "новый шаблон", и заполняем по примеру:

Код: octoclub.tutorial::mail.feedback
Тема: Новая заявка со страницы предмета
Описание: Отправка уведомления администратору
HTML:

Пришла новая заявка со следующими данными:

* Имя: {{ name }}
* Email: {{ email }}
* {% if phone %}Телефон: {{ phone }}{% endif %}
* Предмет: {{ item_name }}

Все очень просто, и достаточно примитивно.
С помощью твига, вставляем переменные, если телефон заполнен, прикрепляем его тоже.

Пока не ушли из настроек, давайте переключим работу почты в режим тестирования, и сделаем так, чтобы все письма падали в журнал событий, чтобы быстро посмотреть результат. Открываем в настройках страницу "Настройки почты", и в выпадающем списке "метод" указываем "файл журнала".

Разработка верстки формы, подключение AJAX

Вся логика готова. Давайте добавим компонент на CMS страницу предмета через админку, укажем email, и перейдем к верстке формы.

Компонент добавили, ввели необходимый email.

Заметьте что в баре компонентов, под названием страницы, компонент формы идет следом после компонента предмета. Чтобы мы смогли в форме обратиться к переменной {{ item.name }}.
{% component 'OctoClubFeedBack' %} в коде страницы уже не важно где вставлять, перед или после.

Теперь перейдем к верстке, переходим в папку с компонентами, и в папку /feedback, открываем файл default.htm. Удаляем дефолтный шаблон. И пишем верстку.

Получилось примерно так:

<form>
    <div class="form-group">
        <label for="name">Имя</label>
        <input type="text" name="name" id="name" class="form-control" required>
    </div>
    <div class="form-group">
        <label for="email">Email</label>
        <input type="email" name="email" id="email" class="form-control" required>
    </div>
    <div class="form-group">
        <label for="phone">Телефон</label>
        <input type="text" name="phone" id="phone" class="form-control">
    </div>
    <input type="hidden" name="item_name" value="{{ item.name }}">
    <button type="submit" class="btn btn-primary">Отправить</button>
</form>

Обычная Bootstrap форма.

Теперь давайте добавим туда хандлеры, которые будут обрабатывать форму при отправке. Делается это очень просто, достаточно в тег <form> добавить пару атрибутов.

<form 
    data-request="{{ __SELF__ }}::onSend"
    data-request-success="$(this).find('input[type=text], input[type=email]').val('');"
    data-request-flash>
  • data-request="{{ __SELF__ }}::onSend"– указываем хандлер в нашем компоненте, который будет обрабатывать форму.
  • data-request-success="$(this).find('input[type=text], input[type=email]').val('');" – очистка формы после успешной отправки формы.
  • data-request-flash – добавляет возможность при использовании данной формы отображать уведомления.

Помните, чтобы у вас работала форма и уведомления, вам необходимо включить JS фреймворк OctoberCMS, в шаблон вашей страниы.

{% framework extras %}

Если у вас уже стоит обычный вызов фреймворка {% framework %} , просто замените его примером выше.

Теперь давайте перейдем на страницу предмета, и проверим отправку формы 3 шагами!

1) Открываем страницу предмета, заполняем форму и отправляем

2) После нажатия на кнопку "Отправить", показывается уведомление и форма сбрасывается.

3) Перейдем в журнал событий в настройках админки, и посмотрим на наше письмо.

Все пришло правильно, и так как мы это захотели!

Вот и все. Вы можете свободно ни в чем себе не отказывая менять или переписывать данный способ обработки формы. Я лишь показал примитивный пример, который поможет новичкам в освоении OctoberCMS.

А у меня флаш вообще отказывается работать:

  • reazzon ответили на это сообщение.

    Electrica Ошибку выдает или просто не показывается?

    • Electrica ответили на это сообщение.

      reazzon он просто мимо проходит и идет дальше

      • reazzon ответили на это сообщение.
        • Изменено

        Electrica Проверь {% framework extras %} подключен в шаблоне у тебя или нет. С обычным {% framework %} не будет работать

        • Electrica ответили на это сообщение.

          Electrica Так-же проверь атрибут data-request-flash в теге формы

          reazzon вот тут чет вроде, правда 406 ошибку выдает

            Electrica Это уже локальные какие-то проблемы твоего окружения. Я пока писал инструкцию, параллельно выполнял все действия в живую на своей локальной машине, без каких-либо модификаций.

            • Electrica ответили на это сообщение.

              reazzon ну главное, что нужно было изменить framework extras

              • reazzon ответили на это сообщение.

                Electrica В инструкции это указано 😉

                Небольшие 2 копейки))

                1. Может ошибаюсь, но Flash::error только выводит красное flash сообщение. data-request-success при этом все рано отработает и сотрет данные формы. Чтобы этого не было я обычно вместо Flash::error использую
                  throw new AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Текст flash сообщения' ]);
                  тогда точно отрабатывает data-request-error

                2. Может случится так, что письмо по каким то причинам не уйдет (не верно настроен сервер и тд) такие случаи тоже надо обработать


                 if ( count( Mail::failures() ) == 0 ) {
                    Flash::success( 'Форма успешно отправлена!' );
                } else {
                    throw new AjaxException([ 'X_OCTOBER_ERROR_MESSAGE' => 'Произошла ошибка, попробуйте позже' ]);
                }
                • reazzon ответили на это сообщение.
                • reazzon оценил это.

                  yurasovm Спасибо за совет. Сейчас исправлю как ты написал. Я думал сделать так-же но что-то остановило меня, скорее всего не хотел нагромождать)

                  Electrica валидатор всегда 406 возвращает)

                  yurasovm Изменения внесены) Спасибо еще раз)

                  Исправил все ошибки, сейчас должно все работать как задумано.

                  22 дня спустя
                  4 месяца спустя

                  А можно спросить по аяксу, вот есть подключение use Input;, соответственно получаем значение таким образом: e(Input::get('id')). Это для полей input. А как для <textarea></textarea> делать? Так же через use Input; или есть что то специальное?

                  • reazzon ответили на это сообщение.
                    • Изменено

                    Koresh Это для полей input

                    В корне не верно.

                    Класс Input представляет из себя Helper метод, с помощью которого, можно "ловить" данные, которые поступают любым видом на ваш обработчик.

                    Немного разобрав что происходит в AJAX компоненте, все ваши поля в <form> по нажатию на кнопку Submit, отправляются как обычный массив данных. Хандлер onSend, по сути, делает обычный AJAX запрос с данными из формы в виде POST запроса.

                    Класс Input можно использовать не только как Input::get(), там есть много других полезных функций, о них в документации.

                    Поэтому, вы можете с помощью Input класса ловить любые данные. Будь это POST, PUT или GET запрос.

                    В вашем случае, в <textarea> необходимо иметь тег name как у любого поля внутри тега <form> и внутри хандлера ловить это поле по его тегу name

                      reazzon Класс Input представляет из себя Helper метод, с помощью которого, можно "ловить" данные, которые поступают любым видом на ваш обработчик.
                      Хандлер onSend, по сути, делает обычный AJAX запрос с данными из формы в виде POST запроса.

                      Значит если у меня нет формы, а данные я отправляю из js

                      $.request('onAlloDzindzin', {
                          data: {
                              page_id: this.page_id,
                              content: this.textarea.value
                          },
                          success: function(data) {
                              // ...
                          }
                      });

                      то в обработчике так же нужно подключить use Input; и так же получаем данные:

                      function onAlloDzindzin() {
                          $page_id = e(Input::get('page_id'));
                          $content = e(Input::get('content'));
                      }

                      Правильно?

                      Или в таком случае оборачивать в e(...) не нужно? Я смотрю по ссылке которую вы дали, там такого примера с e(...) вообще нет.

                      За ссылку спасибо, я пробовал сам эту доку найти, но так и не нашёл.

                      • reazzon ответили на это сообщение.
                        • Изменено

                        Koresh Правильно?

                        Да.

                        Koresh Или в таком случае оборачивать в e(...) не нужно? Я смотрю по ссылке которую вы дали, там такого примера с e(...) вообще нет.

                        Оборачивание в функцию e(...), нужно чтобы исключить XSS атаки, которые могут пройти делая инъекции сигнатуры кода в строку формы.

                        Просто дополнительная защита не помешает)