Files
comment/class/comment.php

2164 lines
91 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Класс, включающий все свойства и методы для управления комментариями как в
* Публичной части сайта, так и в Панели управления.
*
* @package AVE4cms
* @author Александр Сальников (Repellent)
* @copyright 2026
* @subpackage module_Comment
* @since 3.33
* @filesource
*/
class Comment
{
/**
* Свойства класса
*/
/**
* Идентификатор записи с настройками модуля Комментарии
*
* @var int
*/
private $_config_id = 1;
/**
* Количество комментариев на странице в административной части
*
* @var int
*/
private $_limit = 15;
/**
* Имя файла с шаблоном для вывода блока комментариев
*
* @var string
*/
public $_comments_tree_tpl = 'comments_tree.tpl';
/**
* Имя файла с шаблоном для рекурсивного вывода иерархии комментариев
*
* @var string
*/
public $_comments_tree_sub_tpl = 'comments_tree_sub.tpl';
/**
* Имя файла с шаблоном формы добавления комментария
*
* @var string
*/
public $_comment_form_tpl = 'comment_form.tpl';
/**
* Имя файла с шаблоном нового комментария
*
* @var string
*/
public $_comment_new_tpl = 'comment_new.tpl';
/**
* Имя файлаа с шаблоном уведомляющим о успешном выполнении операции
*
* @var string
*/
public $_comment_thankyou_tpl = 'comment_thankyou.tpl';
/**
* Имя файла с шаблоном редактирования шаблона в административной части
*
* @var string
*/
public $_admin_edit_link_tpl = 'admin_edit.tpl';
/**
* Имя файла с шаблоном списка комментариев в административной части
*
* @var string
*/
public $_admin_comments_tpl = 'admin_comments.tpl';
/**
* Имя файла с шаблоном редактирования настроек модуля в административной части
*
* @var string
*/
public $_admin_settings_tpl = 'admin_settings.tpl';
/**
* Имя файла с шаблоном редактирования шаблона в публичной части
*
* @var string
*/
public $_edit_link_tpl = 'comment_edit.tpl';
/**
* Имя файла с шаблоном для вывода информации об авторе комментария
*
* @var string
*/
public $_postinfo_tpl = 'comment_info.tpl';
/**
* Настройки времени (подгружаются из БД)
*/
public $conf_edit_time;
public $conf_cookie_life;
private $_anon_cookie_name = 'ave_anon_comment_key';
/**
* Редактирование Авторской оценки (1-разрешено, 0-запрещено)
*/
private $_edit_avtor_rating = 0;
/**
* Конструктор класса
*
*/
function __construct()
{
// Как только класс создан идем в базу за настройками
$this->_commentSettingsGet();
}
/**
* Внутренние методы класса
*/
/**
* Получение или создание уникального ключа для анонимного пользователя
*/
private function _getAnonKey()
{
if (isset($_COOKIE[$this->_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 $anon_key уникальный ключ
* @param string $exclude_name исключить это имя из результата (текущее)
* @return array
*/
private function _getAnonNamesHistory($anon_key, $exclude_name = '')
{
global $AVE_DB;
$names = [];
$exclude_sql = !empty($exclude_name) ? " AND comment_author_name != '" . addslashes($exclude_name) . "'" : "";
$sql = $AVE_DB->Query("
SELECT DISTINCT comment_author_name
FROM " . PREFIX . "_module_comment_info
WHERE anon_key = '" . addslashes($anon_key) . "'
" . $exclude_sql . "
ORDER BY Id DESC
");
while ($row = $sql->FetchAssocArray()) {
$names[] = stripslashes($row['comment_author_name']);
}
return $names;
}
function _commentSettingsGet($param = '')
{
global $AVE_DB;
static $settings = null;
if ($settings === null)
{
$settings = $AVE_DB->Query("
SELECT *
FROM " . PREFIX . "_module_comments
WHERE Id = '" . $this->_config_id . "'
")->FetchAssocArray();
// ОБНОВЛЯЕМ ПЕРЕМЕННЫЕ КЛАССА ЗНАЧЕНИЯМИ ИЗ БАЗЫ
if ($settings) {
// Если в базе есть настройка, перезаписываем стандартные 60 секунд
if (isset($settings['comment_edit_time'])) {
$this->conf_edit_time = (int)$settings['comment_edit_time'];
}
// Если в базе есть настройка куки, перезаписываем стандартные 30 дней
if (isset($settings['comment_cookie_life'])) {
$this->conf_cookie_life = (int)$settings['comment_cookie_life'];
}
}
}
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 = (int)(UGROUP ?? 0);
// 1. Подключаем CSS
$GLOBALS['user_header']['comment_css'] = '<link rel="stylesheet" href="' . ABS_PATH . 'modules/comment/css/mod_comment_styles.css" type="text/css" />';
// --- ОПРЕДЕЛЯЕМ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ ---
$current_user_id = (int)($_SESSION['user_id'] ?? 0);
$anon_key = $this->_getAnonKey();
// --- УСЛОВИЕ ВИДИМОСТИ (Свои неодобренные + Все одобренные) ---
if ($user_group == 1) {
$where_visibility = ""; // Админ видит всё
} else {
$cond = "comment_status = '1'";
if ($current_user_id > 0) {
$cond .= " OR (comment_status = '0' AND comment_author_id = '$current_user_id')";
}
if (!empty($anon_key)) {
$cond .= " OR (comment_status = '0' AND anon_key = '$anon_key')";
}
$where_visibility = "AND (" . $cond . ")";
}
// Получаем ВСЕ настройки модуля разом (из БД)
$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;
// Чтобы Smarty не ругался, создаем пустую заглушку
$assign['saved_anon'] = ['name' => '', 'email' => '', 'exists' => false];
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'];
$assign['comment_allowed_extensions'] = $settings['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif,webp';
$assign['comment_max_file_size'] = $settings['comment_max_file_size'] ?? 2048;
$assign['comment_max_files'] = (int)($settings['comment_max_files'] ?? 5);
$assign['ajax_replies_limit'] = (int)($settings['comment_ajax_replies_limit'] ?? 0);
$assign['comment_allow_self_answer'] = (int)($settings['comment_allow_self_answer'] ?? 0);
$comments = array();
// --- ВЫБОРКА ИЗ БД ---
// Логика формирования SQL-сортировки
$sort_setting = $settings['comment_sort_order'] ?? 'ASC';
switch ($sort_setting) {
case 'USER_RATING':
// Сначала самые высокие оценки автора (5 звезд), затем дата
$sql_sort = "user_rating DESC, comment_published DESC";
break;
case 'RATING':
// Сначала самые залайканные пользователями, затем дата
$sql_sort = "rating_sum DESC, comment_published DESC";
break;
case 'DESC':
$sql_sort = "comment_published DESC";
break;
case 'ASC':
default:
$sql_sort = "comment_published ASC";
break;
}
if ($settings['comment_use_page_nav'] == 1)
{
$limit = (int)$settings['comment_page_nav_count'];
if ($limit <= 0)
{
$sql = $AVE_DB->Query("SELECT * FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' " . $where_visibility . " ORDER BY parent_id ASC, " . $sql_sort);
$page_nav = '';
}
else
{
$start = get_current_page() * $limit - $limit;
// Считаем только РОДИТЕЛЕЙ для пагинации
$num = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' AND parent_id = '0' " . $where_visibility)->GetCell();
// Основной запрос (сортировка применяется к родителям в подзапросе)
$sql = $AVE_DB->Query("
SELECT * FROM " . PREFIX . "_module_comment_info
WHERE document_id = '" . $document_id . "'
AND (
id IN (SELECT id FROM (SELECT id FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' AND parent_id = '0' " . $where_visibility . " ORDER BY " . $sql_sort . " LIMIT " . (int)$start . "," . (int)$limit . ") as tmp)
OR
parent_id != '0'
)
" . $where_visibility . "
ORDER BY comment_published ASC
");
if ($num > $limit)
{
$page_nav = '<a class="page_nav" href="index.php?id=' . $AVE_Core->curentdoc->Id
. '&amp;doc=' . (empty($AVE_Core->curentdoc->document_alias) ? prepare_url($AVE_Core->curentdoc->document_title) : $AVE_Core->curentdoc->document_alias)
. ((isset($artpage) && is_numeric($artpage)) ? '&amp;artpage=' . $artpage : '')
. ((isset($apage) && is_numeric($apage)) ? '&amp;apage=' . $apage : '')
. '&amp;page={s}">{t}</a> ';
$total_pages = ceil($num / $limit);
$page_nav = get_pagination($total_pages, 'page', $page_nav, get_settings('navi_box'));
$page_nav = str_ireplace('"//"', '"/"', str_ireplace('///', '/', rewrite_link($page_nav)));
$page_nav = preg_replace('/(?<!:)\/\//', '/', $page_nav);
$GLOBALS['page_id'][$document_id]['page'] = ($GLOBALS['page_id'][$document_id]['page'] > $total_pages ? $GLOBALS['page_id'][$document_id]['page'] : $total_pages);
}
else { $page_nav = ''; }
}
}
else
{
// Если навигация отключена
$sql = $AVE_DB->Query("SELECT * FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' " . $where_visibility . " ORDER BY parent_id ASC, " . $sql_sort);
$page_nav = '';
}
$date_time_format = $AVE_Template->get_config_vars('COMMENT_DATE_TIME_FORMAT');
$now = time();
$conf_limit = (int)($settings['comment_edit_time'] ?? 0);
while ($row = $sql->FetchAssocArray())
{
if ($assign['saved_anon']['exists'] == false && !empty($row['anon_key']) && $row['anon_key'] == $anon_key) {
$assign['saved_anon'] = [
'name' => stripslashes($row['comment_author_name']),
'email' => stripslashes($row['comment_author_email']),
'exists' => true
];
}
// Аватар
if (isset($row['comment_author_id']) && $row['comment_author_id'] > 0) {
$row['avatar'] = getAvatar($row['comment_author_id'], 48);
} else {
$row['avatar'] = '';
}
if (!empty($row['avatar']) && strpos($row['avatar'], 'user.png') !== false) {
$row['avatar'] = '';
}
if (empty($row['avatar'])) {
$name = !empty($row['comment_author_name']) ? stripslashes($row['comment_author_name']) : 'Guest';
$row['first_letter'] = mb_substr(trim($name), 0, 1, 'UTF-8');
$row['avatar_color_index'] = (abs(crc32($name)) % 12) + 1;
}
// --- ТАЙМЕР И ПРАВА ---
$row['can_edit'] = 0;
$is_admin = ($user_group === 1);
$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;
$row['edit_time_left'] = 0;
} else {
$elapsed = $now - (int)$row['comment_published'];
$time_left = $conf_limit - $elapsed;
$row['edit_time_left'] = ($time_left > 0) ? $time_left : 0;
if ($is_author && $row['edit_time_left'] > 0) {
$row['can_edit'] = 1;
}
}
$row['is_my_own'] = $is_author;
// История имен
$row['past_names'] = [];
if (!empty($row['anon_key'])) {
$row['past_names'] = $this->_getAnonNamesHistory($row['anon_key'], $row['comment_author_name']);
}
$row['comment_published_raw'] = $row['comment_published'];
$row['comment_published'] = ave_date_format($date_time_format, $row['comment_published']);
if ($row['comment_changed'] > 0) {
$row['comment_changed'] = ave_date_format($date_time_format, $row['comment_changed']);
} else {
$row['comment_changed'] = 0;
}
$comments[$row['parent_id']][] = $row;
}
}
else
{
$comments = array();
$page_nav = '';
}
// --- AJAX ЛИМИТЫ ---
$assign['more_counts'] = [];
$requested_branch = (int)($_REQUEST['ajax_load_branch'] ?? 0);
if ($assign['ajax_replies_limit'] > 0 && !empty($comments)) {
foreach ($comments as $parentId => $subList) {
if ($parentId > 0 && count($subList) > $assign['ajax_replies_limit']) {
if ($requested_branch == $parentId) {
continue;
}
$assign['more_counts'][$parentId] = count($subList) - $assign['ajax_replies_limit'];
$comments[$parentId] = array_slice($subList, 0, $assign['ajax_replies_limit']);
}
}
}
$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;
$docid = (int)($_REQUEST['docid'] ?? 0);
$user_group = UGROUP ?? 0;
// --- Автоподстановка для анонима ---
$anon_data = ['name' => '', 'email' => '', 'exists' => false];
if ($user_group == 2) { // Если гость
$anon_key = $this->_getAnonKey();
$last_post = $AVE_DB->Query("
SELECT comment_author_name, comment_author_email
FROM " . PREFIX . "_module_comment_info
WHERE anon_key = '" . $anon_key . "'
ORDER BY Id DESC LIMIT 1
")->FetchAssocArray();
if ($last_post) {
$anon_data['name'] = stripslashes($last_post['comment_author_name']);
$anon_data['email'] = stripslashes($last_post['comment_author_email']);
$anon_data['exists'] = true;
}
}
$AVE_Template->assign('saved_anon', $anon_data);
$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'])))
{
// --- ОБРАБОТКА ЗАГРУЗКИ ИЗОБРАЖЕНИЯ (С ЗАЩИТОЙ ОТ ДУБЛЕЙ) ---
$uploaded_files = [];
if ($settings['comment_allow_files'] == 1 && isset($_FILES['comment_image']))
{
// --- ПРОВЕРКА ЛИМИТА КОЛИЧЕСТВА ---
$max_files_limit = (int)($settings['comment_max_files'] ?? 5);
$total_incoming = 0;
if (is_array($_FILES['comment_image']['name'])) {
foreach($_FILES['comment_image']['name'] as $fname) if(!empty($fname)) $total_incoming++;
} elseif(!empty($_FILES['comment_image']['name'])) {
$total_incoming = 1;
}
if ($total_incoming > $max_files_limit) {
if ($ajax) { echo 'error_max_files'; exit; }
else { header('Location:' . $link . '#end'); exit; }
}
// --- КОНЕЦ ПРОВЕРКИ ---
$upload_path = BASE_DIR . '/uploads/comments/';
if (!is_dir($upload_path)) {
@mkdir($upload_path, 0775, true);
$index_content = "<?php\n/**\n * Файл-заглушка...\n */\nheader('Location:/');\nexit;\n?>";
@file_put_contents($upload_path . 'index.php', $index_content);
}
$files_to_process = [];
if (is_array($_FILES['comment_image']['name'])) {
foreach ($_FILES['comment_image']['name'] as $k => $v) {
if (!empty($v) && $_FILES['comment_image']['error'][$k] == UPLOAD_ERR_OK) {
$files_to_process[] = [
'name' => $_FILES['comment_image']['name'][$k],
'tmp_name' => $_FILES['comment_image']['tmp_name'][$k],
'size' => $_FILES['comment_image']['size'][$k]
];
}
}
} elseif (!empty($_FILES['comment_image']['name']) && $_FILES['comment_image']['error'] == UPLOAD_ERR_OK) {
$files_to_process[] = $_FILES['comment_image'];
}
$allowed_ext_str = $settings['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif,webp';
$allowed_extensions = array_map('trim', explode(',', strtolower($allowed_ext_str)));
$max_file_size_bytes = (int)($settings['comment_max_file_size'] ?? 2048) * 1024;
// не даем загрузить одно и то же содержимое дважды в одном запросе
$processed_hashes = [];
foreach ($files_to_process as $file) {
$file_hash = md5_file($file['tmp_name']);
if (in_array($file_hash, $processed_hashes)) continue;
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
// Проверки расширения и безопасности
$is_allowed_ext = in_array($ext, $allowed_extensions);
$is_dangerous = in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
// Условие: расширение разрешено, файл не опасен и проходит по размеру
if ($is_allowed_ext && !$is_dangerous && $file['size'] > 0 && $file['size'] <= $max_file_size_bytes)
{
// 1. Берем имя
$original_basename = pathinfo($file['name'], PATHINFO_FILENAME);
// 2. Очищаем (БЕЗ $this->, просто вызываем функцию)
$clean_name = prepare_fname($original_basename);
// 3. Если пусто - дефолт
if (empty($clean_name)) {
$clean_name = 'file';
}
// 4. Собираем имя
$new_name = $clean_name . '_' . time() . '.' . $ext;
if (move_uploaded_file($file['tmp_name'], $upload_path . $new_name)) {
$uploaded_files[] = $new_name;
$processed_hashes[] = $file_hash;
}
}
}
}
// --- ПОДГОТОВКА ДАННЫХ ДЛЯ БД ---
$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'] = !empty($uploaded_files) ? implode(',', $uploaded_files) : '';
$user_rating = (int)($_POST['comment_user_rating'] ?? 0);
if ($parent_id > 0 && (!isset($settings['comment_show_user_rating_replies']) || $settings['comment_show_user_rating_replies'] == 0)) {
$user_rating = 0;
}
if ($user_rating < 0) $user_rating = 0; if ($user_rating > 5) $user_rating = 5;
$new_comment['user_rating'] = $user_rating;
// --- ПОДГОТОВКА И ОБРАБОТКА ТЕКСТА КОММЕНАТРИЯ ЖЕСТКАЯ (вырежет весь тест как только встретит < тег) ---
//$comment_text_raw = $_POST['comment_text'] ?? '';
//$comment_text_clean = strip_tags(stripslashes($comment_text_raw));
//$new_comment['comment_text'] = addslashes($comment_text_clean);
// --- ПОДГОТОВКА И ОБРАБОТКА ТЕКСТА КОММЕНАТРИЯ МЯГКАЯ (с соранением в виде специальных сущностей) ---
// 1. Получаем сырой текст
$comment_text_raw = $_POST['comment_text'] ?? '';
// 2. Убираем лишние слеши
$comment_text_raw = stripslashes($comment_text_raw);
// 3. ПРЕВРАЩАЕМ теги в текст (экранируем), а не вырезаем их
// Теперь <?php станет &lt;?php и просто отобразится как текст
$comment_text_safe = htmlspecialchars($comment_text_raw, ENT_QUOTES, 'UTF-8');
// 4. Подготавливаем для вставки в SQL запрос
$new_comment['comment_text'] = addslashes($comment_text_safe);
$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)) . '&comment_id=' . $new_comment['Id'] . '#' . $new_comment['Id'];
$mail_text = "Новый комментарий:\n" . stripslashes($new_comment['comment_text']) . "\n\nСсылка: " . $page_link;
send_mail($mail_from, $mail_text, "Новый комментарий на сайте", $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) : '';
if (empty($new_comment['avatar']) || strpos($new_comment['avatar'], 'user.png') !== false) {
$name = !empty($new_comment['comment_author_name']) ? stripslashes($new_comment['comment_author_name']) : 'Guest';
$new_comment['first_letter'] = mb_substr(trim($name), 0, 1, 'UTF-8');
$new_comment['avatar_color_index'] = (abs(crc32($name)) % 12) + 1;
}
$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']);
$subcomments[] = $new_comment;
$AVE_Template->assign('subcomments', $subcomments);
$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;
// Инициализация данных
$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');
// Получаем данные комментария и настройки модуля (JOIN)
$row = $AVE_DB->Query("
SELECT
msg.*,
cmnt.comment_max_chars,
cmnt.comment_need_approve,
cmnt.comment_user_groups,
cmnt.comment_edit_time,
cmnt.comment_allowed_extensions,
cmnt.comment_max_file_size,
cmnt.comment_max_files,
cmnt.comment_allow_files,
cmnt.comment_allow_files_anon
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');
// Проверка прав
$is_admin = ($user_group == 1);
$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);
$time_limit = (isset($row['comment_edit_time'])) ? (int)$row['comment_edit_time'] : 60;
$time_passed = time() - (int)$row['comment_published'];
$is_time_ok = ($time_passed < $time_limit);
if (!$is_admin) {
if (!$is_author) exit('NOT_AUTHOR');
if (!$is_time_ok) {
echo "TIME_EXPIRED";
exit;
}
}
// Обработка текста
// 1. Получаем текст
$comment_text = $_POST['text'] ?? '';
// 2. Убираем слеши
$comment_text = stripslashes($comment_text);
// 3. Декодируем сущности (чтобы не было &amp;lt;)
$comment_text = html_entity_decode($comment_text, ENT_QUOTES, 'UTF-8');
// 4. УБИРАЕМ ДУБЛИРОВАНИЕ СТРОК:
// Сначала превращаем все варианты переносов (Windows \r\n, Mac \r) в единый \n
$comment_text = str_replace(array("\r\n", "\r"), "\n", $comment_text);
// Теперь заменяем <br> (если они есть) на \n, но так, чтобы не создавать лишних пустых строк
$comment_text = preg_replace('/<br\s*\/?>\n?/i', "\n", $comment_text);
// Удаляем лишние пробелы в концах строк (необязательно, но полезно)
$comment_text = implode("\n", array_map('trim', explode("\n", $comment_text)));
// 5. БЕЗОПАСНОСТЬ: Экранируем обратно для базы
$comment_text = htmlspecialchars($comment_text, ENT_QUOTES, 'UTF-8');
// 6. Ограничение по длине (как у тебя и было)
$max = ($row['comment_max_chars'] > 10) ? (int)$row['comment_max_chars'] : 1000;
if (mb_strlen($comment_text) > $max) {
$comment_text = mb_substr($comment_text, 0, $max) . '…';
}
$comment_text_cut = $comment_text;
// Работа с изображениями (МУЛЬТИЗАГРУЗКА И УДАЛЕНИЕ)
// ПРОВЕРКА ПРАВ НА ЗАГРУЗКУ (Защита от взлома через POST)
$allow_files = (int)($row['comment_allow_files'] ?? 0);
$allow_anon = (int)($row['comment_allow_files_anon'] ?? 0);
// Определяем, имеет ли право текущий пользователь загружать файлы
$can_upload = false;
if ($allow_files === 1) {
if ($user_group == 2) { // Если гость
if ($allow_anon === 1) $can_upload = true;
} else { // Если зарегистрирован
$can_upload = true;
}
}
// Если файлы пытаются загрузить, но прав нет — просто очищаем массив файлов
if (!$can_upload && isset($_FILES['comment_image'])) {
unset($_FILES['comment_image']);
}
$upload_dir = BASE_DIR . '/uploads/comments/';
// Получаем текущие файлы из БД в массив
$current_files = !empty($row['comment_file']) ? explode(',', $row['comment_file']) : [];
// --- Обработка выборочного удаления ---
if (!empty($_POST['delete_files'])) {
$files_to_remove = explode(',', $_POST['delete_files']);
foreach ($files_to_remove as $rem_file) {
$rem_file = trim($rem_file);
if (empty($rem_file)) continue;
// Удаляем физически
if (file_exists($upload_dir . $rem_file)) {
@unlink($upload_dir . $rem_file);
}
// Удаляем из массива для БД
$current_files = array_filter($current_files, function($v) use ($rem_file) {
return trim($v) !== $rem_file;
});
}
}
// --- Загрузка новых файлов (с проверкой лимита) ---
if (isset($_FILES['comment_image']) && is_array($_FILES['comment_image']['name'])) {
// Считаем, сколько реально новых файлов пытаются загрузить
$new_files_to_upload_count = 0;
foreach ($_FILES['comment_image']['name'] as $k => $fname) {
if (!empty($fname) && $_FILES['comment_image']['error'][$k] == UPLOAD_ERR_OK) {
$new_files_to_upload_count++;
}
}
// Проверка лимита: (Оставшиеся старые + Новые) не должно быть больше MAX
$max_limit = (int)($row['comment_max_files'] ?? 5);
if ((count($current_files) + $new_files_to_upload_count) > $max_limit) {
echo "MAX_FILES_LIMIT_EXCEEDED";
exit;
}
$allowed_ext_str = $row['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif,webp';
$allowed_extensions = array_map('trim', explode(',', strtolower($allowed_ext_str)));
$max_kb = (int)($row['comment_max_file_size'] ?? 2048);
// Перебираем загруженные файлы
foreach ($_FILES['comment_image']['name'] as $i => $fname) {
if ($_FILES['comment_image']['error'][$i] == UPLOAD_ERR_OK) {
$tmp_name = $_FILES['comment_image']['tmp_name'][$i];
$file_ext = strtolower(pathinfo($fname, PATHINFO_EXTENSION));
$file_size = $_FILES['comment_image']['size'][$i];
// Проверка MIME-типа
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $tmp_name);
finfo_close($finfo);
// Проверки расширения и опасных файлов
$is_allowed_ext = in_array($file_ext, $allowed_extensions);
$is_dangerous = in_array($file_ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
if ($is_allowed_ext && !$is_dangerous && $file_size > 0 && $file_size <= ($max_kb * 1024))
{
if (!is_dir($upload_dir)) @mkdir($upload_dir, 0775, true);
// Получаем оригинальное имя файла без расширения
$original_basename = pathinfo($fname, PATHINFO_FILENAME);
// Очищаем имя
$clean_name = prepare_fname($original_basename);
// Защита от пустого имени
if (empty($clean_name)) {
$clean_name = 'file';
}
// Формируем новое имя: чистое_имя + время + расширение
$new_file_name = $clean_name . '_' . time() . '.' . $file_ext;
if (move_uploaded_file($tmp_name, $upload_dir . $new_file_name)) {
$current_files[] = $new_file_name;
}
}
}
}
}
// Собираем итоговую строку файлов для БД через запятую
$final_files_str = implode(',', array_unique(array_filter($current_files)));
//$user_rating = isset($_POST['user_rating']) ? (int)$_POST['user_rating'] : (int)$row['user_rating'];
// --- ПРОВЕРКА ПРАВ НА РЕДАКТИРОВАНИЕ РЕЙТИНГА ---
// Рейтинг может менять только Админ (группа 1) или Автор комментария
$can_edit_rating = ($is_admin || $is_author);
if ($can_edit_rating && isset($_POST['user_rating'])) {
$user_rating = (int)$_POST['user_rating'];
// Ограничиваем диапазон от 0 до 5
if ($user_rating < 0) $user_rating = 0;
if ($user_rating > 5) $user_rating = 5;
} else {
// Если прав нет или в запросе нет нового рейтинга — оставляем старый из БД
$user_rating = (int)$row['user_rating'];
}
// Обновление базы данных
$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 . "',
user_rating = '" . $user_rating . "',
comment_file = '" . addslashes($final_files_str) . "'
WHERE
Id = '" . $comment_id . "'
");
echo htmlspecialchars($comment_text_cut, ENT_QUOTES);
exit;
}
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');
// Читаем тип удаления из запроса (для админки)
$delete_type = $_REQUEST['admin_action_type'] ?? 'auto';
$this->_commentSettingsGet();
$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'] < (int)$this->conf_edit_time);
if ($is_author && $is_time_ok) $can_delete = true;
}
if (!$can_delete) {
header('HTTP/1.1 403 Forbidden');
die('Доступ запрещен');
}
$has_children = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE parent_id = '" . $comment_id . "'")->GetCell();
$upload_dir = BASE_DIR . '/uploads/comments/';
// Проверка на Ajax
$is_ajax = (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
// --- ЛОГИКА МЯГКОГО УДАЛЕНИЯ ---
if ($has_children > 0 && $delete_type !== 'full') {
// 1. Физически удаляем файлы, если они были
if (!empty($comment_data['comment_file'])) {
$files_to_del = explode(',', $comment_data['comment_file']);
foreach ($files_to_del as $f_name) {
$f_path = $upload_dir . trim($f_name);
if (!empty($f_name) && file_exists($f_path)) @unlink($f_path);
}
}
$status_marker = ($user_group === 1) ? '__DEL_BY_ADM__' : '__DEL_BY_AUT__';
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET comment_text = '" . $status_marker . "',
comment_author_name = '__DELETED__',
comment_author_id = '0',
comment_author_email = '',
comment_author_ip = '0.0.0.0',
comment_file = '',
anon_key = '',
user_rating = '0',
rating_sum = '0',
rating_count = '0',
comment_status = '1',
comment_changed = '" . time() . "'
WHERE Id = '" . $comment_id . "'
");
echo $is_ajax ? "OK_SOFT" : "OK";
}
else {
// --- ПОЛНОЕ УДАЛЕНИЕ ---
$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);
}
$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'])) {
$files_to_del = explode(',', $f['comment_file']);
foreach ($files_to_del as $f_name) {
$f_path = $upload_dir . trim($f_name);
if (!empty($f_name) && file_exists($f_path)) @unlink($f_path);
}
}
}
$AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)");
echo "OK";
}
die();
}
/**
* Метод для обработки голосования за комментарий
* Защита: ID для залогиненных, Пара (Ключ + IP) для анонимов.
* Добавлена проверка глобальной настройки прав для анонимов.
*/
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);
// IP пользователя
$user_ip = $_SERVER['REMOTE_ADDR'] ?? '';
// Базовая валидация
if ($comment_id <= 0 || $vote_value < 1 || $vote_value > 5) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'error';
exit;
}
return;
}
// Идентификация текущего голосующего
$user_id = empty($_SESSION['user_id']) ? 0 : (int)$_SESSION['user_id'];
$anon_key = $this->_getAnonKey();
// ПРОВЕРКА ПРАВ ГОЛОСОВАНИЯ
// Получаем настройки модуля, чтобы узнать, разрешено ли анонимам голосовать
$settings = $this->_commentSettingsGet();
if (empty($user_id) && empty($settings['comment_rating_anon_vote'])) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'forbidden_anon'; // Специальный статус для JS
exit;
}
return;
}
// Получаем данные о комментарии, за который голосуют
$comment_row = $AVE_DB->Query("
SELECT comment_author_id, anon_key, comment_author_ip
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
")->FetchRow();
if (!$comment_row) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'error';
exit;
}
return;
}
$c_author_id = (int)$comment_row->comment_author_id;
// ПРОВЕРКА АВТОРСТВА (Запрет голосовать за свой же комментарий)
$is_author = false;
if ($user_id > 0) {
// Если залогинен — проверяем только по ID
if ($user_id === $c_author_id) {
$is_author = true;
}
} else {
// Если аноним — проверяем парой (Ключ + IP)
// И только если автор комментария тоже был анонимом
if ($c_author_id === 0 &&
$anon_key === $comment_row->anon_key &&
$user_ip === $comment_row->comment_author_ip) {
$is_author = true;
}
}
if ($is_author) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'own_comment';
exit;
}
return;
}
// ПРОВЕРКА ПОВТОРНОГО ГОЛОСОВАНИЯ
// Ищем в истории, голосовал ли уже этот посетитель
if ($user_id > 0) {
// Для авторизованных поиск по ID
$sql_check = "SELECT id FROM " . PREFIX . "_module_comment_votes
WHERE comment_id = '" . $comment_id . "'
AND user_id = '" . $user_id . "'";
} else {
// Для анонимов поиск по связке Ключ + IP
$sql_check = "SELECT id FROM " . PREFIX . "_module_comment_votes
WHERE comment_id = '" . $comment_id . "'
AND anon_key = '" . $anon_key . "'
AND remote_addr = '" . $user_ip . "'";
}
if ($AVE_DB->Query($sql_check)->GetCell()) {
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'already_voted';
exit;
}
return;
}
// ЗАПИСЬ ГОЛОСА В ЛОГ
$AVE_DB->Query("
INSERT INTO " . PREFIX . "_module_comment_votes
(comment_id, user_id, anon_key, remote_addr, vote_value, date_voted)
VALUES
('" . $comment_id . "',
'" . $user_id . "',
'" . $AVE_DB->escape($anon_key) . "',
'" . $AVE_DB->escape($user_ip) . "',
'" . $vote_value . "',
'" . time() . "')
");
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET rating_sum = rating_sum + " . $vote_value . ",
rating_count = rating_count + 1
WHERE Id = '" . $comment_id . "'
");
// Чистый ответ для JS
if ($ajax) {
if (ob_get_length()) ob_end_clean();
echo 'success';
exit;
}
}
function commentAdminDelete($comment_id)
{
global $AVE_DB;
// Собираем ID в массив (поддерживаем и одиночное, и массовое удаление)
if (is_array($comment_id)) {
$ids = array_map('intval', $comment_id);
} else {
$ids = [(int)$comment_id];
}
if (empty($ids)) return;
// Путь к папке с файлами
$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/comments/";
// Читаем тип удаления из запроса
$delete_type = $_REQUEST['admin_action_type'] ?? 'auto';
foreach ($ids as $id) {
if ($id <= 0) continue;
// --- БЛОК УДАЛЕНИЯ ФАЙЛОВ (НАЧАЛО) ---
// Получаем имена файлов перед любым действием в БД
$comment_files = $AVE_DB->Query("SELECT comment_file FROM " . PREFIX . "_module_comment_info WHERE Id = '$id' LIMIT 1")->GetCell();
if (!empty($comment_files)) {
$files_to_delete = explode(',', $comment_files);
foreach ($files_to_delete as $filename) {
$filename = trim($filename);
if (empty($filename)) continue;
$full_path = $upload_dir . $filename;
$thumb_path = $upload_dir . "thumbs/" . $filename; // Удаляем и миниатюру, если есть
if (file_exists($full_path)) @unlink($full_path);
if (file_exists($thumb_path)) @unlink($thumb_path);
}
}
// --- БЛОК УДАЛЕНИЯ ФАЙЛОВ (КОНЕЦ) ---
// Проверяем наличие детей
$has_children = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE parent_id = '$id'")->GetCell();
// Если тип 'soft' ИЛИ (тип 'auto' и есть дети) — делаем мягкое удаление
if (($delete_type == 'soft') || ($delete_type == 'auto' && $has_children > 0)) {
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info
SET comment_text = '__DEL_BY_ADM__',
comment_author_name = '__DELETED__',
comment_author_id = '0',
comment_file = '',
comment_status = '1',
comment_changed = '" . time() . "'
WHERE Id = '$id'
");
}
// Если детей нет или принудительно выбран 'full' — удаляем физически
else {
$all_tree_ids = [$id];
$parents = [$id];
while (!empty($parents)) {
$p_str = implode(',', $parents);
$res = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE parent_id IN ($p_str)");
$parents = [];
if ($res) {
while ($r = $res->FetchAssocArray()) {
$all_tree_ids[] = (int)$r['Id'];
$parents[] = (int)$r['Id'];
}
}
}
$final_ids_str = implode(',', array_unique($all_tree_ids));
$AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)");
}
}
// Редирект с сохранением фильтров
$session_id = SESSION ?? '';
$s_query = $_REQUEST['search_query'] ?? '';
$s_order = $_REQUEST['sort_order'] ?? 'new';
$f_type = $_REQUEST['filter_type'] ?? '';
$v_mode = $_REQUEST['view_mode'] ?? 'flat';
$p_page = $_REQUEST['per_page'] ?? '15';
$d_from = $_REQUEST['date_from'] ?? '';
$d_to = $_REQUEST['date_to'] ?? '';
header("Location: index.php?do=modules&action=modedit&mod=comment&moduleaction=1" .
"&cp=" . $session_id .
"&search_query=" . urlencode($s_query) .
"&sort_order=" . $s_order .
"&filter_type=" . $f_type .
"&view_mode=" . $v_mode .
"&per_page=" . $p_page .
"&date_from=" . $d_from .
"&date_to=" . $d_to);
exit;
}
/**
* Метод, предназначенный для вывода детальной информации об авторе комментария
*
* @param string $tpl_dir - путь к шаблонам модуля
*/
function commentPostInfoShow($tpl_dir)
{
global $AVE_DB, $AVE_Template;
$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'] != '')
? '<a target="_blank" href="http://' . $row['comment_author_website'] . '">' . $row['comment_author_website'] .'</a>'
: '';
// Выполняем запрос к БД на получение количества всех комментариев, оставленных данным пользователем
$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;
// Определяем цифровой статус: lock -> 0, unlock -> 1
$status_numeric = ($comment_status == 'lock') ? 0 : 1;
if ($status_numeric == 0) {
// --- Скрываем всю ветку ---
$all_ids = [$comment_id];
$stack = [$comment_id];
while (!empty($stack)) {
$curr_id = array_shift($stack);
$res = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE parent_id = '$curr_id'");
while ($res && $row = $res->FetchAssocArray()) {
$all_ids[] = (int)$row['Id'];
$stack[] = (int)$row['Id'];
}
}
$ids_string = implode(',', $all_ids);
$AVE_DB->Query("UPDATE " . PREFIX . "_module_comment_info SET comment_status = '0' WHERE Id IN ($ids_string)");
echo "OK_LOCKED";
}
else {
// --- Открываем ТОЛЬКО один коммент ---
// Обновляем только если (родителя нет) ИЛИ (у родителя статус не 0)
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comment_info AS child
LEFT JOIN " . PREFIX . "_module_comment_info AS parent ON child.parent_id = parent.Id
SET child.comment_status = '1'
WHERE child.Id = '$comment_id'
AND (child.parent_id = 0 OR parent.comment_status != '0')
");
// Чтобы JS не тупил, всегда отвечаем OK (так как в базе либо уже 1, либо стала 1)
echo "OK_UNLOCKED";
}
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;
$session_id = SESSION ?? '';
// --- ОБРАБОТКА (Иконки и Кнопка "Применить") ---
$action = $_REQUEST['admin_action'] ?? '';
$items = [];
if (!empty($_REQUEST['ids'])) {
$items = explode(',', $_REQUEST['ids']);
} elseif (!empty($_REQUEST['id'])) {
$items = (is_array($_REQUEST['id'])) ? $_REQUEST['id'] : [$_REQUEST['id']];
}
// Проверяем наличие действия И (наличие массива галочек ИЛИ наличие одиночного ID в ссылке)
if (!empty($action) && (!empty($items) || !empty($_REQUEST['id']))) {
// Формируем единый массив $ids для обработки
if (!empty($items)) {
$ids = array_map('intval', $items);
} else {
// Если пришел одиночный ID (может быть как числом, так и массивом из одного элемента)
$single_id = $_REQUEST['id'];
$ids = is_array($single_id) ? array_map('intval', $single_id) : [(int)$single_id];
}
// 1. Для СКРЫТИЯ собираем "паровоз" (всех детей на любую глубину)
$all_related_for_hiding = $ids;
$current_p = $ids;
while (!empty($current_p)) {
$p_str = implode(',', $current_p);
$res = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE parent_id IN ($p_str)");
$found = [];
if ($res) {
while ($r = $res->FetchAssocArray()) { $found[] = (int)$r['Id']; }
}
if (!empty($found)) {
$all_related_for_hiding = array_merge($all_related_for_hiding, $found);
$current_p = $found;
} else { $current_p = []; }
}
$final_hide_list = implode(',', array_unique($all_related_for_hiding));
switch ($action) {
case 'approve':
case 'set_status_1':
// ПУБЛИКАЦИЯ: Работаем СТРОГО с теми ID, на которых кликнули или поставили галки
$active_db = [];
$res_act = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE comment_status = '1'");
if ($res_act) {
while ($row = $res_act->FetchAssocArray()) { $active_db[] = (int)$row['Id']; }
}
$to_publish = [];
foreach ($ids as $curr_id) {
$p_id = (int)$AVE_DB->Query("SELECT parent_id FROM " . PREFIX . "_module_comment_info WHERE Id = $curr_id")->GetCell();
// Разрешаем включить, только если родитель УЖЕ активен в базе
if ($p_id == 0 || in_array($p_id, $active_db)) {
$to_publish[] = $curr_id;
}
}
if (!empty($to_publish)) {
$pub_str = implode(',', $to_publish);
$AVE_DB->Query("UPDATE " . PREFIX . "_module_comment_info SET comment_status = '1' WHERE Id IN ($pub_str)");
}
break;
case 'unapprove':
case 'set_status_0':
// СКРЫТИЕ: Используем полный список (родители + все дети)
if (!empty($final_hide_list)) {
$AVE_DB->Query("UPDATE " . PREFIX . "_module_comment_info SET comment_status = '0' WHERE Id IN ($final_hide_list)");
}
break;
}
// 1. Сначала вытаскиваем текущие настройки из запроса
$s_query = $_GET['search_query'] ?? '';
$s_order = $_GET['sort_order'] ?? 'new';
$f_type = $_GET['filter_type'] ?? '';
$v_mode = $_GET['view_mode'] ?? 'flat';
$p_page = $_GET['per_page'] ?? '15';
$d_from = $_GET['date_from'] ?? '';
$d_to = $_GET['date_to'] ?? '';
// 2. Формируем строку возврата (редирект), чтобы настройки НЕ СЛЕТЕЛИ
header("Location: index.php?do=modules&action=modedit&mod=comment&moduleaction=1" .
"&cp=" . $session_id .
"&search_query=" . urlencode($s_query) .
"&sort_order=" . $s_order .
"&filter_type=" . $f_type .
"&view_mode=" . $v_mode .
"&per_page=" . $p_page .
"&date_from=" . $d_from .
"&date_to=" . $d_to);
exit;
}
// --- ПАРАМЕТРЫ ФИЛЬТРАЦИИ ---
$search = $_GET['search_query'] ?? '';
$sort = $_GET['sort_order'] ?? 'new';
$filter = $_GET['filter_type'] ?? '';
$view_mode = $_GET['view_mode'] ?? 'flat';
$per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 15;
$date_from = $_GET['date_from'] ?? '';
$date_to = $_GET['date_to'] ?? '';
$where = ["1=1"];
// Поиск
if ($search) {
$s = $AVE_DB->Escape($search);
$where[] = "(cmnt.comment_author_name LIKE '%$s%' OR cmnt.comment_author_ip LIKE '%$s%' OR cmnt.comment_text LIKE '%$s%')";
}
// Даты
if ($date_from) $where[] = "cmnt.comment_published >= " . strtotime($date_from);
if ($date_to) $where[] = "cmnt.comment_published <= " . (strtotime($date_to) + 86399);
// Спец. фильтры
if ($filter == 'with_files') $where[] = "cmnt.comment_file != ''";
if ($filter == 'hidden') $where[] = "cmnt.comment_status = '0'";
$where_sql = implode(" AND ", $where);
// Сортировка
switch ($sort) {
case 'old': $order_sql = "cmnt.comment_published ASC"; break;
case 'popular': $order_sql = "cmnt.rating_sum DESC, cmnt.rating_count DESC"; break;
case 'discussed': $order_sql = "(SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE parent_id = cmnt.Id) DESC"; break;
case 'user_rating': $order_sql = "cmnt.user_rating DESC"; break;
default: $order_sql = "cmnt.comment_published DESC"; break;
}
// --- ПАГИНАЦИЯ ---
$num = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info AS cmnt WHERE $where_sql")->GetCell();
$limit = $per_page;
$seiten = ceil($num / $limit);
$page = get_current_page();
$start = ($page * $limit) - $limit;
$sql = $AVE_DB->Query("
SELECT cmnt.*, doc.document_title, doc.document_alias
FROM " . PREFIX . "_module_comment_info AS cmnt
LEFT JOIN " . PREFIX . "_documents AS doc ON doc.Id = cmnt.document_id
WHERE $where_sql
ORDER BY $order_sql
LIMIT " . (int)$start . "," . (int)$limit
);
$all_items = array();
$format = "%d %B %Y, %H:%M";
// --- ПЕРЕД ЦИКЛОМ: Получаем список всех скрытых ID в системе ---
$locked_ids = [];
$res_locked = $AVE_DB->Query("SELECT Id FROM " . PREFIX . "_module_comment_info WHERE comment_status = '0'");
if ($res_locked) {
while ($rl = $res_locked->FetchAssocArray()) { $locked_ids[] = (int)$rl['Id']; }
}
while ($row = $sql->FetchAssocArray()) {
$name = !empty($row['comment_author_name']) ? stripslashes($row['comment_author_name']) : 'Guest';
if (isset($row['comment_author_id']) && $row['comment_author_id'] > 0) {
$row['avatar'] = getAvatar($row['comment_author_id'], 48);
} else { $row['avatar'] = ''; }
if (empty($row['avatar']) || strpos($row['avatar'], 'user.png') !== false) {
$row['avatar'] = '';
$row['first_letter'] = mb_strtoupper(mb_substr(trim($name), 0, 1, 'UTF-8'));
$row['avatar_color_index'] = (abs(crc32($name)) % 12) + 1;
}
// ОБРАБОТКА ФАЙЛОВ
$row['images'] = [];
$row['files'] = [];
if (!empty($row['comment_file'])) {
$img_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$all_files = explode(',', $row['comment_file']);
foreach ($all_files as $f_name) {
$f_name = trim($f_name);
if (!$f_name) continue;
$ext = strtolower(pathinfo($f_name, PATHINFO_EXTENSION));
$clean_name = preg_replace('/_[0-9]+(?=\.[a-z0-9]+$)/i', '', $f_name);
$file_data = ['orig_name' => $f_name, 'clean_name' => $clean_name, 'ext' => $ext];
if (in_array($ext, $img_exts)) {
$row['images'][] = $file_data;
} else {
$row['files'][] = $file_data;
}
}
}
$row['CId'] = $row['Id'];
// ДОБАВЛЯЕМ ПРОВЕРКУ РОДИТЕЛЯ (для линейного вида):
// Если parent_id этого комментария есть в списке скрытых ($locked_ids)
$row['parent_locked'] = (in_array((int)$row['parent_id'], $locked_ids)) ? 1 : 0;
$row['comment_text'] = stripslashes($row['comment_text']);
$ts_pub = (int)$row['comment_published'];
$row['date_pub'] = ($ts_pub > 0) ? pretty_date(ave_date_format($format, $ts_pub)) : '—';
$ts_changed = (int)$row['comment_changed'];
$row['date_edit'] = ($ts_changed > 0 && $ts_changed > $ts_pub) ? pretty_date(ave_date_format($format, $ts_changed)) : '';
$row['user_rating'] = (int)($row['user_rating'] ?? 0);
$row['rating_sum'] = (int)($row['rating_sum'] ?? 0);
$row['r_count'] = (int)($row['rating_count'] ?? 0);
$row['star_public'] = ($row['r_count'] > 0) ? round($row['rating_sum'] / $row['r_count']) : 0;
$doc_id = (int)$row['document_id'];
$alias = (!empty($row['document_alias']) && $row['document_alias'] != '/') ? $row['document_alias'] : '';
$raw_url = "index.php?id=" . $doc_id . "&doc=" . $alias . "/";
$final_url = function_exists('rewrite_link') ? rewrite_link($raw_url) : $alias;
$final_url = preg_replace('/(?<!:)\/\//', '/', str_ireplace(['"//"', '///'], ['/', '/'], $final_url));
$final_url = rtrim($final_url, '/');
if (empty($final_url)) {
$final_url = '/';
}
$row['document_link'] = $final_url;
$all_items[$row['Id']] = $row;
}
// --- ПОСТРОЕНИЕ ДЕРЕВА ---
$child_map = [];
foreach ($all_items as $id => $item) {
$p_id = (int)$item['parent_id'];
if ($p_id > 0) $child_map[$p_id][] = $id;
}
foreach ($all_items as $id => &$item_for_check) {
$item_for_check['has_children'] = isset($child_map[$id]) ? 1 : 0;
}
unset($item_for_check);
$docs = [];
$processed = [];
// Если выбран линейный вид — просто выводим список без иерархии
if ($view_mode == 'flat') {
foreach ($all_items as $item) {
$item['depth_level'] = 0;
// берем parent_locked, который вычислили выше
$docs[] = $item;
}
}
// Если вид древовидный
else {
// функция с наследованием блокировки parent_locked
$buildTree = function($parent_id, $level, $is_parent_hidden) use (&$buildTree, &$docs, &$processed, &$all_items, &$child_map) {
if (isset($child_map[$parent_id])) {
foreach ($child_map[$parent_id] as $child_id) {
if (!in_array($child_id, $processed)) {
$item = $all_items[$child_id];
$item['depth_level'] = $level;
// Блокируем ребенка, если скрыт его родитель или кто-то выше по дереву
$item['parent_locked'] = ($is_parent_hidden || $all_items[$parent_id]['comment_status'] == '0') ? 1 : 0;
$docs[] = $item;
$processed[] = $child_id;
$buildTree($child_id, $level + 1, $item['parent_locked']);
}
}
}
};
// Собираем дерево, начиная с корней
foreach ($all_items as $id => $item) {
if ($item['parent_id'] == 0 && !in_array($id, $processed)) {
$item['depth_level'] = 0;
$item['parent_locked'] = 0;
$docs[] = $item;
$processed[] = $id;
$buildTree($id, 1, false);
}
}
// Добираем то, что не попало в иерархию (битые связи)
foreach ($all_items as $id => $item) {
if (!in_array($id, $processed)) {
$item['depth_level'] = 0;
$item['parent_locked'] = 0;
$docs[] = $item;
}
}
}
// --- КОНЕЦ ПОСТРОЕНИЯ ДЕРЕВА ---
$comment_rating_type = $AVE_DB->Query("
SELECT comment_rating_type
FROM " . PREFIX . "_module_comments
LIMIT 1
")->GetCell();
$AVE_Template->assign('comment_rating_type', (int)$comment_rating_type);
// Собираем все текущие GET-параметры, кроме страницы
$current_params = $_GET;
unset($current_params['page']);
$query_string = http_build_query($current_params);
$AVE_Template->assign([
'docs' => $docs,
'page_nav' => ($num > $limit) ? get_pagination($seiten, 'page', ' <a class="pnav" href="index.php?' . $query_string . '&page={s}">{t}</a> ') : '',
'sess' => $session_id
]);
$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;
$post_sub = $_POST['sub'] ?? '';
$request_id = (int)($_REQUEST['Id'] ?? 0);
$is_ajax = isset($_REQUEST['ajax']);
// Получаем данные и настройки одним запросом
$row = $AVE_DB->Query("
SELECT
msg.*,
cmnt.comment_allowed_extensions,
cmnt.comment_max_file_size,
cmnt.comment_max_files,
cmnt.comment_allow_files,
cmnt.comment_show_f1,
cmnt.comment_show_f2,
cmnt.comment_name_f1,
cmnt.comment_name_f2,
cmnt.comment_show_user_rating
FROM " . PREFIX . "_module_comment_info AS msg
JOIN " . PREFIX . "_module_comments AS cmnt ON cmnt.Id = 1
WHERE msg.Id = '" . (int)$request_id . "'
LIMIT 1
")->FetchAssocArray();
if ($post_sub == 'send' && $row)
{
$upload_dir = BASE_DIR . '/uploads/comments/';
// Получаем массив текущих файлов (разбиваем по запятой)
$current_files = !empty($row['comment_file']) ? explode(',', $row['comment_file']) : [];
// Удаление файлов ---
if (!empty($_POST['delete_files'])) {
$files_to_remove = (array)$_POST['delete_files'];
foreach ($files_to_remove as $rem_file) {
$rem_file = basename(trim($rem_file)); // Очистка имени
if (empty($rem_file)) continue;
if (file_exists($upload_dir . $rem_file)) {
@unlink($upload_dir . $rem_file);
}
$current_files = array_filter($current_files, fn($v) => trim($v) !== $rem_file);
}
}
// Загрузка новых файлов ---
if ($row['comment_allow_files'] == 1 && isset($_FILES['comment_image']) && is_array($_FILES['comment_image']['name'])) {
if (!is_dir($upload_dir)) {
@mkdir($upload_dir, 0775, true);
@file_put_contents($upload_dir . 'index.php', "<?php\nheader('Location:/');\nexit;");
}
$max_limit = (int)($row['comment_max_files'] ?? 5);
$allowed_ext_str = $row['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif,webp';
$allowed_extensions = array_map(fn($e) => trim(strtolower($e)), explode(',', $allowed_ext_str));
$max_file_size_bytes = (int)($row['comment_max_file_size'] ?? 2048) * 1024;
$processed_hashes = [];
foreach ($_FILES['comment_image']['name'] as $k => $fname) {
if ($_FILES['comment_image']['error'][$k] == UPLOAD_ERR_OK) {
// проверяем лимит на каждой итерации
if (count($current_files) >= $max_limit) break;
$tmp_name = $_FILES['comment_image']['tmp_name'][$k];
$file_size = $_FILES['comment_image']['size'][$k];
$file_hash = md5_file($tmp_name);
if (in_array($file_hash, $processed_hashes)) continue;
$ext = strtolower(pathinfo($fname, PATHINFO_EXTENSION));
$is_allowed_ext = in_array($ext, $allowed_extensions);
$is_dangerous = in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi', 'html', 'js']);
if ($is_allowed_ext && !$is_dangerous && $file_size > 0 && $file_size <= $max_file_size_bytes) {
$clean_name = function_exists('prepare_fname') ? prepare_fname(pathinfo($fname, PATHINFO_FILENAME)) : 'file';
if (empty($clean_name)) $clean_name = 'file';
$new_name = $clean_name . '_' . time() . '.' . $ext;
if (move_uploaded_file($tmp_name, $upload_dir . $new_name)) {
$current_files[] = $new_name;
$processed_hashes[] = $file_hash;
}
}
}
}
}
// Финальная строка файлов для базы
$final_files_str = implode(',', array_unique(array_filter($current_files)));
// --- Рейтинг ---
$new_rating = ($this->_edit_avtor_rating == 1 && isset($_POST['user_rating']))
? (int)$_POST['user_rating']
: (int)($row['user_rating'] ?? 0);
if ($new_rating < 0) $new_rating = 0;
if ($new_rating > 5) $new_rating = 5;
// Обновление БД
$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'] ?? '')) . "',
user_rating = '" . $new_rating . "',
comment_file = '" . addslashes($final_files_str) . "',
comment_changed = '" . time() . "'
WHERE Id = '" . (int)$request_id . "'
");
if ($is_ajax) {
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'theme' => 'success',
'header' => 'Обновлено',
'message' => 'Данные успешно сохранены'
]);
exit;
}
exit;
}
if (!$row) {
$AVE_Template->assign('editfalse', 1);
} else {
$row['file_list'] = !empty($row['comment_file']) ? explode(',', $row['comment_file']) : [];
$AVE_Template->assign('row', $row);
$AVE_Template->assign('edit_rating_enabled', $this->_edit_avtor_rating);
$AVE_Template->assign('comment_max_chars', (int)$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, $sess;
$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_allow_self_answer = $_POST['comment_allow_self_answer'] ?? 0;
// Порядок сортировки
$post_sort_order = $_POST['comment_sort_order'] ?? 'ASC';
$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_ajax_replies_limit = $_POST['comment_ajax_replies_limit'] ?? 5;
// Настройки рейтинга и файлов
$post_allow_files = $_POST['comment_allow_files'] ?? 0;
$post_allow_files_anon = $_POST['comment_allow_files_anon'] ?? 0;
/* Настройки расширений и максимального размера файла */
$post_allowed_extensions = $_POST['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif';
$post_max_file_size = $_POST['comment_max_file_size'] ?? 2048;
$post_max_files = $_POST['comment_max_files'] ?? 5;
$post_rating_type = $_POST['comment_rating_type'] ?? 0;
$post_show_user_rating = $_POST['comment_show_user_rating'] ?? 0;
/* Настройка показа авторской оценки в ответах */
$post_show_user_rating_replies = $_POST['comment_show_user_rating_replies'] ?? 0;
$post_rating_anon_vote = $_POST['comment_rating_anon_vote'] ?? 0;
$post_rating_anon_set = $_POST['comment_rating_anon_set'] ?? 0;
// настройки времени и куки
$post_edit_time = $_POST['comment_edit_time'] ?? 60;
$post_cookie_life = $_POST['comment_cookie_life'] ?? 30;
// Дополнительные поля
$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' || $request_sub == 'apply')
{
$max_chars = (empty($post_max_chars) || $post_max_chars < 50) ? 50 : $post_max_chars;
// Валидация сортировки (ASC, DESC или RATING)
$allowed_sorts = ['ASC', 'DESC', 'RATING', 'USER_RATING'];
$sort_order = in_array(strtoupper($post_sort_order), $allowed_sorts) ? strtoupper($post_sort_order) : 'ASC';
// Подготовка имен полей и настроек файлов
$clean_name_f1 = htmlspecialchars(stripslashes($post_name_f1), ENT_QUOTES);
$clean_name_f2 = htmlspecialchars(stripslashes($post_name_f2), ENT_QUOTES);
// Очищаем строку расширений от пробелов и лишних запятых
$clean_extensions = implode(',', array_filter(array_map('trim', explode(',', $post_allowed_extensions))));
$AVE_DB->Query("
UPDATE " . PREFIX . "_module_comments
SET
comment_max_chars = '" . (int)$max_chars . "',
comment_user_groups = '" . addslashes(implode(',', $post_user_groups)) . "',
comment_user_groups_read = '" . addslashes(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_ajax_replies_limit = '" . (int)$post_ajax_replies_limit . "',
comment_allow_self_answer = '" . (int)$post_allow_self_answer . "',
comment_sort_order = '" . $sort_order . "',
comment_allow_files = '" . (int)$post_allow_files . "',
comment_allow_files_anon = '" . (int)$post_allow_files_anon . "',
comment_allowed_extensions = '" . addslashes($clean_extensions) . "',
comment_max_file_size = '" . (int)$post_max_file_size . "',
comment_max_files = '" . (int)$post_max_files . "',
comment_rating_type = '" . (int)$post_rating_type . "',
comment_show_user_rating = '" . (int)$post_show_user_rating . "',
comment_show_user_rating_replies = '" . (int)$post_show_user_rating_replies . "',
comment_rating_anon_vote = '" . (int)$post_rating_anon_vote . "',
comment_rating_anon_set = '" . (int)$post_rating_anon_set . "',
comment_edit_time = '" . (int)$post_edit_time . "',
comment_cookie_life = '" . (int)$post_cookie_life . "',
comment_show_f1 = '" . (int)$post_show_f1 . "',
comment_req_f1 = '" . (int)$post_req_f1 . "',
comment_name_f1 = '" . addslashes($clean_name_f1) . "',
comment_show_f2 = '" . (int)$post_show_f2 . "',
comment_req_f2 = '" . (int)$post_req_f2 . "',
comment_name_f2 = '" . addslashes($clean_name_f2) . "'
WHERE
Id = 1
");
if ($request_sub == 'apply') {
@ob_clean();
echo "success";
exit;
}
if ($request_sub == 'save') {
header("Location: index.php?do=modules&cp=" . $sess);
exit;
}
}
// Получаем данные для отображения в шаблоне
$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));
}
/**
* Метод для получения последних X комментариев
*/
function getLatestComments($limit = 10, $chars = 150)
{
global $AVE_DB;
// 1. Принудительно подключаем CSS
$GLOBALS['user_header']['comment_css'] = '<link rel="stylesheet" href="' . ABS_PATH . 'modules/comment/css/mod_comment_styles.css" type="text/css" />';
$sql = $AVE_DB->Query("
SELECT
comm.*,
doc.document_title,
doc.document_alias
FROM " . PREFIX . "_module_comment_info AS comm
LEFT JOIN " . PREFIX . "_documents AS doc ON doc.Id = comm.document_id
WHERE comm.comment_status = '1'
ORDER BY comm.comment_published DESC
LIMIT " . (int)$limit
);
$items = array();
if ($sql && $sql->NumRows() > 0) {
while ($res = $sql->FetchAssocArray()) {
$row = array_change_key_case($res, CASE_LOWER);
// Обработка текста комментария
$row['comment_text'] = mb_strimwidth(strip_tags($row['comment_text'] ?? ''), 0, (int)$chars, "...");
$row['date'] = ave_date_format(get_settings('date_format'), $row['comment_published']);
// Логика аватара (системный или буквенный)
if (isset($row['comment_author_id']) && $row['comment_author_id'] > 0) {
$row['avatar'] = function_exists('getAvatar') ? getAvatar($row['comment_author_id'], 48) : '';
} else {
$row['avatar'] = '';
}
if (empty($row['avatar']) || strpos($row['avatar'], 'user.png') !== false) {
$row['avatar'] = '';
$name = !empty($row['comment_author_name']) ? stripslashes($row['comment_author_name']) : 'Guest';
$row['first_letter'] = mb_strtoupper(mb_substr(trim($name), 0, 1, 'UTF-8'));
$row['avatar_color_index'] = (abs(crc32($name)) % 12) + 1;
}
// Авторские звезды
$user_rating = (int)($row['user_rating'] ?? 0);
$stars_html = '';
if ($user_rating > 0) {
for ($i = 1; $i <= 5; $i++) {
if ($i <= $user_rating) {
$stars_html .= '<i class="bi bi-star-fill text-warning me-1" style="font-size: 0.8rem;"></i>';
} else {
$stars_html .= '<i class="bi bi-star text-muted me-1" style="font-size: 0.8rem; opacity: 0.5;"></i>';
}
}
}
$row['stars'] = $stars_html;
// Формирование ссылки
$doc_id = (int)$row['document_id'];
$alias = (!empty($row['document_alias']) && $row['document_alias'] != '/') ? $row['document_alias'] : '';
$raw_url = "index.php?id=" . $doc_id . "&doc=" . $alias . "/";
$final_url = function_exists('rewrite_link') ? rewrite_link($raw_url) : $alias;
// Чистим слеши
$final_url = preg_replace('/(?<!:)\/\//', '/', str_ireplace(['"//"', '///'], ['/', '/'], $final_url));
$final_url = rtrim($final_url, '/');
// ФИКС ГЛАВНОЙ: если ссылка пустая после rtrim, значит это корень сайта
if (empty($final_url)) {
$final_url = '/';
}
// Собираем финальный URL с якорем
$row['link'] = $final_url . '#comment_wrapper_' . ($row['id'] ?? 0);
// Дополнительная проверка корректности начала ссылки
if (isset($row['link'][0]) && $row['link'][0] == '#') {
$row['link'] = '/' . $row['link'];
}
$row['document_title'] = !empty($row['document_title']) ? $row['document_title'] : 'Документ #' . $doc_id;
$items[] = $row;
}
}
return $items;
}
}
?>