diff --git a/README.md b/README.md
index b6bd24e..638ec70 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,31 @@
-# forms
+### forms
-Модуль Формы v3.31
\ No newline at end of file
+## Модуль Формы v3.31
+
+### адаптирован для AVE.CMS ALT
+
+### Модуль предназначен для создания веб-форм (например, обратной связи или простейшего оформления заказа), которые могут состоять из любого набора полей.
+
+### Для вывода в публичной части сайта используйте тег [mod_forms:XXX], где XXX - это id или алиас формы.
+
+### Changelog:
+
+18.02.2026 - Версия v3.31 - дальнейшее развитие модуля только для работы с AVE.CMS ALT
+
+08.11.2025 - Версия v2.1.0 - модуль обновлен для работы с шаблонизатором Smarty 5
+
+25.09.2025 - Версия v2.0.0 - модуль обновлен для работы в среде PHP-8.4.x и MySQL-8.4.x
+Добавлен функционал массовых действий с историей: удаление, изменение статусов
+
+01.09.2019 - Версия v1.26.0 - Модуль переименован в модуль Формы. Адаптация для версии ave-cms версии 3.25 и выше, удалено лишнее.
+
+27.04.2017 - Версия 1.2.4 - добавлена возможность в поле "Атрибуты тега поля" работать атрибутом value, в …
+…который можно подставлять теги (например [tag:docid]) и php код
+
+16.04.2017 - Версия 1.2.3 - исправление ошибок, в демоверсии включен пример обновление капчи по клику
+
+19.09.2016 - Версия 1.2.2 - изменения в административной части модуля
+
+18.09.2016 - Версия 1.2.1 - добавлена возможность удаления сообщений
+
+11.09.2016 - инициализация модуля для AVE.CMS v3.1.5
\ No newline at end of file
diff --git a/class/forms.php b/class/forms.php
new file mode 100644
index 0000000..5c2aaf5
--- /dev/null
+++ b/class/forms.php
@@ -0,0 +1,1787 @@
+ array(
+ 'new' => true,
+ 'title' => 'email',
+ 'type' => 'input',
+ 'main' => 1,
+ 'setting' => 'FILTER_VALIDATE_EMAIL',
+ 'required' => 1
+ ),
+ 'subject' => array(
+ 'new' => true,
+ 'title' => 'subject',
+ 'type' => 'input',
+ 'main' => 1
+ ),
+ 'receivers' => array(
+ 'new' => true,
+ 'title' => 'receivers',
+ 'type' => 'select',
+ 'main' => 1,
+ 'setting' => array()
+ ),
+ 'copy' => array(
+ 'new' => true,
+ 'title' => 'copy',
+ 'type' => 'checkbox',
+ 'main' => 1
+ ),
+ 'captcha' => array(
+ 'new' => true,
+ 'title' => 'captcha',
+ 'type' => 'input',
+ 'main' => 1,
+ 'required' => 1
+ )
+ );
+ // переменная для хранения формы
+ public $form = array();
+
+ /**
+ * Внутренние методы класса
+ */
+
+ /**
+ * Конструктор
+ */
+ function __construct ()
+ {
+ $this->fields_main = array_keys($this->fields_main_data);
+ $this->fields_main_in_string = "'" . implode("','",$this->fields_main) . "'";
+ }
+
+
+ /**
+ * Возвращаем JSON
+ *
+ * @param $data
+ * @param bool $exit
+ */
+ function _json($data, $exit = false)
+ {
+ header("Content-Type: application/json;charset=utf-8");
+
+ $json = json_encode($data);
+
+ if ($json === false)
+ {
+ $json = json_encode(array("jsonError", json_last_error_msg()));
+
+ if ($json === false)
+ {
+ $json = '{"jsonError": "unknown"}';
+ }
+
+ http_response_code(500);
+ }
+
+ echo $json;
+
+ if ($exit)
+ exit;
+ }
+
+
+ /**
+ * Метод забирает форму из бд по алиасу/id
+ *
+ * @param $alias_id
+ * @param bool $_fields
+ *
+ * @return array
+ */
+ function _form ($alias_id, $_fields = true)
+ {
+ global $AVE_DB, $AVE_Template;
+
+ // иначе забираем из бд форму
+ $form = array();
+
+ // основные параметры
+ $form = $AVE_DB->Query("
+ SELECT * FROM " . PREFIX . "_module_forms_forms
+ WHERE
+ " . (is_numeric($alias_id) ? 'id' : 'alias') . " = '" . $alias_id . "'
+ ")->FetchAssocArray();
+
+ // если форма не обнаружена, выходим
+ if (empty($form))
+ return array();
+
+ $form['alias_id'] = $alias_id;
+
+ // получатели
+ // ИСПРАВЛЕНИЕ: Добавляем @ для подавления Warning при десериализации, т.к. пользователь сообщил о проблеме
+ $form['mail_set'] = @unserialize($form['mail_set']);
+
+ if ($_fields === true)
+ {
+ // поля
+ $sql = $AVE_DB->Query("
+ SELECT * FROM " . PREFIX . "_module_forms_fields
+ WHERE form_id = '" . $form['id'] . "'
+ ORDER BY id ASC
+ ");
+
+ $form['fields'] = array();
+
+ while ($field = $sql->FetchAssocArray())
+ {
+ // раскрываем массив настроек для селектов
+ if (in_array($field['type'],array('select','multiselect','doc','multidoc')))
+ {
+ // @fix для v1.0 beta <= 2: поддержка одной рубрики, не массива
+ if (in_array($field['type'],array('doc','multidoc')) && !empty($field['setting']) && is_numeric($field['setting']))
+ $field['setting'] = array(0 => $field['setting']);
+ else
+ // ИСПРАВЛЕНИЕ: Добавляем @ для подавления Warning при десериализации
+ $field['setting'] = @unserialize($field['setting']) !== false ? @unserialize($field['setting']) : array();
+ }
+ // если тип поля поменялся, ставим пустую строку
+ // ИСПРАВЛЕНИЕ: Добавляем дополнительную проверку на то, что строка может быть сериализована (начинается с 'a', 'O', 's', 'i', 'd', 'b')
+ elseif (is_string($field['setting']) && strlen($field['setting']) > 2 && in_array($field['setting'][0], ['a', 'O', 's', 'i', 'd', 'b']) && @unserialize($field['setting']) !== false) $field['setting'] = '';
+
+ // раскрываем массив опций по умолчанию для мультиселекта
+ if ($field['type'] == 'multiselect')
+ {
+ // ИСПРАВЛЕНИЕ: Добавляем @ для подавления Warning при десериализации
+ $field['defaultval'] = @unserialize($field['defaultval']) !== false ? @unserialize($field['defaultval']) : array();
+ }
+ // если тип поля поменялся, ставим пустую строку
+ // ИСПРАВЛЕНИЕ: Добавляем дополнительную проверку на то, что строка может быть сериализована
+ elseif (is_string($field['defaultval']) && strlen($field['defaultval']) > 2 && in_array($field['defaultval'][0], ['a', 'O', 's', 'i', 'd', 'b']) && @unserialize($field['defaultval']) !== false) $field['defaultval'] = '';
+
+ // главные поля
+ if (in_array($field['title'],$this->fields_main) && $field['main'])
+ {
+ $form['fields_main'][$field['title']] = $field['id'];
+ $field['title_lang'] = $AVE_Template->get_config_vars('mfld_' . $field['title']);
+ }
+
+ $form['fields'][$field['id']] = $field;
+ }
+ }
+
+ // убираем слеши
+ $form = $this->_stripslashes($form);
+
+ // сохраняем форму в переменную класса
+ $this->form = $form;
+
+ return $form;
+ }
+
+
+/**
+ * Метод убирает слэши во всей переменной
+ *
+ * @param mixed $var
+ *
+ * @return array|string
+ */
+ function _stripslashes($var)
+ {
+ if (is_array($var))
+ return array_map(array($this, '_stripslashes'), $var);
+ else
+ // ИСПРАВЛЕНИЕ: Гарантируем, что $var является строкой, используя ?? ''
+ return stripslashes($var ?? '');
+ }
+
+
+ /**
+ * Метод тримует всю переменную
+ *
+ * @param $var
+ *
+ * @return array|string
+ */
+ function _trim($var)
+ {
+ if (! is_array($var))
+ return trim($var);
+ else
+ return array_map(array($this, '_trim'), $var);
+ }
+
+
+/**
+ * Метод чистит переменную от пустых значений и массивов
+ *
+ * @param $var
+ *
+ * @return array|null|string
+ */
+function _cleanvar($var)
+{
+ if (!is_array($var)) {
+ return trim($var) > ''
+ ? trim($var)
+ : null;
+ }
+
+ $narr = array();
+
+ // Заменяем while (list($key, $val) = each($var)) на foreach
+ foreach ($var as $key => $val) {
+ if (is_array($val)) {
+ $val = $this->_cleanvar($val);
+ if (count($val) > 0) {
+ $narr[$key] = $val;
+ }
+ } else {
+ if (trim($val) > '') {
+ $narr[$key] = $val;
+ }
+ }
+ }
+
+ unset($var);
+
+ return $narr;
+}
+
+ /**
+ * Валидация Email-а
+ *
+ * @param string $email
+ *
+ * @return bool
+ */
+ function _email_validate ($email = '')
+ {
+ return (filter_var($email, FILTER_VALIDATE_EMAIL) === false ? false : true);
+ }
+
+
+ /**
+ * Проверка алиаса тега на валидность и уникальность
+ *
+ * @param string $alias
+ * @param int $fid
+ *
+ * @return bool|string
+ */
+ function _alias_validate ($alias = '', $fid = 0)
+ {
+ global $AVE_DB;
+
+ // соответствие требованиям
+ if (
+ empty ($alias) ||
+ preg_match('/^[A-Za-z0-9-_]{1,20}$/i', $alias) !== 1 ||
+ is_numeric($alias)
+ ) return 'syn';
+
+ // уникальность
+ return !(bool)$AVE_DB->Query("
+ SELECT 1 FROM " . PREFIX . "_module_forms_forms
+ WHERE
+ alias = '" . $alias . "' AND
+ id != '" . $fid . "'
+ ")->GetCell();
+ }
+
+
+ /**
+ * Получение списка рубрик
+ */
+ function _rubrics ()
+ {
+ global $AVE_DB;
+
+ $rubs = array();
+
+ $sql = $AVE_DB->Query("
+ SELECT Id, rubric_title FROM " . PREFIX . "_rubrics
+ ");
+
+ while ($rub = $sql->FetchAssocArray())
+ $rubs[$rub['Id']] = $rub['rubric_title'];
+
+ return $rubs;
+ }
+
+
+ /**
+ * Получение списка документов
+ *
+ * @param array $rubs_id
+ *
+ * @return array
+ */
+ function _docs ($rubs_id = array())
+ {
+ global $AVE_DB;
+
+ if (empty($rubs_id))
+ return array();
+
+ // @fix для v1.0 beta <= 2: поддержка одной рубрики, не массива
+ if (! is_array($rubs_id))
+ $rubs_id = array(0 => $rubs_id);
+
+ $docs = array();
+
+ $sql = $AVE_DB->Query("
+ SELECT Id, document_title FROM " . PREFIX . "_documents
+ WHERE rubric_id IN (" . implode(',',$rubs_id) . ")
+ ");
+
+ while ($doc = $sql->FetchAssocArray())
+ $docs[$doc['Id']] = $doc['document_title'];
+
+ return $docs;
+ }
+
+
+/**
+ * Парсинг главных тегов
+ */
+ function _parse_tags ($str)
+ {
+ global $AVE_Core, $AVE_DB;
+
+ if (empty($_SESSION['user_login']))
+ $_SESSION['user_login'] = (UGROUP != 2)
+ ? $AVE_DB->Query("SELECT user_name FROM " . PREFIX . "_users WHERE Id = '" . UID . "'")->GetCell()
+ : '';
+
+ $str = preg_replace_callback('/\[tag:(css|js):([^ :\/]+):?(\S+)*\]/', array($AVE_Core, '_parse_combine'), $str);
+
+ $str = parse_hide($str);
+
+ // Получаем запись пользователя ОДИН раз
+ // Если get_user_rec_by_id(UID) вернет null, мы можем безопасно использовать ее дальше.
+ $user_rec = get_user_rec_by_id(UID);
+ return str_replace(array(
+ '[tag:docid]',
+ '[tag:formtitle]',
+ // @fix для v1.0 beta <= 2: поддержка тега formaction
+ '[tag:formaction]',
+ '[tag:document]',
+ '[tag:formalias]',
+ '[tag:path]',
+ '[tag:mediapath]',
+ '[tag:captcha]',
+ '[tag:uname]',
+ '[tag:ufname]',
+ '[tag:ulname]',
+ '[tag:ulogin]',
+ '[tag:uemail]',
+ '[tag:sitename]',
+ '[tag:sitehost]',
+ '[tag:submitted_page]',
+ ),array(
+ $AVE_Core->curentdoc->Id,
+ $this->form['title'],
+ $_SERVER['REQUEST_URI'],
+ $_SERVER['REQUEST_URI'],
+ $this->form['alias'] ? $this->form['alias'] : $this->form['id'],
+ ABS_PATH,
+ ABS_PATH . 'templates/' . ((defined('THEME_FOLDER') === false) ? DEFAULT_THEME_FOLDER : THEME_FOLDER) . '/',
+ 'inc/captcha.php',
+ $_SESSION['user_name'],
+
+ // ИСПРАВЛЕНИЕ: Безопасное получение firstname и lastname
+ $user_rec->firstname ?? '',
+ $user_rec->lastname ?? '',
+
+ $_SESSION['user_login'] ?? '',
+ $_SESSION['user_email'] ?? '',
+ htmlspecialchars(get_settings('site_name'), ENT_QUOTES),
+ $_SERVER['HTTP_HOST'],
+ getSiteUrl() . $_SERVER['REQUEST_URI'],
+ ), $str);
+ }
+
+ /**
+ * Внутренний PHP-парсер
+ */
+ function _eval2var($code)
+ {
+ global $AVE_DB, $AVE_Core, $AVE_Template;
+
+ ob_start();
+
+ eval($code);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Приведение тегов главных полей к стандартной форме
+ */
+ function _parse_tag_fld_main ($tpl='', $save_is=false)
+ {
+ foreach ($this->fields_main as $field_title)
+ {
+ $count = 0;
+
+ $tpl = str_replace(array(
+ '[tag:fld:' . $field_title . ']',
+ '[tag:if_fld:' . $field_title,
+ '[tag:elseif_fld:' . $field_title,
+ ), array(
+ '[tag:fld:' . $this->form['fields_main'][$field_title] . ']',
+ '[tag:if_fld:' . $this->form['fields_main'][$field_title],
+ '[tag:elseif_fld:' . $this->form['fields_main'][$field_title],
+ ),$tpl,$count);
+
+ if ($save_is)
+ $this->form['is_' . $field_title] = $count > 0 ? true : false;
+ }
+
+ return $tpl;
+ }
+
+ /**
+ * Замена тега условия
+ */
+ function _parse_tag_if (&$tpl,$tag='',$open = true)
+ {
+ if ($open)
+ $tpl = str_replace(array('[tag:' . $tag . ']','[/tag:' . $tag . ']'),'',$tpl);
+ else
+ $tpl = preg_replace('/\[tag:' . $tag . '](.*?)\[\/tag:' . $tag . ']/si','',$tpl);
+ }
+
+ /**
+ * Замена тега названия поля
+ */
+ function _parse_tag_title ($matches)
+ {
+ $field_id = $matches[1];
+
+ return !$this->form['fields'][$field_id]['main']
+ ? $this->form['fields'][$field_id]['title']
+ : $this->form['fields'][$field_id]['title_lang'];
+ }
+
+ /**
+ * Замена тега поля на значение $_POST
+ */
+ function _parse_tag_fld_post ($matches)
+ {
+ return $_POST['form-' . $this->form['alias_id']][$matches[1]] ?? '';
+ }
+
+ /**
+ * Замена тега поля при выводе формы
+ */
+ function _parse_tag_fld_form ($matches)
+ {
+ $field_id = (int)$matches[1];
+
+ if (!isset($this->form['fields'][$field_id])) {
+ return $matches[0];
+ }
+
+ // забираем массив поля
+ $field = $this->form['fields'][$field_id];
+
+ // если поля нет, возвращаем тег обратно
+ if (empty($field))
+ return $matches[0];
+
+ // если поле выключено, возвращаем пустую строку
+ if (empty($field['active']))
+ return '';
+
+ $alias_id = $this->form['alias_id'];
+
+ $fld_val = $this->form['is_submited']
+ ? $this->_stripslashes($_POST['form-' . $alias_id][$field_id] ?? null)
+ : (in_array($field['type'],array('input','textarea'))
+ ? $this->_eval2var('?>' . $field['defaultval'] . '')
+ : $field['defaultval']);
+
+ // Убедитесь, что $fld_val является массивом
+ if (!is_array($fld_val)) {
+ $fld_val = [$fld_val]; // Преобразуем в массив
+ }
+
+ $attributes = trim($field['attributes']);
+
+ $this->form['fields'][$field_id]['is_used'] = true;
+
+ $input = '';
+ $return = '';
+
+ switch ($field['type'])
+ {
+ case 'input':
+ $input = '';
+ break;
+
+ case 'textarea':
+ $input = '';
+ break;
+
+ case 'select':
+ $input = '';
+ break;
+
+ case 'multiselect':
+ $input = '';
+ break;
+
+ case 'checkbox':
+ $input = '
+
+ ';
+ break;
+
+ case 'file':
+ $input = '';
+ break;
+
+ case 'doc':
+ $input = '';
+ break;
+
+ case 'multidoc':
+ $input = '';
+ break;
+ }
+
+ // Вставляем поле в шаблон поля
+ $return = trim($field['tpl']) > ''
+ ? str_replace('[tag:fld]', $input, $field['tpl'])
+ : $input;
+
+ // Парсим теги названия и id
+ $return = str_replace(array(
+ '[tag:id]',
+ '[tag:title]',
+ ), array(
+ $field['id'],
+ '[tag:title:' . $field_id . ']',
+ ), $return);
+
+ // если попытка отправить форму, обрабатываем валидацию и пустоту
+ if ($this->form['is_submited'])
+ {
+ // валидация (только для капчи, input, textarea и file)
+ if (
+ ($field['title'] == 'captcha' && $field['main'] && $this->form['is_captcha'] === true) ||
+ (in_array($field['type'],array('input','textarea','file')) && !empty($field['setting']))
+ )
+ {
+ $valid = false;
+ // если капча
+ if ($field['title'] == 'captcha') $valid = (empty($_SESSION['captcha_keystring']) || empty($fld_val[0]) || $_SESSION['captcha_keystring'] != $fld_val[0]) ? false : true;
+
+ // если файл
+ elseif ($field['type'] == 'file') {
+ $file_size = (isset($_FILES['form-' . $alias_id]['size'][$field_id])) ? $_FILES['form-' . $alias_id]['size'][$field_id] : 0;
+ $valid = ($file_size / 1024 / 1024) <= $field['setting'];
+ }
+
+ // Если передали регулярку
+ elseif (isset($field['setting'][0]) && $field['setting'][0] == '/') {
+ $valid = false; // Изначально считаем, что валидности нет
+
+ // Если $fld_val - массив, проверяем каждое значение
+ if (is_array($fld_val)) {
+ foreach ($fld_val as $value) {
+ if (preg_match($field['setting'], $value) === 1) {
+ $valid = true; // Если хотя бы одно значение валидно
+ break; // Выходим из цикла
+ }
+ }
+ } else {
+ // Если это одно значение, просто проверяем его
+ $valid = preg_match($field['setting'],$fld_val) === 1 ? true : false;
+ }
+ }
+ // если константу
+ elseif (isset($field['setting']) && is_string($field['setting']) && defined($field['setting']))
+ {
+ if (is_array($fld_val) && isset($fld_val[0])) {
+ $fld_val = $fld_val[0]; // Берем первое значение из массива
+ }
+ $valid = filter_var($fld_val, constant($field['setting'])) !== false ? true : false;
+ }
+ // иначе, ничего не делаем
+ else return 'Неверные параметры валидации!';
+ // парсим теги валидности
+ $this->_parse_tag_if($return,'if_valid',$valid);
+ $this->_parse_tag_if($return,'if_invalid',!$valid);
+ // записываем результаты
+ $this->form['ajax']['fields'][$field_id]['validate'] = true;
+ $this->form['ajax']['fields'][$field_id]['pattern'] = $field['setting'];
+ $this->form['ajax']['fields'][$field_id]['is_valid'] = $valid;
+ if (!$valid)
+ {
+ $this->form['is_valid'] = false;
+ $this->form['ajax']['form']['is_valid'] = false;
+ }
+ }
+ else
+ {
+ $this->form['ajax']['fields'][$field_id]['validate'] = false;
+ $this->form['ajax']['fields'][$field_id]['is_valid'] = null;
+ }
+
+ // пустота (для любых обязательных полей)
+ if (! empty($field['required']) && $field['required'])
+ {
+ if ($field['type'] == 'file') {
+ // Безопасный доступ к $_FILES
+ $is_uploaded = isset($_FILES['form-' . $alias_id]['tmp_name'][$field_id]) && !empty($_FILES['form-' . $alias_id]['tmp_name'][$field_id]);
+ $has_error = isset($_FILES['form-' . $alias_id]['error'][$field_id]) && !empty($_FILES['form-' . $alias_id]['error'][$field_id]);
+
+ $empty = (!$is_uploaded || $has_error);
+ }
+ else
+ {
+ $clean_fld_val = $this->_cleanvar($fld_val);
+ $empty = empty($clean_fld_val);
+ }
+ // парсим теги
+ $this->_parse_tag_if($return,'if_empty',$empty);
+ $this->_parse_tag_if($return,'if_notempty',!$empty);
+ // записываем результаты
+ $this->form['ajax']['fields'][$field_id]['required'] = true;
+ $this->form['ajax']['fields'][$field_id]['is_empty'] = $empty;
+ if ($empty)
+ {
+ $this->form['is_valid'] = false;
+ $this->form['ajax']['form']['is_valid'] = false;
+ }
+ }
+ else
+ {
+ $this->form['ajax']['fields'][$field_id]['required'] = false;
+ $this->form['ajax']['fields'][$field_id]['is_empty'] = null;
+ }
+ }
+ // удаляем оставшиеся теги
+ $this->_parse_tag_if($return,'if_valid',false);
+ $this->_parse_tag_if($return,'if_invalid',false);
+ $this->_parse_tag_if($return,'if_empty',false);
+ $this->_parse_tag_if($return,'if_notempty',false);
+
+ return $return;
+ }
+
+ /**
+ * Замена тега поля в шаблоне письма
+ */
+ function _parse_tag_fld_mail ($matches)
+ {
+ global $AVE_DB, $AVE_Template;
+
+ $field_id = (int)$matches[1];
+ // забираем массив поля
+ $field = $this->form['fields'][$field_id];
+ // если поля нет, возвращаем тег обратно
+ if (empty($field)) return $matches[0];
+ // если поля не было в шаблоне формы, убираем тег
+ if ($field['is_used'] !== true || empty($field['active'])) return '';
+ // иначе, продолжаем
+ $alias_id = $this->form['alias_id'];
+ // Если ключ $field_id отсутствует в $_POST, берем пустую строку.
+ $val = $_POST['form-' . $alias_id][$field_id] ?? '';
+ $newval = '';
+ $tag_mail_empty = ($this->form['mail_set']['format'] === 'text' ? '<' : '<') . $AVE_Template->get_config_vars('tag_mail_empty') . ($this->form['mail_set']['format'] === 'text' ? '>' : '>');
+
+ // делаем поправки для типов
+ switch ($field['type'])
+ {
+ case 'select':
+ $newval = $field['setting'][$val];
+ if ($field['title'] == 'receivers') $newval = $newval['name'];
+ break;
+
+ case 'multiselect':
+ foreach ($val as $val1) $newval .= $field['setting'][$val1] . ',';
+ $newval = rtrim($newval,',');
+ break;
+
+ case 'checkbox':
+ $newval = ($val ? $AVE_Template->get_config_vars('yes') : $AVE_Template->get_config_vars('no'));
+ break;
+
+ case 'doc':
+ if (!empty($val)) $newval = $AVE_DB->Query("SELECT document_title FROM " . PREFIX . "_documents WHERE Id='" . $val . "'")->GetCell();
+ break;
+
+ case 'multidoc':
+ if (!empty($val))
+ {
+ $sql = $AVE_DB->Query("SELECT document_title FROM " . PREFIX . "_documents WHERE Id IN (" . implode(',',$val) . ")");
+ while ($titl = $sql->GetCell()) $titls[] = $titl;
+ $newval = implode(', ',$titls);
+ }
+ break;
+
+ case 'file':
+ // 1. Безопасно получаем массив имен файлов, используя оператор объединения с null (??).
+ // Если $_FILES['form-bag-report'] отсутствует или не содержит ['name'],
+ // $file_names будет пустым массивом [].
+ $file_names = $_FILES['form-' . $alias_id]['name'] ?? [];
+
+ // 2. implode() теперь безопасно работает с $file_names (массив или []).
+ $newval = implode(', ', $file_names);
+ break;
+
+ default:
+ $newval = $val;
+ }
+ return (empty($newval) ? $tag_mail_empty : $newval);
+ }
+
+ /**
+ * Внешние методы класса
+ */
+
+ /**
+ * Вывод списка форм
+ */
+ function forms_list ()
+ {
+ global $AVE_DB, $AVE_Template;
+ $assign = array();
+
+ $limit = 20;
+ $start = get_current_page() * $limit - $limit;
+ $sql = $AVE_DB->Query("
+ SELECT SQL_CALC_FOUND_ROWS
+ f.*,
+ SUM(IF(h.status='new',1,0)) AS history_new,
+ SUM(IF(h.status='viewed',1,0)) AS history_viewed,
+ SUM(IF(h.status='replied',1,0)) AS history_replied
+ FROM " . PREFIX . "_module_forms_forms AS f
+ LEFT OUTER JOIN " . PREFIX . "_module_forms_history AS h ON f.id = h.form_id
+ GROUP BY f.id
+ ORDER BY f.id ASC
+ LIMIT " . $start . "," . $limit . "
+ ");
+ $num = (int)$AVE_DB->Query("SELECT FOUND_ROWS()")->GetCell();
+ $pages = @ceil($num / $limit);
+ if ($num > $limit)
+ {
+ $page_nav = '{t}';
+ $page_nav = get_pagination($pages, 'page', $page_nav);
+ $AVE_Template->assign('page_nav', $page_nav);
+ }
+
+ while ($row = $sql->FetchAssocArray())
+ {
+ $assign['forms'][] = $row;
+ }
+
+ $AVE_Template->assign($assign);
+ $AVE_Template->assign('content', $AVE_Template->fetch($this->tpl_dir . 'forms.tpl'));
+ }
+
+ /**
+ * Создание/редактирование формы
+ */
+ function form_edit ()
+ {
+ global $AVE_DB, $AVE_Template;
+ $form = array();
+ $assign = array();
+ $fid = $assign['fid'] = !empty($_REQUEST['fid']) ? (int)$_REQUEST['fid'] : 0;
+
+ if ($fid)
+ {
+ $form = $this->_form($fid);
+
+ // для правильного вывода селектов
+ if (empty($form['mail_set']['receivers'])) $form['mail_set']['receivers'] = array(0 => array());
+
+ // Добавляем проверку, чтобы избежать ошибки
+ if (is_array($form['fields']))
+ {
+ foreach ($form['fields'] as &$field)
+ {
+ if (($field['type'] == 'select' || $field['type'] == 'multiselect') && empty($field['setting']))
+ {
+ $field['setting'] = array(array());
+ }
+ }
+ }
+ }
+
+ // алерт при открытии правки
+ if (!empty($_SESSION['module_forms_admin'][$fid]['edit_alert']))
+ {
+ $assign['alert']['text'] = $AVE_Template->get_config_vars($_SESSION['module_forms_admin'][$fid]['edit_alert']['text']);
+ $assign['alert']['theme'] = $_SESSION['module_forms_admin'][$fid]['edit_alert']['theme'];
+ unset($_SESSION['module_forms_admin'][$fid]['edit_alert']);
+ }
+ $assign['form'] = $form;
+ $assign['form_fields_tpl'] = $this->tpl_dir . 'form_fields.tpl';
+ $assign['rubrics'] = $this->_rubrics();
+
+ // назначаем массив CodeMirror
+ $assign['codemirror_data'] = array(
+ 'rubheader' => 200,
+ 'from_name' => 60,
+ 'from_email' => 60,
+ 'subject_tpl' => 60,
+ 'form_tpl' => 460,
+ 'mail_tpl' => 460,
+ 'finish_tpl' => 320,
+ 'code_onsubmit' => 200,
+ 'code_onvalidate' => 200,
+ 'code_onsend' => 200,
+ 'code_beforesend' => 200
+ );
+
+ $AVE_Template->assign($assign);
+ $AVE_Template->assign('content', $AVE_Template->fetch($this->tpl_dir . 'form_edit.tpl'));
+ }
+
+ /**
+ * Получение полей через аякс
+ */
+ function form_fields_fetch ()
+ {
+ global $AVE_DB, $AVE_Template;
+ $fid = $assign['fid'] = !empty($_REQUEST['fid']) ? (int)$_REQUEST['fid'] : 0;
+ if (!$fid) return;
+ $form = $this->_form($fid);
+ // для правильного вывода селектов
+ if (empty($form['mail_set']['receivers'])) $form['mail_set']['receivers'] = array(0 => array());
+ foreach ($form['fields'] as &$field)
+ {
+ if (($field['type'] == 'select' || $field['type'] == 'multiselect') && empty($field['setting']))
+ {
+ $field['setting'] = array(0 => '');
+ $field['setting_empty'] = true;
+ }
+ }
+ $AVE_Template->assign('fields',$form['fields']);
+ $AVE_Template->assign('rubrics',$this->_rubrics());
+ $AVE_Template->assign('field_tpl_open',$_REQUEST['field_tpl_open']);
+
+ return $AVE_Template->fetch($this->tpl_dir . 'form_fields.tpl');
+ }
+
+ /**
+ * Сохранение формы
+ */
+ function form_save ($fid)
+ {
+ global $AVE_DB;
+
+ // 🛑 ИСПРАВЛЕНИЕ PHP 8.4: Инициализация mail_set и receivers, если они отсутствуют в $_REQUEST
+ if (!isset($_REQUEST['mail_set']) || !is_array($_REQUEST['mail_set'])) {
+ $_REQUEST['mail_set'] = array();
+ }
+ if (!isset($_REQUEST['mail_set']['receivers']) || !is_array($_REQUEST['mail_set']['receivers'])) {
+ $_REQUEST['mail_set']['receivers'] = array();
+ }
+ // ----------------------------------------------------------------------------------
+
+ // проверяем Email-ы получателей
+ $receivers = array();
+ foreach ($_REQUEST['mail_set']['receivers'] as $receiver)
+ {
+ if ($this->_email_validate($receiver['email'])) $receivers[] = $receiver;
+ }
+ $_REQUEST['mail_set']['receivers'] = $receivers;
+
+ // параметры отправителя
+ // 🛑 ИСПРАВЛЕНИЕ PHP 8.4: Использование оператора ?? '' для from_email
+ if (! trim($_REQUEST['mail_set']['from_email'] ?? '') > '') $_REQUEST['mail_set']['from_email'] = get_settings('mail_from');
+
+ // 🛑 ИСПРАВЛЕНИЕ PHP 8.4: Использование оператора ?? '' для from_name
+ if (! trim($_REQUEST['mail_set']['from_name'] ?? '') > '') $_REQUEST['mail_set']['from_name'] = get_settings('mail_from_name');
+
+ if ($fid)
+ {
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_forms
+ SET
+ title = '" . addslashes($_REQUEST['title']) . "',
+ protection = '" . (int)$_REQUEST['protection'] . "',
+ " . (($_REQUEST['alias'] === '' || $this->_alias_validate($_REQUEST['alias'],$fid) === true) ? " alias = '" . $_REQUEST['alias'] . "'," : '') . "
+ mail_set = '" . addslashes(serialize($_REQUEST['mail_set'])) . "',
+ rubheader = '" . addslashes($_REQUEST['rubheader']) . "',
+ form_tpl = '" . addslashes($_REQUEST['form_tpl']) . "',
+ mail_tpl = '" . addslashes($_REQUEST['mail_tpl']) . "',
+ finish_tpl = '" . addslashes($_REQUEST['finish_tpl']) . "',
+ code_onsubmit = '" . addslashes($_REQUEST['code_onsubmit']) . "',
+ code_onvalidate = '" . addslashes($_REQUEST['code_onvalidate']) . "',
+ code_onsend = '" . addslashes($_REQUEST['code_onsend']) . "',
+ code_beforesend = '" . addslashes(isset($_REQUEST['code_beforesend']) ? $_REQUEST['code_beforesend'] : '') . "'
+ WHERE
+ id = '" . $fid . "'
+ ");
+ }
+ else
+ {
+ $mail_set = array(
+ 'from_name' => get_settings('mail_from_name'),
+ 'from_email' => get_settings('mail_from'),
+ 'receivers' => array(0 => array(
+ 'name' => get_settings('mail_from_name'),
+ 'email' => get_settings('mail_from')
+ ),
+ ),
+ 'subject_tpl' => '[tag:formtitle]',
+ 'format' => 'text'
+ );
+ $AVE_DB->Query("
+ INSERT INTO " . PREFIX . "_module_forms_forms
+ SET
+ title = '" . addslashes($_REQUEST['title']) . "',
+ " . (($this->_alias_validate($_REQUEST['alias'],$fid) === true || $_REQUEST['alias'] === '') ? " alias = '" . $_REQUEST['alias'] . "'," : '') . "
+ mail_set = '" . addslashes(serialize($mail_set)) . "'
+ ");
+ $fid = (int)$AVE_DB->InsertId();
+
+/* // 🛑 ИСПРАВЛЕНИЕ AJAX: Явный вывод ID для редиректа JS (fid=NaN)
+ if (isset($_REQUEST['ajax']) && empty($_REQUEST['demo'])) {
+ echo $fid;
+ return; // Прекращаем выполнение, чтобы не выводить лишние данные полей
+ }*/
+
+ $_REQUEST['fields'] = $this->fields_main_data;
+ // прописываем алерт об успешном создании
+ if ($fid > 0) $_SESSION['module_forms_admin'][$fid]['edit_alert'] = array('text' => 'created', 'theme' => 'accept');
+
+ // если устанавливаем пример
+ if (!empty($_REQUEST['demo']))
+ {
+ $demo = array();
+ include(BASE_DIR . '/modules/forms/demo.php');
+ $_REQUEST = array_merge($_REQUEST,$demo);
+ // обновляем форму с данными примера
+ $this->form_save($fid);
+ // подставляем в шаблон новые id полей
+ $demo['form_tpl'] = preg_replace_callback(
+ '/\[tag:fld:(\d+)]/',
+ function($matches) {
+ return "[tag:fld:" . ($_REQUEST["demo_change"][(int)$matches[1]] ?? '') . "]";
+ },
+ $demo['form_tpl']
+ );
+
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_forms
+ SET
+ form_tpl = '" . addslashes($demo['form_tpl']) . "'
+ WHERE id = '" . $fid . "'
+ ");
+ return $fid;
+ }
+ }
+
+ // сохраняем поля
+ foreach ($_REQUEST['fields'] as $field_id => $field)
+ {
+ if (!trim($field['title'])) continue;
+ if (isset($field['setting']) && is_array($field['setting']))
+ {
+ $settings = array();
+ foreach ($field['setting'] as $setting)
+ {
+ // если получатели
+ if ($field['title'] == 'receivers' && is_array($setting) && trim($setting['name']) > '' && trim($setting['email']) > '' && $this->_email_validate($setting['email']))
+ $settings[] = $setting;
+ // другое
+ elseif (!is_array($setting) && trim($setting) > '')
+ $settings[] = $setting;
+ }
+ $field['setting'] = serialize($settings);
+ }
+ elseif ($field['type'] == 'file') $field['setting'] = (int)$field['setting'];
+
+ // 🛑 ИСПРАВЛЕНИЕ PHP 8.4: Проверка существования ключа перед доступом
+ if (isset($field['defaultval']) && is_array($field['defaultval'])) {
+ $field['defaultval'] = serialize($field['defaultval']);
+ }
+
+ $sql = "
+ title = '" . addslashes($field['title']) . "',
+ active = '" . (int)($field['active'] ?? 0) . "',
+ type = '" . $field['type'] . "',
+ setting = '" . addslashes($field['setting'] ?? '') . "',
+ required = '" . (int)($field['required'] ?? 0) . "',
+ defaultval = '" . addslashes($field['defaultval'] ?? '') . "',
+ attributes = '" . addslashes(trim($field['attributes'] ?? '')) . "',
+ tpl = '" . addslashes($field['tpl'] ?? '') . "'
+ ";
+ // ИСПРАВЛЕНИЕ PHP 8.4: Проверка существования ключа 'new'
+ if (isset($field['new']) && $field['new'])
+ {
+ $AVE_DB->Query("
+ INSERT INTO " . PREFIX . "_module_forms_fields
+ SET
+ form_id = '" . (int)$fid . "',
+ main = '" . (int)($field['main'] ?? 0) . "',
+ " . $sql . "
+ ");
+ if ($_REQUEST['demo'] ?? false) $_REQUEST['demo_change'][$field_id] = (int)$AVE_DB->InsertId();
+ }
+ else
+ {
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_fields
+ SET
+ " . $sql . "
+ WHERE
+ id = '" . (int)$field_id . "' AND
+ form_id = '" . $fid . "'
+ ");
+ }
+ }
+
+ // ИСПРАВЛЕНИЕ PHP 8.4: Безопасная итерация по удаляемым полям
+ foreach ($_REQUEST['field_del'] ?? [] as $field_id => $delete)
+ {
+ if (empty($delete)) continue;
+ $AVE_DB->Query("
+ DELETE FROM " . PREFIX . "_module_forms_fields
+ WHERE
+ id = '" . (int)$field_id . "' AND
+ main != '1'
+ ");
+ }
+
+ return $fid;
+ }
+
+ /**
+ * Удаление формы
+ */
+ function form_del ($fid)
+ {
+ global $AVE_DB;
+
+ $AVE_DB->Query("
+ DELETE FROM " . PREFIX . "_module_forms_forms
+ WHERE id = '" . $fid . "'
+ ");
+ $AVE_DB->Query("
+ DELETE FROM " . PREFIX . "_module_forms_fields
+ WHERE form_id = '" . $fid . "'
+ ");
+ }
+
+ /**
+ * Сохранение формы
+ */
+ function form_copy ($fid)
+ {
+ global $AVE_DB;
+
+ // форма
+ $form = $AVE_DB->Query("
+ SELECT * FROM " . PREFIX . "_module_forms_forms
+ WHERE id = '" . $fid . "'
+ ")->FetchAssocArray();
+ if (empty($form)) return;
+ $query = "INSERT INTO " . PREFIX . "_module_forms_forms SET ";
+ foreach ($form as $key => $val)
+ {
+ if ($key == 'id' || $key == 'alias') continue;
+ $query .= $key . " = '" . addslashes($val) . "', ";
+ }
+ $query = rtrim($query,', ');
+ $AVE_DB->Query($query);
+ $fid_new = (int)$AVE_DB->InsertId();
+
+ // поля
+ $sql = $AVE_DB->Query("
+ SELECT * FROM " . PREFIX . "_module_forms_fields
+ WHERE form_id = '" . $fid . "'
+ ");
+ while ($row = $sql->FetchAssocArray())
+ {
+ if (empty($row['id'])) continue;
+ $query = "INSERT INTO " . PREFIX . "_module_forms_fields SET ";
+ foreach ($row as $key => $val)
+ {
+ if ($key == 'id') continue;
+ elseif ($key == 'form_id') $val = $fid_new;
+ $query .= $key . " = '" . addslashes($val) . "', ";
+ }
+ $query = rtrim($query,', ');
+ $AVE_DB->Query($query);
+ }
+
+ return $fid_new;
+ }
+
+
+ /**
+ * Вывод формы
+ */
+ function form_display ($alias_id)
+ {
+ global $AVE_Template;
+
+ $form = $this->_form($alias_id);
+
+ if (empty($form))
+ return "[mod_forms:$alias_id] - " . $AVE_Template->get_config_vars('form_notfound');
+
+ // по дефолту форма валидна, но не отправлена - потом перезаписываем, если что
+ $this->form['is_valid'] = true;
+ $this->form['ajax']['form']['is_valid'] = true;
+ $this->form['ajax']['form']['is_sent'] = false;
+ $this->form['ajax']['form']['finish_tpl'] = null;
+
+ // rubheader
+ $GLOBALS['user_header']['module_forms_' . $alias_id] = $this->_parse_tags($this->form['rubheader']);
+
+ // вывод финишной страницы, если включена проверка от повторной отправки
+ if (! empty($_GET['mcnfinish']) && $form['protection'])
+ {
+ $session_key = $form['id'] . $_GET['mcnfinish']; // Определяем ключ заранее
+
+ // ИСПРАВЛЕНО: Используем isset() для проверки существования ключа сессии
+ if (isset($_SESSION['mcnfinish'][$session_key]) && $_SESSION['mcnfinish'][$session_key] === true)
+ {
+ unset($_SESSION['mcnfinish'][$session_key]);
+
+ // формируем финишную страницу
+ $tpl = $this->_parse_tags($form['finish_tpl']);
+ $tpl = $this->_eval2var('?>' . $tpl . '');
+
+ return $tpl;
+ }
+ else
+ {
+ header('Location: ' . trim(str_replace('mcnfinish=' . $_GET['mcnfinish'],'',$_SERVER['REQUEST_URI']),'?&'));
+ exit;
+ }
+ }
+ // определяем (bool) отправка формы
+ else
+ $this->form['is_submited'] = false;
+
+ if (! empty($_POST['form-' . $alias_id]))
+ {
+ $this->form['is_submited'] = true;
+ // выполняем код после отправки формы
+ eval('?>' . $this->form['code_onsubmit'] . '');
+ }
+
+ $tpl = $this->form['form_tpl'];
+ // меняем теги основных полей на стандартные
+ $tpl = $this->_parse_tag_fld_main($tpl,true);
+ // парсим теги полей и названий
+ $tpl = preg_replace_callback('/\[tag:fld:(\d+)]/', array($this,'_parse_tag_fld_form'), $tpl);
+ $tpl = preg_replace_callback('/\[tag:title:([A-Za-z0-9-_]+)]/', array($this,'_parse_tag_title'),$tpl);
+
+ // выполняем код после валидации
+ eval('?>' . $this->form['code_onvalidate'] . '');
+
+ // если форма валидна, отправляем её
+ if ($this->form['is_submited'] === true && $this->form['is_valid'] === true)
+ return $this->form_submit($alias_id);
+ // иначе - заканчиваем вывод
+ else
+ {
+ // парсим основные теги
+ $tpl = $this->_parse_tags($tpl);
+ // теги общей валидности
+ if ($this->form['is_submited'])
+ {
+ $this->_parse_tag_if($tpl,'if_form_valid',$this->form['is_valid']);
+ $this->_parse_tag_if($tpl,'if_form_invalid',!$this->form['is_valid']);
+ }
+ else
+ {
+ $this->_parse_tag_if($tpl,'if_form_valid',false);
+ $this->_parse_tag_if($tpl,'if_form_invalid',false);
+ }
+ // заменяем теги условий
+ $tpl = preg_replace('/\[tag:(if|elseif)_fld:(\d*)(.*?)]/si',' $1 (\$_POST["fields"][$2]$3): ?>',$tpl);
+ $tpl = str_replace(array('[tag:else_fld]','[/tag:if_fld]'),array(' else: ?>',' endif ?>'),$tpl);
+ // выполняем код
+ return $this->_eval2var('?>' . $tpl . '');
+ }
+ }
+
+
+ /**
+ * Отправка формы
+ */
+ function form_submit ($alias_id)
+ {
+ global $AVE_DB, $AVE_Template;
+
+ $form = $this->form;
+
+ $fields = $form['fields'];
+
+ $fid = $form['id'];
+
+ // формируем список получателей
+ $recs = array();
+
+ // Определяем ID главных полей безопасно (Line 1225 fix preparation)
+ $copy_field_id = isset($form['fields_main']['copy']) ? $form['fields_main']['copy'] : null;
+ $email_field_id = isset($form['fields_main']['email']) ? $form['fields_main']['email'] : null;
+
+ // пользователь (отправка копии) - (Line 1225 fix)
+ // Проверяем наличие ключа 'copy' и его значение в POST или в defaultval
+ $send_copy_post = ($copy_field_id !== null && isset($_POST['form-' . $alias_id][$copy_field_id]) && $_POST['form-' . $alias_id][$copy_field_id] == 1);
+ $send_copy_default = ($copy_field_id !== null && isset($fields[$copy_field_id]['defaultval']) && $fields[$copy_field_id]['defaultval'] == 1);
+
+ if (($form['is_copy'] === true && $send_copy_post) || $send_copy_default)
+ {
+ $email = '';
+
+ if ($form['is_email'] === true && $email_field_id !== null && isset($_POST['form-' . $alias_id][$email_field_id]))
+ $email = $_POST['form-' . $alias_id][$email_field_id];
+
+ if (empty($email))
+ $email = $_SESSION['user_email'];
+
+ if (! empty($email))
+ {
+ $recs[] = array(
+ 'email' => $email,
+ 'name' => $_SESSION['user_name'],
+ 'agent' => 'user',
+ );
+ }
+
+ $history['email'] = $email;
+ }
+ else // если чекбоксы - отправить копию неактивные
+ {
+ $email = '';
+
+ if ($form['is_email'] === true && $email_field_id !== null && isset($_POST['form-' . $alias_id][$email_field_id]))
+ $email = $_POST['form-' . $alias_id][$email_field_id];
+
+ if (empty($email))
+ $email = $_SESSION['user_email'];
+
+ $history['email'] = $email;
+ }
+
+ // главные получатели
+ // Проверка, что receivers - это массив, прежде чем слиять
+ $mail_receivers = is_array($form['mail_set']['receivers']) ? $form['mail_set']['receivers'] : [];
+ $recs = array_merge($recs, $mail_receivers);
+
+ // выбранные в форме получатели
+ if ($this->form['is_receivers'] === true)
+ {
+ // Проверка наличия ключа 'receivers' (Line 1268 fix)
+ $recs_field_id = isset($form['fields_main']['receivers']) ? $form['fields_main']['receivers'] : null;
+
+ if ($recs_field_id !== null && isset($_POST['form-' . $alias_id][$recs_field_id]))
+ {
+ // Проверка, что поле и его настройки существуют, прежде чем получить значение
+ $post_value = $_POST['form-' . $alias_id][$recs_field_id];
+ if (isset($fields[$recs_field_id]['setting'][$post_value])) {
+ $recs[] = $fields[$recs_field_id]['setting'][$post_value];
+ }
+ }
+ }
+
+ // если ни один получатель не назначен, отправляем админскому
+ if (empty($recs)) $recs[] = array(
+ 'name' => get_settings('mail_from_name'),
+ 'email' => get_settings('mail_from')
+ );
+
+ // перезаписываем список уникальных получателей в переменную письма
+ $this->form['receivers'] = array();
+
+ foreach ($recs as $rec)
+ {
+ // Fix Line 1282: Убеждаемся, что $rec — это массив и имеет ключ 'email'
+ if (is_array($rec) && isset($rec['email']))
+ {
+ $trimmed_email = trim($rec['email']);
+ if (!isset($this->form['receivers'][$trimmed_email]) && $trimmed_email > '')
+ $this->form['receivers'][$trimmed_email] = $rec;
+ }
+ }
+
+ $recs = $this->form['receivers'];
+ $recs[] = array('agent' => 'history');
+
+ // обрабатываем тему по умолчанию
+ // Проверка наличия ключа 'subject' и самого поля
+ $subject_field_id = isset($form['fields_main']['subject']) ? $form['fields_main']['subject'] : null;
+
+ if ($subject_field_id !== null && isset($form['fields'][$subject_field_id]))
+ {
+ $subject_field = $form['fields'][$subject_field_id];
+
+ // Добавляем isset для 'active' и 'is_used'
+ $is_active = isset($subject_field['active']) ? $subject_field['active'] : false;
+ $is_used = isset($subject_field['is_used']) ? $subject_field['is_used'] : false;
+
+ if (!$is_active || !$is_used)
+ {
+ $_POST['form-' . $alias_id][$subject_field_id] = $subject_field['defaultval'];
+ }
+ }
+
+ // обрабатываем шаблон письма
+ $tpl = $form['mail_tpl'];
+
+ // меняем теги основных полей на стандартные
+ $tpl = $this->_parse_tag_fld_main($tpl);
+
+ // парсим [tag:easymail]
+ if (strpos($tpl,'[tag:easymail]') !== false)
+ {
+ $easy = '';
+
+ foreach ($fields as $field_id => $field)
+ {
+ // Fix Line 1308: Проверка существования ключа 'is_used'
+ $is_used = isset($field['is_used']) ? $field['is_used'] : false;
+
+ if ($is_used !== true || $field['title'] == 'captcha' || empty($field['active']))
+ continue;
+
+ $easy .= "[tag:title:$field_id]" . ": [tag:fld:$field_id];" . ($form['mail_set']['format'] === 'text' ? "\r\n" : '
');
+ }
+
+ // убираем последний перевод строки
+ $easy = ($form['mail_set']['format'] === 'text') ? rtrim($easy) : substr($easy,0,-4);
+ $tpl = str_replace('[tag:easymail]',$easy,$tpl);
+ }
+
+ // парсим теги полей и названий
+ $tpl = preg_replace_callback('/\[tag:fld:(\d+)]/', array($this,'_parse_tag_fld_mail'),$tpl);
+ $tpl = preg_replace_callback('/\[tag:title:([A-Za-z0-9-_]+)]/', array($this,'_parse_tag_title'),$tpl);
+
+ // парсим основные теги
+ $tpl = $this->_parse_tags($tpl);
+
+ // заменяем теги условий
+ $tpl = preg_replace('/\[tag:(if|elseif)_fld:(\d*)(.*?)]/si',' $1 (\$_POST["form-' . $alias_id . '"][$2]$3): ?>',$tpl);
+ $tpl = str_replace(array('[tag:else_fld]','[/tag:if_fld]'),array(' else: ?>',' endif ?>'),$tpl);
+
+ // файлы-вложения
+ $attach = array();
+
+ if (! empty($_FILES['form-' . $alias_id]['tmp_name']))
+ {
+ foreach ($_FILES['form-' . $alias_id]['name'] as $field_id => $fname)
+ {
+ // ИСПРАВЛЕНИЕ: Безопасное получение расширения
+ $path_parts = pathinfo($fname);
+ $ext = $path_parts['extension'] ?? '';
+
+ if (
+ !empty($_FILES['form-' . $alias_id]['tmp_name'][$field_id]) &&
+ !empty($form['fields'][$field_id]) &&
+ empty($_FILES['form-' . $alias_id]['error'][$field_id]) &&
+ !in_array($ext,array('php', 'phtml', 'php3', 'php4', 'php5', 'js', 'pl'))
+ )
+ {
+ $fname = BASE_DIR . '/tmp/' . ATTACH_DIR . '/' . str_replace(' ', '_', mb_strtolower(trim($fname)));
+
+ if (file_exists($fname))
+ $fname = rtrim($fname,'.' . $ext) . '_' . mt_rand(0,10000) . '.' . $ext;
+
+ @move_uploaded_file($_FILES['form-' . $alias_id]['tmp_name'][$field_id], $fname);
+
+ $attach[] = $fname;
+ }
+ }
+ }
+
+ // Имя отправителя
+ $from_name_tpl = $form['mail_set']['from_name'];
+ $from_name_tpl = $this->_parse_tags($from_name_tpl);
+ $from_name_tpl = $this->_parse_tag_fld_main($from_name_tpl);
+ $from_name_tpl = preg_replace_callback('/\[tag:fld:(\d+)]/', array($this,'_parse_tag_fld_post'),$from_name_tpl);
+
+ // Email отправителя
+ $from_email_tpl = $form['mail_set']['from_email'];
+ $from_email_tpl = $this->_parse_tags($from_email_tpl);
+ $from_email_tpl = $this->_parse_tag_fld_main($from_email_tpl);
+ $from_email_tpl = preg_replace_callback('/\[tag:fld:(\d+)]/', array($this,'_parse_tag_fld_post'),$from_email_tpl);
+
+ // Тема
+ $subject_tpl = $form['mail_set']['subject_tpl'];
+ $subject_tpl = $this->_parse_tags($subject_tpl);
+ $subject_tpl = $this->_parse_tag_fld_main($subject_tpl);
+ $subject_tpl = preg_replace_callback('/\[tag:fld:(\d+)]/', array($this,'_parse_tag_fld_post'),$subject_tpl);
+
+ // выполняем код перед отправкой писем
+ eval(' ?>' . $this->form['code_beforesend'] . '' . 'php ');
+
+ // отправляем письма
+ foreach ($recs as $rec)
+ {
+ $mail = $tpl;
+
+ $from_name = $from_name_tpl;
+
+ $from_email = $from_email_tpl;
+
+ $subject = $subject_tpl;
+
+ $if_user_open = (($rec['agent'] ?? '') === 'user'); // ИСПРАВЛЕНО: Безопасный доступ к 'agent'
+ $if_admin_open = !$if_user_open;
+
+ $this->_parse_tag_if($mail,'if_user',$if_user_open);
+ $this->_parse_tag_if($mail,'if_admin',$if_admin_open);
+ $this->_parse_tag_if($subject,'if_user',$if_user_open);
+ $this->_parse_tag_if($subject,'if_admin',$if_admin_open);
+
+ // @fix для v1.0 beta <= 2: поддержка тега if_notuser
+ $this->_parse_tag_if($mail,'if_notuser',$if_admin_open);
+ $this->_parse_tag_if($subject,'if_notuser',$if_admin_open);
+
+ $mail = trim($this->_eval2var(' ?>' . $mail . '' . 'php '));
+ $subject = trim($this->_eval2var(' ?>' . $subject . '' . 'php '));
+
+ // сохраняем в бд историю (письмо, которое пришло админу)
+ if (($rec['agent'] ?? '') === 'history') // ИСПРАВЛЕНО: Безопасный доступ к 'agent'
+ {
+ $history['dialog']['request']['body'] = $mail;
+ $history['dialog']['request']['format'] = $form['mail_set']['format'];
+
+ $AVE_DB->Query("
+ INSERT INTO " . PREFIX . "_module_forms_history
+ SET
+ form_id = '" . $fid . "',
+ email = '" . $history['email'] . "',
+ subject = '" . addslashes($subject) . "',
+ date = '" . time() . "',
+ dialog = '" . addslashes(serialize($history['dialog'])) . "',
+ postdata = '" . addslashes(serialize($_POST)) . "'
+ ");
+
+ unset($history);
+ }
+ // иначе, отправляем письмо
+ else
+ {
+ $this->_parse_tag_if($from_name,'if_user',$if_user_open);
+ $this->_parse_tag_if($from_name,'if_admin',$if_admin_open);
+ $this->_parse_tag_if($from_email,'if_user',$if_user_open);
+ $this->_parse_tag_if($from_email,'if_admin',$if_admin_open);
+
+ $from_name = $this->_eval2var('?>' . $from_name . '');
+ $from_name = trim(preg_replace('/\s+/',' ',$from_name));
+ $from_email = $this->_eval2var('?>' . $from_email . '');
+ $from_email = trim(preg_replace('/\s+/','',$from_email));
+
+ if (empty($from_email))
+ $from_email = get_settings('mail_from');
+
+ send_mail(
+ $rec['email'],
+ $mail,
+ $subject,
+ $from_email,
+ $from_name,
+ $form['mail_set']['format'],
+ $attach,
+ false,
+ false
+ );
+ // @fix для AVE.cms.v1.5.beta <= 28: в send_mail() не выключен кэш в конце
+ //ob_end_clean();
+ }
+ }
+
+ // выполняем код после отправки писем
+ eval(' ?>' . $this->form['code_onsend'] . '' . 'php ');
+
+ // если включена защита от повторной отправки и не ajax
+ if ($form['protection'] && ! isAjax())
+ {
+ $rand = mt_rand();
+ $_SESSION['mcnfinish'][$form['id'] . $rand] = true;
+ header('Location: ' . $_SERVER['REQUEST_URI'] . ((strpos($_SERVER['REQUEST_URI'], '?') !== false) ? '&' : '?') . 'mcnfinish=' . $rand);
+ exit;
+ }
+ // иначе
+ else
+ {
+ // формируем финишную страницу
+ $tpl = $this->_parse_tags($form['finish_tpl']);
+ $tpl = $this->_eval2var(' ?>' . $tpl . '' . 'php ');
+ // сохраняем информацию для аякса
+ $this->form['ajax']['form']['is_sent'] = true;
+ $this->form['ajax']['form']['finish_tpl'] = $tpl;
+
+ return $tpl;
+ }
+ }
+
+
+ /**
+ * Вывод истории
+ */
+ function history_list ($fid)
+ {
+ global $AVE_DB, $AVE_Template;
+ $assign = array();
+ $assign['fid'] = $fid;
+ $assign['form'] = $this->_form($fid,false);
+
+ $limit = 50;
+ $start = get_current_page() * $limit - $limit;
+ $sql = $AVE_DB->Query("
+ SELECT SQL_CALC_FOUND_ROWS *
+ FROM " . PREFIX . "_module_forms_history
+ WHERE form_id = '" . $fid . "'
+ ORDER BY date DESC
+ LIMIT " . $start . "," . $limit . "
+ ");
+
+ $num = (int)$AVE_DB->Query("SELECT FOUND_ROWS()")->GetCell();
+ $pages = @ceil($num / $limit);
+
+ if ($num > $limit)
+ {
+ $page_nav = '{t}';
+ $page_nav = get_pagination($pages, 'page', $page_nav);
+ $AVE_Template->assign('page_nav', $page_nav);
+ }
+
+ while ($row = $sql->FetchAssocArray())
+ {
+ unset($row['dialog']);
+ $assign['dialogs'][] = $row;
+ }
+
+ $assign = $this->_stripslashes($assign);
+ $AVE_Template->assign($assign);
+ $AVE_Template->assign('content', $AVE_Template->fetch($this->tpl_dir . 'history.tpl'));
+ }
+
+ /**
+ * Удаление выбранного e-mail
+ */
+ function email_del ($hid)
+ {
+ global $AVE_DB, $AVE_Template;
+
+ $AVE_DB->Query("
+ DELETE FROM " . PREFIX . "_module_forms_history
+ WHERE id = '" . $hid . "'
+ ");
+ }
+
+ /**
+ * Сохранение статуса диалога
+ */
+ function dialog_status ($hid)
+ {
+ global $AVE_DB;
+
+ if ($_REQUEST['status'] !== 'new')
+ {
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_history
+ SET
+ status = '" . $_REQUEST['status'] . "'
+ WHERE id = '" . $hid . "'
+ ");
+ }
+
+ if (empty($_REQUEST['ajax']))
+ {
+ header('Location: index.php?do=modules&action=modedit&mod=forms&moduleaction=history_list&fid=' . $_REQUEST['fid'] . '&cp=' . SESSION);
+ exit;
+ }
+ }
+
+ /**
+ * Вывод диалога
+ */
+ function history_dialog ($hid)
+ {
+ global $AVE_DB, $AVE_Template;
+ $assign = array();
+
+ $assign['hid'] = $hid;
+ $history = $AVE_DB->Query("
+ SELECT *
+ FROM " . PREFIX . "_module_forms_history
+ WHERE id = '" . $hid . "'
+ ")->FetchAssocArray();
+
+ $history['dialog'] = unserialize($history['dialog']);
+ $history = $this->_stripslashes($history);
+ $assign['fid'] = $history['form_id'];
+ $assign['form'] = $this->_form($history['form_id'],false);
+
+ // меняем статус на прочитанное
+ if ($history['status'] === 'new')
+ {
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_history
+ SET
+ status = 'viewed'
+ WHERE id = '" . $hid . "'
+ ");
+ }
+
+ // обращение
+ $request_author = $AVE_DB->Query("
+ SELECT Id AS user_id, user_name, firstname, lastname
+ FROM " . PREFIX . "_users
+ WHERE email = '" . $history['email'] . "'
+ ")->FetchAssocArray();
+
+ if (!empty($request_author))
+ $history['dialog']['request'] = array_merge($history['dialog']['request'], $request_author);
+
+// ответы
+foreach ($history['dialog']['response'] ?? [] as &$response)
+{
+ $response_author = $AVE_DB->Query("
+ SELECT user_name, firstname, lastname
+ FROM " . PREFIX . "_users
+ WHERE Id = '" . $response['user_id'] . "'
+ ")->FetchAssocArray();
+
+ if (!empty($response_author))
+ $response = array_merge($response, $response_author);
+}
+
+ // форма ответа
+ if (empty($history['dialog']['response_draft']))
+ {
+ $history['dialog']['response_draft'] = array(
+ 'from_email' => get_settings('mail_from'),
+ 'from_name' => get_settings('mail_from_name'),
+ 'body' => "\r\n\r\n\r\n--\r\n" . get_settings('mail_signature'),
+ );
+ }
+
+ // алерт при открытии $fid -> $hid
+ if (!empty($_SESSION['module_forms_admin'][$hid]['dialog_alert']))
+ {
+ $assign['alert']['text'] = $AVE_Template->get_config_vars($_SESSION['module_forms_admin'][$hid]['dialog_alert']['text']);
+ $assign['alert']['theme'] = $_SESSION['module_forms_admin'][$hid]['dialog_alert']['theme'];
+ unset($_SESSION['module_forms_admin'][$hid]['dialog_alert']);
+ }
+
+ $AVE_Template->assign($assign);
+ $AVE_Template->assign($history);
+ $AVE_Template->assign('content', $AVE_Template->fetch($this->tpl_dir . 'dialog.tpl'));
+ }
+
+ /**
+ * Сохранение и отправка ответа
+ */
+ function history_dialog_submit ($hid)
+ {
+ global $AVE_DB;
+
+ $history = $AVE_DB->Query("
+ SELECT *
+ FROM " . PREFIX . "_module_forms_history
+ WHERE id = '" . $hid . "'
+ ")->FetchAssocArray();
+ $history['dialog'] = unserialize($history['dialog']);
+
+ if ($_REQUEST['send'])
+ {
+ $response = $_POST;
+ $response['user_id'] = UID;
+ $response['date'] = time();
+ $history['dialog']['response'][] = $response;
+ $history['status'] = 'replied';
+ unset($history['dialog']['response_draft']);
+ send_mail(
+ $history['email'],
+ $_POST['body'],
+ $_POST['subject'],
+ $_POST['from_email'],
+ $_POST['from_name'],
+ $_POST['format'],
+ array(),
+ false,false
+ );
+ }
+ else
+ {
+ $history['dialog']['response_draft'] = $_POST;
+ }
+
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_forms_history
+ SET
+ dialog = '" . addslashes(serialize($history['dialog'])) . "',
+ status = '" . $history['status'] . "'
+ WHERE id = '" . $hid . "'
+ ");
+
+ if ($_REQUEST['send'])
+ {
+ // прописываем алерт об успешной отправке письма $fid -> $hid
+ $_SESSION['module_forms_admin'][$hid]['dialog_alert'] = array('text' => 'respose_sent', 'theme' => 'accept');
+
+ header('Location: index.php?do=modules&action=modedit&mod=forms&moduleaction=history_dialog&hid=' . $hid . '&cp=' . SESSION);
+ exit;
+ }
+ }
+ }
+?>
\ No newline at end of file
diff --git a/demo.php b/demo.php
new file mode 100644
index 0000000..808ee79
--- /dev/null
+++ b/demo.php
@@ -0,0 +1,478 @@
+
+'[tag:hide:2:У вас нет прав для заполнения данной формы!]
+
| + | ID | +{#title#} | +{#type#} | +{#sets#} | +! |
+ {#defaultval#} | ++ | + | ||
| + + | ++ + | ++ {if $field.main} + + {else} + + {/if} + | + {if $field.main && $field.title != 'subject' && $field.title != 'email'} +
+
+ {if $field.main && $field.title=='receivers'}
+ {foreach from=$field.setting item=receiver name=receivers}
+
+
+
+ {if $smarty.foreach.receivers.index == 0}
+
+ {else}
+
+ {/if}
+
+ {/foreach}
+ {else}
+
+ {/if}
+ |
+ {else}
+ + {if $field.main} + + {else} + + {/if} + | +
+ {if $field.type=='input' || $field.type=='textarea'}
+
+ {elseif $field.type=='select' || $field.type=='multiselect'}
+ {foreach from=$field.setting item=title name=select}
+
+
+ {if $smarty.foreach.select.index == 0}
+
+ {else}
+
+ {/if}
+
+ {/foreach}
+ {elseif $field.type=='doc' || $field.type=='multidoc'}
+
+ {elseif $field.type == 'file'}
+
+ {/if}
+ |
+ {/if}
+ + {if !($field.type == 'doc' || $field.type == 'multidoc' || ($field.main && ($field.title == 'copy' || $field.title == 'receivers')))} + + {if $field.main && $field.title=='captcha'} + + {/if} + {/if} + {if !$field.main && $field.type == 'select'} + + {/if} + | +
+ {if $field.main && $field.title=='captcha'}
+ {elseif $field.type=='input' || $field.type=='textarea'}
+
+ |
+ php |
+ [tag:title] |
+ [tag:uemail] |
+ [tag:uname] |
+ [tag:ufname] |
+ [tag:ulname] |
+ [tag:submitted_page]
+
+
+ {if $ave15}
+ {include file="$codemirror_editor" ctrls='form_save();' conn_id="_field_defaultval_$field_id" textarea_id="field_defaultval[$field_id]" height=60}
+ {else}
+
+ {/if}
+ {elseif ($field.type=='select' || $field.type=='multiselect')}
+ {if !$field.setting_empty|default:0}
+
+ {else}
+ {#setting_empty#}
+ {/if}
+ {elseif $field.type=='checkbox'}
+
+
+ {/if}
+ |
+ + + + | ++ {if !$field.main} + + {/if} + | +|
|
+
+
+ {#attributes#}
+
+ |
+ ||||||||||
{#field_add#} |
+ ||||||||||
| + + | ++ | + + | ++ + | ++ + + | +||||||