Систематизация, переиспользование и редактирование форм.

Приветствую! Я открыл для себя ещё один вариант использования компонента MIGX и хочу поделиться им с сообществом. Скорее всего, кто-то до меня так делал, но либо не делился, либо я не нашёл.
Для чего это нужно? На сайтах как правило есть несколько форм: обратная связь, подписка, отзывы и.т.д. Иногда формы эти содержат идентичный набор полей, но выглядят по-разному и располагаются на разных страницах. А ещё они могут отправляться на разные почты или наоборот все на одну. В общем, так или иначе это приводит к полному или частичному дублированию вызовов и к тому, что в случае необходимости внесения изменений, нужно править несколько файлов. Вот я и разработал несложную систему для упрощения управления формами.

Для того, чтобы всё написанное дальше работало нужно установить MIGX, AjaxForm, FormIt, pdoTools. Включить Fenom на страницах.

1. Создание конфигурации MIGX
Из верхнего меню переходим на страницу компонента MIGX и создаем новый элемент с названием form_list. На вкладке Formtabs добавляем элемент, в caption пишем Настройки, или как угодно иначе, и добавляем следующие поля(имена полей менять не рекомендую, часть из них используется в плагинах):
fid — ID формы — listbox — список возможных значений будет заполняться автоматически(обязательно);
formName — Название формы — text — любое понятное название, используется при отображении формы в списке и при сохранении формы в БД хуком FormItSaveForm(обязательно);
Сохраняем. Закрываем.
Добавляем ещё один элемент, в caption пишем Параметры и добавляем поля:
preset — Пресет — listbox — список возможных значений будет заполняться автоматически(обязательно);
params — Список параметров — migx — в это поле можно будет записывать параметры вызова AjaxForm, которые переопределят одноименные параметры пресета. Например, если у вас две идентичные формы, но после отправки одной нужно выводить уведомление «Спасибо! Мы скоро перезвоним!», а после отправки другой «Ваша заявка принята!», то в поле preset выбираем одинаковые базовые настройки, а в поле params переопределяем значение successMessage. Это поле типа migx, значит ему в config надо указать имя другого поля или написать конфигурацию вручную, первый вариант проще. Итак, у меня config содержит значение list_double с полями title и content — это важно, т.к. названия этих полей используются в функции reformatParams плагина parseMigxFormList(обязательно).
Сохраняем. Закрываем.
Переходим на кладку Columns и дабавляем всего 1 элемент Название формы — formName.
!!! ВАЖНО!!! При добавлении в поле params правил валидации(validate) значение, т.е. строка со списком правил, должно заканчиваться ЗАПЯТОЙ, т.к. в плагине parseMigxFormList к этому параметру добавляется обязательное значение путём конкатенации строк.
2. Создание TV и привязка к шаблону.
Вторым шагом надо создать TV, я назвал его form_list, с типом migx и в качестве конфигурации определить ту, что создана на первом этапе, в нашем случае form_list. Зачем нужно привязать это поле к какому-нибудь шаблону, например, Контакты.
3. Создание чанков форм.
Нужно создать отдельную папку с чанками форм. Название файлов этой папке будут значениями ID форм. У меня эта папка находится в core/elements/chunks/forms/.
4. Создание пресетов.
Теперь можно создать пресеты. У меня они расположены в одном файле core/elements/presets/ajaxform.json и выглядит это так
{
  "callback_form": {
    "form": "",
    "snippet" : "FormIt",
    "emailFrom" : "noreply@domain.ru",
    "emailSubject" : "Заявка с сайта",
    "validationErrorMessage" : "Исправьте, пожалуйста, ошибки!",
    "secret.vTextContains" : "Кажется Вы робот. Если это не так, обновите страницу.",
    "successMessage" : "Форма успешно отправлена! Менеджер свяжется с Вами в течение 5 минут.",
    "hooks" : "FormItSaveForm,email"
  }
}
5. Создание плагина для заполнения пресетов и ID.
Чтобы руками не писать возможные значения ID и названия пресетов, я написал плагина на событие OnDocFormPrerender и назвал его updateMigxFormIds
<?php
if($resource){
    if($resource->getTVValue('form_list')){
        $formsPath = MODX_BASE_PATH . 'core/elements/chunks/forms/'; // заменить при необходимости
        $presetsFile = MODX_BASE_PATH . 'core/elements/presets/ajaxform.json'; // заменить при необходимости
        if(file_exists($presetsFile)){
            $presets = file_get_contents($presetsFile);
            $presets = json_decode($presets,1);
            $preset_keys = array_keys($presets);
        }
        $fileNames = scandir($formsPath); // собираем все имена файлов в массив
        unset($fileNames[0], $fileNames[1]); // удаляем ненужные нам элементы массива, которые возвращает функция scandir()
        $inputOptionValues = array();
        if (!empty($fileNames)) {
            $modx->addPackage('migx', $modx->getOption('core_path') . 'components/migx/model/'); // подключаем модель объекта MIGX
            $list_form = $modx->getObject('migxConfig', array('name' => 'form_list')); // получаем объект
            $list_form_data = $list_form->toArray(); // преобразуем в массив
            $formtabs = json_decode($list_form_data['formtabs'], 1);
            foreach ($fileNames as $name) {
                $inputOptionValues[] = str_replace(['.tpl','.html'], '', $name);
            }

            $formtabs[0]['fields'][0]['inputOptionValues'] =  implode('||', array_unique($inputOptionValues));
            if(!empty($preset_keys)){
                $formtabs[1]['fields'][1]['inputOptionValues'] =  implode('||', array_merge(array('Не выбран'),$preset_keys));
            }
            $list_form_data['formtabs'] = json_encode($formtabs);
            $list_form->fromArray($list_form_data);
            $list_form->save();
        }
    }
}
6. Создание плагина для генерации вызовов.
Теперь, когда у нас есть все необходимые данные, нам нужен плагин, который сгенерирует для нас вызов AjaxForm и сохранит его в файл для последующего подключения в шаблоне или чанке через include. Плагин я назвал parseMigxFormList:
<?php
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

