_anon_cookie_name])) {
return preg_replace('/[^a-f0-9]/', '', $_COOKIE[$this->_anon_cookie_name]);
} else {
$new_key = md5(uniqid(rand(), true));
// Устанавливаем куку на заданный срок
setcookie($this->_anon_cookie_name, $new_key, time() + (86400 * $this->conf_cookie_life), "/");
$_COOKIE[$this->_anon_cookie_name] = $new_key; // Чтобы сразу было доступно в текущем скрипте
return $new_key;
}
}
/**
* Метод, предназначенный для получения основных настроек модуля,которые задаются в Панели управления.
*
* @param string $param название параметра
* @return mixed значение настройки
*/
function _commentSettingsGet($param = '')
{
global $AVE_DB;
// Определяем статическую переменную, которая будет хранить полученные настройки на протяжении всего
// срока жизни объекта.
static $settings = null;
// Если переменная $settings еще не имеет значений, тогда выполняем запрос к БД на получение данных
if ($settings === null)
{
$settings = $AVE_DB->Query("
SELECT *
FROM " . PREFIX . "_module_comments
WHERE Id = '" . $this->_config_id . "'
")->FetchAssocArray();
}
if ($param == '')
return $settings;
// В противном случае возвращаем уже имеющиеся значения
return (isset($settings[$param])
? $settings[$param]
: null);
}
/**
* Метод, предназначенный для получения количества комментариев для определенного документа.
*
* @param int $document_id - идентификатор документа
* @return int - количество комментариев
*/
function _commentPostCountGet($document_id)
{
global $AVE_DB;
// Определяем статический массив, который будет хранить количество комментариев для документов на
// протяжении всего срока жизни объекта.
static $comments = array();
// Если в массиве не найден ключ, который соответствует запрашиваемому документу, тогда выполняем
// запрос к БД на получение количества комментариев
if (! isset($comments[$document_id]))
{
$comments[$document_id] = $AVE_DB->Query("
SELECT COUNT(*)
FROM " . PREFIX . "_module_comment_info
WHERE document_id = '" . (int)$document_id . "'
")->GetCell();
}
// Возвращаем количество комментариев для запрашиваемого документа
return $comments[$document_id];
}
/**
* Внешние методы класса
*/
/**
* Следующие методы описывают работу модуля в Публичной части сайта.
*/
/**
* Метод, предназначенный для получения из БД всех комментариев, относящихся к указанному
* документу с последующим выводом в Публичной части.
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentListShow($tpl_dir)
{
global $AVE_DB, $AVE_Template, $AVE_Core;
$document_id = (int)($_REQUEST['id'] ?? 0);
$artpage = $_REQUEST['artpage'] ?? null;
$apage = $_REQUEST['apage'] ?? null;
$user_group = UGROUP ?? 0;
// --- НОВАЯ ЛОГИКА: ОПРЕДЕЛЯЕМ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ ---
$current_user_id = (int)($_SESSION['user_id'] ?? 0);
$anon_key = $this->_getAnonKey();
// -----------------------------------------------------
// Получаем ВСЕ настройки модуля разом
$settings = $this->_commentSettingsGet();
// Проверяем, что в настройках модуля разрешено комментирование
if (isset($settings['comment_active']) && $settings['comment_active'] == 1)
{
$read_groups = explode(',', $settings['comment_user_groups_read']);
$assign['no_read_permission'] = 0;
if (!in_array($user_group, $read_groups))
{
$assign['no_read_permission'] = 1;
}
$assign['display_comments'] = 1;
if (in_array($user_group, explode(',', $settings['comment_user_groups'])))
{
$assign['cancomment'] = 1;
}
if ($assign['no_read_permission'] == 0)
{
$assign['comment_max_chars'] = $settings['comment_max_chars'];
$assign['im'] = $settings['comment_use_antispam'];
$comments = array();
if ($settings['comment_use_page_nav'] == 1)
{
$limit = $settings['comment_page_nav_count'];
$start = get_current_page() * $limit - $limit;
$num = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "'")->GetCell();
$sql = $AVE_DB->Query("
SELECT *
FROM " . PREFIX . "_module_comment_info
WHERE document_id = '" . $document_id . "'
" . ($user_group == 1 ? '' : "AND comment_status = '1'") . "
ORDER BY comment_published ASC
LIMIT " . (int)$start . "," . (int)$limit . "
");
if ($num > $limit)
{
$page_nav = '{t} ';
$page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
$page_nav = preg_replace('/(?Query("SELECT * FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' " . ($user_group == 1 ? '' : "AND comment_status = '1'") . " ORDER BY comment_published ASC");
$page_nav = '';
}
$date_time_format = $AVE_Template->get_config_vars('COMMENT_DATE_TIME_FORMAT');
$now = time();
while ($row = $sql->FetchAssocArray())
{
if (isset($row['comment_author_id']) && $row['comment_author_id'] > 0)
{
$row['avatar'] = getAvatar($row['comment_author_id'], 48);
}
else
{
$row['avatar'] = '';
}
// --- ИСПРАВЛЕННАЯ ЛОГИКА ТАЙМЕРА И ПРАВ ---
$row['can_edit'] = 0;
$is_admin = ($user_group == 1);
// Рассчитываем остаток времени (в секундах)
$elapsed = $now - $row['comment_published'];
$time_left = $this->conf_edit_time - $elapsed;
// Обязательно записываем в массив, чтобы избежать ошибки в шаблоне
$row['edit_time_left'] = ($time_left > 0) ? $time_left : 0;
// Проверяем авторство (зарегистрированный или аноним по ключу)
$is_author = ($current_user_id > 0 && $current_user_id == $row['comment_author_id']) ||
($row['comment_author_id'] == 0 && !empty($row['anon_key']) && $row['anon_key'] == $anon_key);
if ($is_admin) {
$row['can_edit'] = 1;
} elseif ($is_author) {
// Автор может править, только если время еще есть
if ($row['edit_time_left'] > 0) {
$row['can_edit'] = 1;
}
}
$row['is_my_own'] = $is_author;
// ------------------------------------------
$row['comment_published_raw'] = $row['comment_published'];
$row['comment_published'] = ave_date_format($date_time_format, $row['comment_published']);
$row['comment_changed'] = ave_date_format($date_time_format, $row['comment_changed']);
$comments[$row['parent_id']][] = $row;
}
}
else
{
$comments = array();
$page_nav = '';
}
$assign['closed'] = @$comments[0][0]['comments_close'];
$assign['comments'] = $comments;
$assign['theme'] = defined('THEME_FOLDER') ? THEME_FOLDER : DEFAULT_THEME_FOLDER;
$assign['doc_id'] = $document_id;
$assign['page'] = base64_encode(get_redirect_link());
$assign['subtpl'] = $tpl_dir . $this->_comments_tree_sub_tpl;
$AVE_Template->assign('anon_key', $anon_key);
$AVE_Template->assign($settings);
$AVE_Template->assign($assign);
$AVE_Template->assign('page_nav', $page_nav);
$AVE_Template->display($tpl_dir . $this->_comments_tree_tpl);
}
}
/**
* Метод, предназначенный для отображения формы при добавлении нового комментария.
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentPostFormShow($tpl_dir)
{
global $AVE_DB, $AVE_Template;
// Используем оператор объединения с null для PHP 8.4
$docid = (int)($_REQUEST['docid'] ?? 0);
$user_group = UGROUP ?? 0;
// Получаем список комментариев на которые запрещены ответы
$geschlossen = $AVE_DB->Query("
SELECT comments_close
FROM " . PREFIX . "_module_comment_info
WHERE document_id = '" . $docid . "'
LIMIT 1
")->GetCell();
// Формируем ряд переменных для использования в шаблоне
$AVE_Template->assign('closed', $geschlossen);
$AVE_Template->assign('cancomment', ($this->_commentSettingsGet('comment_active') == 1 && in_array($user_group, explode(',', $this->_commentSettingsGet('comment_user_groups')))));
$AVE_Template->assign('comment_max_chars', $this->_commentSettingsGet('comment_max_chars'));
$AVE_Template->assign('theme', defined('THEME_FOLDER') ? THEME_FOLDER : DEFAULT_THEME_FOLDER);
// Отображаем форму для добавления комментария
$AVE_Template->display($tpl_dir . $this->_comment_form_tpl);
}
/**
* Метод, предназначенный для записи в БД нового комментария.
*
* @param string $tpl_dir - путь к шаблонам модуля
*
*/
function commentPostNew($tpl_dir)
{
global $AVE_DB, $AVE_Template;
$page = $_REQUEST['page'] ?? '';
$ajax = (isset($_REQUEST['ajax']) && $_REQUEST['ajax'] == 1);
$user_group = UGROUP ?? 0;
$secure_code = $_POST['securecode'] ?? '';
$session_captcha = $_SESSION['captcha_keystring'] ?? null;
$settings = $this->_commentSettingsGet();
// --- ПОЛУЧАЕМ КЛЮЧ АНОНИМА ---
$anon_key = $this->_getAnonKey();
if (! $ajax)
{
$link = rewrite_link(base64_decode($page));
}
// --- ПРОВЕРКА ОБЯЗАТЕЛЬНЫХ ПОЛЕЙ ---
if ($settings['comment_show_f1'] == 1 && $settings['comment_req_f1'] == 1 && empty($_POST['comment_author_website']))
{
if ($ajax) { echo 'error_req_f1'; exit; }
else { header('Location:' . $link . '#end'); exit; }
}
if ($settings['comment_show_f2'] == 1 && $settings['comment_req_f2'] == 1 && empty($_POST['comment_author_city']))
{
if ($ajax) { echo 'error_req_f2'; exit; }
else { header('Location:' . $link . '#end'); exit; }
}
// --- АНТИСПАМ ---
if ($settings['comment_use_antispam'] == 1)
{
if (! (isset($session_captcha) && $session_captcha == $secure_code))
{
unset($_SESSION['captcha_keystring']);
if ($ajax) { echo 'wrong_securecode'; }
else {
if(isset($GLOBALS['tmpl']))$GLOBALS['tmpl']->assign("wrongSecureCode", 1);
header('Location:' . $link . '#end');
}
exit;
}
unset($_SESSION['captcha_keystring']);
}
$comment_status = ($settings['comment_need_approve'] == 1) ? 0 : 1;
if ($settings['comment_active'] == 1
&& !empty($_POST['comment_text'])
&& !empty($_POST['comment_author_name'])
&& in_array($user_group, explode(',', $settings['comment_user_groups'])))
{
// --- ОБРАБОТКА ЗАГРУЗКИ ИЗОБРАЖЕНИЯ ---
$comment_file_name = '';
if ($settings['comment_allow_files'] == 1 && isset($_FILES['comment_image']) && $_FILES['comment_image']['error'] == UPLOAD_ERR_OK)
{
$upload_path = BASE_DIR . '/uploads/comments/';
// Создаем папку, если её нет
if (!is_dir($upload_path)) {
@mkdir($upload_path, 0775, true);
// Создаем пустой index.html для безопасности (запрет листинга папки)
@file_put_contents($upload_path . 'index.html', '');
}
$allowed_mime = ['image/jpeg', 'image/png', 'image/gif'];
$file_info = @getimagesize($_FILES['comment_image']['tmp_name']);
$file_size = $_FILES['comment_image']['size'];
// Проверка: это реально картинка и её размер не более 2Мб
if ($file_info && in_array($file_info['mime'], $allowed_mime) && $file_size <= 2 * 1024 * 1024)
{
$ext = pathinfo($_FILES['comment_image']['name'], PATHINFO_EXTENSION);
$comment_file_name = time() . '_' . rand(100, 999) . '.' . $ext;
if (!move_uploaded_file($_FILES['comment_image']['tmp_name'], $upload_path . $comment_file_name)) {
$comment_file_name = ''; // Сбрасываем, если не удалось переместить
}
}
}
// --- ПОДГОТОВКА ДАННЫХ ДЛЯ БД ---
// ПРОВЕРКА РОДИТЕЛЯ: Если родитель не найден в БД, сбрасываем в 0 (корень)
$parent_id = (int)($_POST['parent_id'] ?? 0);
if ($parent_id > 0) {
$parent_exists = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE Id = '" . $parent_id . "'")->GetCell();
if (!$parent_exists) $parent_id = 0;
}
$new_comment['parent_id'] = $parent_id;
$new_comment['document_id'] = (int)($_POST['doc_id'] ?? 0);
$new_comment['comment_author_name'] = addslashes(strip_tags($_POST['comment_author_name'] ?? ''));
$new_comment['comment_author_id'] = empty($_SESSION['user_id']) ? 0 : (int)$_SESSION['user_id'];
// ПРИВЯЗКА КЛЮЧА К КОММЕНТАРИЮ
$new_comment['anon_key'] = ($new_comment['comment_author_id'] == 0) ? $anon_key : '';
$new_comment['comment_author_email'] = addslashes(strip_tags($_POST['comment_author_email'] ?? ''));
$new_comment['comment_author_city'] = addslashes(strip_tags($_POST['comment_author_city'] ?? ''));
$new_comment['comment_author_website'] = addslashes(strip_tags($_POST['comment_author_website'] ?? ''));
$new_comment['comment_author_ip'] = $_SERVER['REMOTE_ADDR'];
$new_comment['comment_published'] = time();
$new_comment['comment_status'] = $comment_status;
$new_comment['comment_file'] = $comment_file_name;
$comment_text_raw = $_POST['comment_text'] ?? '';
$comment_max_chars = $settings['comment_max_chars'];
$comment_max_chars = (!empty($comment_max_chars) && $comment_max_chars > 10) ? $comment_max_chars : 200;
$comment_text_clean = strip_tags(stripslashes($comment_text_raw));
$comment_text_cut = mb_substr($comment_text_clean, 0, $comment_max_chars);
$comment_text_cut .= (mb_strlen($comment_text_clean) > $comment_max_chars) ? '…' : '';
$new_comment['comment_text'] = addslashes($comment_text_cut);
// Выполняем запрос
$AVE_DB->Query("
INSERT INTO " . PREFIX . "_module_comment_info
(`" . implode('`,`', array_keys($new_comment)) ."`)
VALUES
('" . implode("','", $new_comment) . "')
");
$new_comment['Id'] = $AVE_DB->InsertId();
// --- УВЕДОМЛЕНИЕ АДМИНА ---
$mail_from = get_settings('mail_from');
$mail_from_name = get_settings('mail_from_name');
$page_link = get_home_link() . urldecode(base64_decode($page)) . '&subaction=showonly&comment_id=' . $new_comment['Id'] . '#' . $new_comment['Id'];
$mail_text = $AVE_Template->get_config_vars('COMMENT_MESSAGE_ADMIN');
$mail_text = str_replace('%COMMENT%', stripslashes($new_comment['comment_text']), $mail_text);
$mail_text = str_replace('%N%', "\n", $mail_text);
$mail_text = str_replace('%PAGE%', $page_link, $mail_text);
$mail_text = str_replace('&', '&', $mail_text);
send_mail($mail_from, $mail_text, $AVE_Template->get_config_vars('COMMENT_SUBJECT_MAIL'), $mail_from, $mail_from_name, 'text');
if ($ajax)
{
$new_comment['avatar'] = (isset($new_comment['comment_author_id']) && $new_comment['comment_author_id'] > 0) ? getAvatar($new_comment['comment_author_id'], 48) : '';
$new_comment['comment_changed'] = 0;
// Передаем флаги для мгновенной активации управления в шаблоне
$new_comment['can_edit'] = 1;
$new_comment['edit_time_left'] = $this->conf_edit_time;
$new_comment['comment_published'] = ave_date_format($AVE_Template->get_config_vars('COMMENT_DATE_TIME_FORMAT'), $new_comment['comment_published']);
$new_comment['settings'] = $settings;
$subcomments[] = $new_comment;
$AVE_Template->assign('subcomments', $subcomments);
$AVE_Template->assign('theme', defined('THEME_FOLDER') ? THEME_FOLDER : DEFAULT_THEME_FOLDER);
$AVE_Template->display($tpl_dir . $this->_comments_tree_sub_tpl);
}
}
if (! $ajax) header('Location:' . str_replace("//", "", $link) . '#end');
exit;
}
/**
* Метод, предназначенный для редактирования комментария в Публичной части
* с поддержкой анонимных пользователей и ограничением по времени.
*/
function commentPostEdit($comment_id)
{
global $AVE_DB;
// 1. Инициализация данных
$comment_id = (int)$comment_id;
$user_id = (int)($_SESSION['user_id'] ?? 0);
$user_group = (int)(defined('UGROUP') ? UGROUP : 0);
$anon_key = $this->_getAnonKey(); // Наш метод получения куки анонима
if ($comment_id <= 0 || $user_group <= 0) exit('INVALID_ID');
// 2. Получаем данные комментария и настройки модуля (JOIN)
// Нам нужно знать время создания и кто автор, а также лимиты из настроек
$row = $AVE_DB->Query("
SELECT
msg.*,
cmnt.comment_max_chars,
cmnt.comment_need_approve,
cmnt.comment_user_groups
FROM " . PREFIX . "_module_comment_info AS msg
JOIN " . PREFIX . "_module_comments AS cmnt ON cmnt.Id = 1
WHERE msg.Id = '" . $comment_id . "'
LIMIT 1
")->FetchAssocArray();
if (!$row) exit('NOT_FOUND');
// 3. Проверка прав (БЕЗОПАСНОСТЬ)
$is_admin = ($user_group == 1);
// Проверка на авторство:
// Либо совпадает ID зарегистрированного юзера,
// Либо совпадает anon_key в базе с кукой текущего анонима
$is_author = ($user_id > 0 && $user_id == $row['comment_author_id']) ||
($row['comment_author_id'] == 0 && !empty($row['anon_key']) && $row['anon_key'] == $anon_key);
// Проверка времени: прошло меньше секунд, чем указано в $this->conf_edit_time
$time_passed = time() - (int)$row['comment_published'];
$is_time_ok = ($time_passed < $this->conf_edit_time);
// Если не админ, проверяем: автор ли это и не вышло ли время
if (!$is_admin) {
if (!$is_author) exit('NOT_AUTHOR');
if (!$is_time_ok) {
echo "TIME_EXPIRED"; // Этот ответ поймает JS
exit;
}
}
// 4. Обработка текста (ТВОИ ОРИГИНАЛЬНЫЕ РЕГУЛЯРКИ)
$comment_text = $_POST['text'] ?? '';
// Декодируем HEX и десятичные сущности (твоя логика)
$comment_text = preg_replace_callback('/([0-9a-f]{1,7});/i', function($matches) { return chr(hexdec($matches[1])); }, $comment_text);
$comment_text = preg_replace_callback('/([0-9]{1,7});/', function($matches) { return chr($matches[1]); }, $comment_text);
$comment_text = stripslashes($comment_text);
// Заменяем брейки на переносы строк для корректной работы mb_substr и strip_tags
$comment_text = str_replace(array("
\n", "
\n", "
\n", "
", "
"), "\n", $comment_text);
$comment_text = strip_tags($comment_text);
$max = ($row['comment_max_chars'] > 10) ? (int)$row['comment_max_chars'] : 1000;
$comment_text_cut = mb_substr($comment_text, 0, $max);
if (mb_strlen($comment_text) > $max) $comment_text_cut .= '…';
// 5. Работа с изображениями
$new_file_sql = "";
$upload_dir = BASE_DIR . '/uploads/comments/';
// Вариант А: Загрузка нового файла взамен старого
if (isset($_FILES['comment_image']) && $_FILES['comment_image']['error'] == UPLOAD_ERR_OK) {
if (!is_dir($upload_dir)) @mkdir($upload_dir, 0775, true);
// Удаляем старый файл физически
if (!empty($row['comment_file']) && file_exists($upload_dir . $row['comment_file'])) {
@unlink($upload_dir . $row['comment_file']);
}
$file_ext = strtolower(pathinfo($_FILES['comment_image']['name'], PATHINFO_EXTENSION));
$new_file_name = 'comm_' . time() . '_' . rand(100, 999) . '.' . $file_ext;
if (move_uploaded_file($_FILES['comment_image']['tmp_name'], $upload_dir . $new_file_name)) {
$new_file_sql = ", comment_file = '" . addslashes($new_file_name) . "'";
}
}
// Вариант Б: Просто удаление изображения (если стоит галочка в форме)
elseif (isset($_POST['delete_image']) && $_POST['delete_image'] == 1) {
if (!empty($row['comment_file']) && file_exists($upload_dir . $row['comment_file'])) {
@unlink($upload_dir . $row['comment_file']);
}
$new_file_sql = ", comment_file = ''";
}
// 6. Обновление базы данных
// Если в настройках стоит "нужна премодерация", после правки статус меняется на 0 (если не админ)
$new_status = $is_admin ? (int)$row['comment_status'] : ($row['comment_need_approve'] == '1' ? 0 : 1);
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET
comment_changed = '" . time() . "',
comment_text = '" . addslashes($comment_text_cut) . "',
comment_status = '" . $new_status . "'
$new_file_sql
WHERE
Id = '" . $comment_id . "'
");
// Возвращаем текст для JS, чтобы он обновил блок без перезагрузки страницы
echo htmlspecialchars($comment_text_cut, ENT_QUOTES);
exit;
}
/**
* Метод, предназначенный для удаления комментария.
* Реализует логику мягкого удаления (затирки) при наличии ответов
* и полную очистку при их отсутствии.
*
* @param int $comment_id - идентификатор комментария
*/
function commentPostDelete($comment_id)
{
global $AVE_DB;
if (ob_get_level()) ob_end_clean();
$comment_id = (int)$comment_id;
if ($comment_id <= 0) die('Ошибка: Неверный ID');
// --- ШАГ 0. ПОЛУЧЕНИЕ ДАННЫХ И ПРОВЕРКА ПРАВ ---
$comment_data = $AVE_DB->Query("
SELECT comment_author_id, anon_key, comment_published, comment_file
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
")->FetchAssocArray();
if (!$comment_data) die('Ошибка: Комментарий не найден');
$current_user_id = (int)($_SESSION['user_id'] ?? 0);
$user_group = (int)($_SESSION['user_group'] ?? (defined('UGROUP') ? UGROUP : 0));
$anon_key = $this->_getAnonKey();
$can_delete = false;
if ($user_group === 1) {
$can_delete = true;
} else {
$is_author = ($current_user_id > 0 && $current_user_id == $comment_data['comment_author_id']) ||
($comment_data['comment_author_id'] == 0 && !empty($comment_data['anon_key']) && $comment_data['anon_key'] == $anon_key);
$is_time_ok = (time() - (int)$comment_data['comment_published'] < $this->conf_edit_time);
if ($is_author && $is_time_ok) {
$can_delete = true;
}
}
if (!$can_delete) {
header('HTTP/1.1 403 Forbidden');
die('Доступ запрещен');
}
// --- ШАГ 1. ПРОВЕРКА НАЛИЧИЯ ОТВЕТОВ ---
$has_children = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE parent_id = '" . $comment_id . "'")->GetCell();
$upload_dir = BASE_DIR . '/uploads/comments/';
// Мягкое удаление ТОЛЬКО для авторов, если есть ответы
if ($has_children > 0 && $user_group != 1) {
if (!empty($comment_data['comment_file'])) {
if (file_exists($upload_dir . $comment_data['comment_file'])) @unlink($upload_dir . $comment_data['comment_file']);
}
$del_text = "";
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET comment_text = '" . addslashes($del_text) . "', comment_file = '', anon_key = '', comment_changed = '" . time() . "'
WHERE Id = '" . $comment_id . "'
");
echo "OK_SOFT";
}
else {
// --- ВАРИАНТ Б: ПОЛНОЕ УДАЛЕНИЕ (Для админа или если нет детей) ---
// Собираем массив ID для удаления. Сразу кладем туда родителя.
$ids_to_delete = [$comment_id];
// Если удаляет Админ, ищем ВСЮ ветку (любой вложенности)
if ($user_group == 1) {
$all_child_ids = [];
$parent_ids = [$comment_id];
// Рекурсивный поиск всех уровней вложенности
while (!empty($parent_ids)) {
$ids_string = implode(',', array_map('intval', $parent_ids));
$res = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE parent_id IN ($ids_string)");
$parent_ids = []; // Сбрасываем для следующего уровня
while ($row = $res->FetchAssocArray()) {
$all_child_ids[] = (int)$row['Id'];
$parent_ids[] = (int)$row['Id'];
}
}
$ids_to_delete = array_merge($ids_to_delete, $all_child_ids);
}
// 1. Удаляем файлы всех комментариев из списка удаления
$final_ids_str = implode(',', $ids_to_delete);
$files_res = $AVE_DB->Query("SELECT comment_file FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)");
while ($f = $files_res->FetchAssocArray()) {
if (!empty($f['comment_file'])) {
$f_path = $upload_dir . $f['comment_file'];
if (file_exists($f_path)) @unlink($f_path);
}
}
// 2. Удаляем все записи из БД одним запросом
$AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)");
echo "OK";
}
die();
}
/**
* Метод для обработки голосования за комментарий
*/
function commentVote()
{
global $AVE_DB;
$comment_id = (int)($_POST['comment_id'] ?? 0);
$vote_value = (int)($_POST['vote'] ?? 0);
$ajax = (isset($_POST['ajax']) && $_POST['ajax'] == 1);
// Базовая проверка значения (от 1 до 5 звезд)
if ($comment_id <= 0 || $vote_value < 1 || $vote_value > 5) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'error';
exit;
}
return;
}
// 1. Идентификация пользователя
$user_id = empty($_SESSION['user_id']) ? 0 : (int)$_SESSION['user_id'];
$anon_key = $this->_getAnonKey();
// 2. Получаем данные о комментарии (FetchRow исправлен)
$comment_data = $AVE_DB->Query("
SELECT comment_author_id, anon_key
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
")->FetchRow();
if (!$comment_data) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'error';
exit;
}
return;
}
// 3. Запрет голосовать за свой же комментарий
$is_author = false;
if ($user_id > 0 && $user_id == $comment_data->comment_author_id) $is_author = true;
if ($user_id == 0 && $anon_key == $comment_data->anon_key) $is_author = true;
if ($is_author) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'own_comment';
exit;
}
return;
}
// 4. Проверка на повторное голосование
$sql_check = "SELECT id FROM " . PREFIX . "_module_comment_votes WHERE comment_id = '" . $comment_id . "' AND ";
if ($user_id > 0) {
$sql_check .= "user_id = '" . $user_id . "'";
} else {
$sql_check .= "anon_key = '" . $anon_key . "'";
}
if ($AVE_DB->Query($sql_check)->GetCell()) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'already_voted';
exit;
}
return;
}
// 5. Записываем голос в лог
$AVE_DB->Query("
INSERT INTO " . PREFIX . "_module_comment_votes
(comment_id, user_id, anon_key, vote_value, date_voted)
VALUES
('" . $comment_id . "', '" . $user_id . "', '" . $anon_key . "', '" . $vote_value . "', '" . time() . "')
");
// 6. Обновляем агрегированные данные
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET rating_sum = rating_sum + " . $vote_value . ",
rating_count = rating_count + 1
WHERE Id = '" . $comment_id . "'
");
// 7. ФИНАЛ: Очищаем мусор и отдаем чистый ответ
if ($ajax) {
if (ob_get_length()) ob_end_clean(); // Выбрасываем все Warning-и и HTML
echo 'success';
exit;
}
}
function commentAdminDelete($comment_id)
{
global $AVE_DB;
$comment_id = (int)$comment_id; // Убедимся, что это целое число
// Выполняем запрос к БД на удаление родительского комментария
$AVE_DB->Query("
DELETE
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
");
// Выполняем запрос к БД на удаление дочерних комментариев (ответов)
$AVE_DB->Query("
DELETE
FROM " . PREFIX . "_module_comment_info
WHERE parent_id = '" . $comment_id . "'
AND parent_id != 0
");
// Используем оператор объединения с null для PHP 8.4
$session_id = SESSION ?? '';
header('Location:index.php?do=modules&action=modedit&mod=comment&moduleaction=1&cp=' . $session_id);
exit;
}
/**
* Метод, предназначенный для вывода детальной информации об авторе комментария
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentPostInfoShow($tpl_dir)
{
global $AVE_DB, $AVE_Template;
// Используем оператор объединения с null для PHP 8.4
$comment_id = (int)($_REQUEST['Id'] ?? 0);
// Получаем полную информацию о комментарии
$row = $AVE_DB->Query("
SELECT *
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
")->FetchAssocArray();
// Преобразуем адрес сайта к формату ссылки
$row['comment_author_website'] = str_replace('http://', '', $row['comment_author_website']);
$row['comment_author_website'] = ($row['comment_author_website'] != '')
? '' . $row['comment_author_website'] .''
: '';
// Выполняем запрос к БД на получение количества всех комментариев, оставленных данным пользователем
$row['num'] = $AVE_DB->Query("
SELECT COUNT(*)
FROM " . PREFIX . "_module_comment_info
WHERE comment_author_id = '" . $row['comment_author_id'] . "'
AND comment_author_id != 0
")->GetCell();
// Отображаем окно с информацией
$AVE_Template->assign('c', $row);
$AVE_Template->display($tpl_dir . $this->_postinfo_tpl);
}
/**
* Метод, предназначенный для управления запретом или разрешением отвечать на комментарии
*
* @param int $comment_id - идентификатор комментария
* @param string $comment_status - {lock|unlock} признак запрета/разрешения
*/
function commentReplyStatusSet($comment_id, $comment_status = 'lock')
{
global $AVE_DB;
$comment_id = (int)$comment_id;
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET comment_status = '" . (($comment_status == 'lock') ? 0 : 1) . "'
WHERE Id = '" . $comment_id . "'
");
exit;
}
/**
* Метод, предназначенный для управления запретом или разрешением комментировать документ
*
* @param int $document_id - идентификатор документа
* @param string $comment_status - {close|open} признак запрета/разрешения
*/
function commentStatusSet($document_id, $comment_status = 'open')
{
global $AVE_DB;
$document_id = (int)$document_id;
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET comments_close = '" . (($comment_status == 'open') ? 0 : 1) . "'
WHERE document_id = '" . $document_id . "'
");
exit;
}
/**
* Следующие методы описывают работу модуля в Административной части сайта.
*/
/**
* Метод, предназначенный для вывода списка всех комментариев в Административной части.
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentAdminListShow($tpl_dir)
{
global $AVE_DB, $AVE_Template;
// Используем оператор объединения с null для PHP 8.4
$request_sort = $_REQUEST['sort'] ?? '';
$session_id = SESSION ?? '';
// Получаем общее количество комментариев
$num = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info")->GetCell();
// Определяем количество страниц, учитывая параметр _limit, который опроеделяет количество
// комментариев отображаемых на одной странице
@$seiten = @ceil($num / $this->_limit);
$start = get_current_page() * $this->_limit - $this->_limit;
$docs = array();
$def_sort = 'ORDER BY doc.Id DESC';
$def_nav = '';
// Определяем условия сортировки комментариев
if (!empty($request_sort))
{
switch ($request_sort)
{
case 'document_desc':
$def_sort = 'ORDER BY doc.Id ASC'; // Предполагаю, что document_desc должен быть ASC
$def_nav = '&sort=document_desc';
break;
case 'document':
$def_sort = 'ORDER BY doc.Id DESC';
$def_nav = '&sort=document';
break;
case 'comment_desc':
$def_sort = 'ORDER BY cmnt.comment_text ASC';
$def_nav = '&sort=comment_desc';
break;
case 'comment':
$def_sort = 'ORDER BY cmnt.comment_text DESC';
$def_nav = '&sort=comment';
break;
case 'created_desc':
$def_sort = 'ORDER BY cmnt.comment_published ASC';
$def_nav = '&sort=created_desc';
break;
case 'created':
$def_sort = 'ORDER BY cmnt.comment_published DESC';
$def_nav = '&sort=created';
break;
}
}
// Выполняем запрос к БД на получение комметариев с учетом параметров сортировки и лимита.
$sql = $AVE_DB->Query("
SELECT
doc.Id,
doc.document_title,
cmnt.Id AS CId,
cmnt.document_id,
cmnt.comment_text,
cmnt.comment_published,
cmnt.comment_status
FROM
" . PREFIX . "_module_comment_info AS cmnt
JOIN
" . PREFIX . "_documents AS doc
ON doc.Id = cmnt.document_id
" . $def_sort . "
LIMIT " . $start . "," . $this->_limit
);
while ($row = $sql->FetchAssocArray())
{
$row['Comments'] = $this->_commentPostCountGet($row['Id']);
array_push($docs, $row);
}
// Если количество комментариев полученных из БД превышает допустимое на странице, тогда формируем
// меню постраницной навигации
if ($num > $this->_limit)
{
$page_nav = ' {t} ';
$page_nav = get_pagination($seiten, 'page', $page_nav);
$AVE_Template->assign('page_nav', $page_nav);
}
// Передаем данные в шаблон для вывода и отображаем шаблон
$AVE_Template->assign('docs', $docs);
$AVE_Template->assign('content', $AVE_Template->fetch($tpl_dir . $this->_admin_comments_tpl));
}
/**
* Метод, предназначенный для редактирования комментариев в Административной части.
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentAdminPostEdit($tpl_dir)
{
global $AVE_DB, $AVE_Template;
// Используем оператор объединения с null для PHP 8.4
$post_sub = $_POST['sub'] ?? '';
$request_id = (int)($_REQUEST['Id'] ?? 0);
$request_docid = (int)($_REQUEST['docid'] ?? 0);
// Выполняем запрос к БД на получение информации о редактируемом комментарии
$row = $AVE_DB->Query("
SELECT *
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $request_id . "'
LIMIT 1
")->FetchAssocArray();
// Если в запросе содержится подзапрос на сохранение данных (пользователь уже отредактировал комментарий
// и нажал кнопку сохранить изменения), тогда выполняем запрос к БД на обновление информации.
if ($post_sub == 'send' && false != $row)
{
// --- !!! БЕЗОПАСНОСТЬ: Экранирование данных перед сохранением !!! ---
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET
comment_author_name = '" . addslashes(htmlspecialchars($_POST['comment_author_name'] ?? '')) . "',
comment_author_email = '" . addslashes(htmlspecialchars($_POST['comment_author_email'] ?? '')) . "',
comment_author_city = '" . addslashes(htmlspecialchars($_POST['comment_author_city'] ?? '')) . "',
comment_author_website = '" . addslashes(htmlspecialchars($_POST['comment_author_website'] ?? '')) . "',
comment_text = '" . addslashes(htmlspecialchars($_POST['comment_text'] ?? '')) . "',
comment_changed = '" . time() . "'
WHERE
Id = '" . (int)($_POST['Id'] ?? 0) . "'
");
echo '';
return;
}
// Если в первой выборке из БД мы получили нулевой результат, тогда генерируем сообщение с ошибкой
if ($row == false)
{
$AVE_Template->assign('editfalse', 1);
}
// в противном случае получаем список комментариев, у которых стоит запрет на ответы
else
{
$closed = $AVE_DB->Query("
SELECT comments_close
FROM " . PREFIX . "_module_comment_info
WHERE document_id = '" . $request_docid . "'
LIMIT 1
")->GetCell();
$AVE_Template->assign('closed', $closed);
$AVE_Template->assign('row', $row);
$AVE_Template->assign('comment_max_chars', $this->_commentSettingsGet('comment_max_chars'));
}
// Отображаем шаблон
$AVE_Template->assign('content', $AVE_Template->fetch($tpl_dir . $this->_admin_edit_link_tpl));
}
/**
/**
/**
* Метод, предназначенный для управления настройками модуля
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentAdminSettingsEdit($tpl_dir)
{
global $AVE_DB, $AVE_Template;
$request_sub = $_REQUEST['sub'] ?? '';
$post_max_chars = $_POST['comment_max_chars'] ?? 0;
$post_user_groups = $_POST['comment_user_groups'] ?? array();
$post_user_groups_read = $_POST['comment_user_groups_read'] ?? array();
$post_need_approve = $_POST['comment_need_approve'] ?? 0;
$post_active = $_POST['comment_active'] ?? 0;
$post_use_antispam = $_POST['comment_use_antispam'] ?? 0;
$post_use_page_nav = $_POST['comment_use_page_nav'] ?? 0;
$post_page_nav_count = $_POST['comment_page_nav_count'] ?? 0;
// НОВОЕ: Настройка разрешения загрузки файлов
$post_allow_files = $_POST['comment_allow_files'] ?? 0;
// Данные для универсальных полей
$post_show_f1 = $_POST['comment_show_f1'] ?? 0;
$post_req_f1 = $_POST['comment_req_f1'] ?? 0;
$post_name_f1 = $_POST['comment_name_f1'] ?? '';
$post_show_f2 = $_POST['comment_show_f2'] ?? 0;
$post_req_f2 = $_POST['comment_req_f2'] ?? 0;
$post_name_f2 = $_POST['comment_name_f2'] ?? '';
if ($request_sub == 'save')
{
$max_chars = (empty($post_max_chars) || $post_max_chars < 50) ? 50 : $post_max_chars;
$clean_name_f1 = htmlspecialchars(stripslashes($post_name_f1), ENT_QUOTES);
$clean_name_f2 = htmlspecialchars(stripslashes($post_name_f2), ENT_QUOTES);
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comments
SET
comment_max_chars = '" . (int)$max_chars . "',
comment_user_groups = '" . implode(',', $post_user_groups) . "',
comment_user_groups_read = '" . implode(',', $post_user_groups_read) . "',
comment_need_approve = '" . (int)$post_need_approve . "',
comment_active = '" . (int)$post_active . "',
comment_use_antispam = '" . (int)$post_use_antispam . "',
comment_use_page_nav = '" . (int)$post_use_page_nav . "',
comment_page_nav_count = '" . (int)$post_page_nav_count . "',
comment_allow_files = '" . (int)$post_allow_files . "',
comment_show_f1 = '" . (int)$post_show_f1 . "',
comment_req_f1 = '" . (int)$post_req_f1 . "',
comment_name_f1 = '" . $clean_name_f1 . "',
comment_show_f2 = '" . (int)$post_show_f2 . "',
comment_req_f2 = '" . (int)$post_req_f2 . "',
comment_name_f2 = '" . $clean_name_f2 . "'
WHERE
Id = 1
");
}
$row = $this->_commentSettingsGet();
$row['comment_user_groups'] = explode(',', $row['comment_user_groups']);
$row['comment_user_groups_read'] = explode(',', $row['comment_user_groups_read']);
$AVE_Template->assign($row);
$AVE_Template->assign('content', $AVE_Template->fetch($tpl_dir . $this->_admin_settings_tpl));
}
}
?>