function reformatParams($array, $key, $value){
    $result = array();
    if(is_array($array) && !empty($array)){
        foreach($array as $item){
            $result[$item[$key]] = $item[$value];
        }
    }
    return $result;
}

function getSnippetCall($params){
    $params_str = '';
    foreach($params as $key => $value){
        if(strpos($value, '$') !== false){
            $params_str .= "'".$key."'" . " => " . $value.",".PHP_EOL;
        }else{
            $params_str .= "'".$key."'" . " => " . "'".$value."',".PHP_EOL;
        }
    }
    return "{'!AjaxForm' | snippet:[$params_str]}";
}

if($form_list = $resource->getTVValue('form_list')){
    $form_list = json_decode($form_list, 1);
    $callsPath = MODX_CORE_PATH . 'elements/calls/'; // сюда сохраняем вызовы
    $default_params = array();
    $http_host = $modx->getOption('http_host');
    if($сp_id = $modx->getOption('contacts_page_id')){
        $contacts = $modx->runSnippet('get_contacts', ['input' => $сp_id, 'options' => 'contacts']);
        $emails = array();
        if(!empty($contacts['emails'])){
            foreach($contacts['emails'] as $item){
                $emails[] = $item['value'];
            }
            if(!empty($emails)){
                $emailTo = implode(',', $emails);
            }
        }
    }
    if(!$emailTo){
        $emailTo = $modx->getOption('email') ?: $modx->getOption('ms2_email_manager');
    }
    if(!$emailTo){
        $emailTo = 'info@'. $http_host;
    }
    $default_params['emailFrom'] = 'noreply@'. $http_host;
    $default_params['emailTo'] = $emailTo;
    if(file_exists(MODX_CORE_PATH . 'elements/presets/ajaxform.json')){
        $file_params = file_get_contents(MODX_CORE_PATH . 'elements/presets/ajaxform.json');
        $file_params = json_decode($file_params,1);
    }  
    foreach ($form_list as $form){
        $file_name = $form['fid'].'.tpl';
        $preset = $file_params[$form['preset']] ?: array();
        $default_params['secret'] =  generateRandomString(); // если не нужна такая защита от спама удалить
        $default_params['form'] =  '@FILE elements/chunks/forms/'.$file_name;
        $params = reformatParams(json_decode($form['params'], 1), 'title', 'content');
        $params['validate'] .= $default_params['validate'];
        $params['validate'] .= 'secret:contains=^'.$default_params['secret'].'^'; // если не нужна такая защита от спама удалить
        unset($form['MIGX_id']);
        unset($form['params']);
        unset($form['add_params']);

        $all_params = array_merge($preset, $default_params, $form, $params);
        $call = getSnippetCall($all_params);
        if(!is_dir($callsPath)){
            mkdir($callsPath);
        }
        file_put_contents($callsPath.$file_name, $call);       
    }
}
Это плагин для каждой формы генерирует случайны ключ, который передаётся в чанк формы и вот таким нехитрым кодом на JS вставляется в скрытое поле secret, которое нужно добавить в чанк формы вот в таком виде
<input type="hidden" name="secret" value="" data-secret="{$secret}">
Код для вставки защитного ключа выглядит так:
function insrtAntiSpamKey() {
    const antiSpamKeyInput = document.querySelectorAll('input[name="secret"]');
    antiSpamKeyInput.forEach(el => {
        el.value = el.dataset.secret;
    });   
    document.removeEventListener('mousemove', insrtAntiSpamKey);
}
document.addEventListener('mousemove', insrtAntiSpamKey);
Всё это нужно для защиты от спама. Рецепт предоставлен @alexij. Кому не надо, можно удалить, строки помечены комментарием. Так же стоит обратить внимание на то, как формируется адрес получателя. Сначала скрипт смотрит есть ли настройка в которой указан ID страницы Контакты, если есть, то проверяет наличие заполненного поля contacts и из него получает список почт, если в результате ничего не найдено проверяется настройка email и ms2_email_namager, если и там пусто — генерируется заглушка. Если же в поле params передан параметр emalTo, то всё что было найдено ранее будет перезаписано.

Таким образом, задав пресеты, можно в несколько кликов генерировать вызовы форм, при этом сами пресеты это 1 файл, который можно таскать из проекта в проект. К тому же у админа есть возможность поменять параметры не залезая в код.
Артур Шевченко
22 августа 2022, 16:14
modx.pro
2
717
+3
Поблагодарить автора Отправить деньги

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

Роман
08 сентября 2022, 16:35
0
Хотелось бы увидеть видео, как это работает.
    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
    1