Files
comment/class/comment.php

1128 lines
45 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 AVE.cms
* @subpackage module_Comment
* @since 1.4
* @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 = 60; // 10 минут на редактирование/удаление
public $conf_cookie_life = 30; // 30 дней жизни куки для анонима
private $_anon_cookie_name = 'ave_anon_comment_key';
/**
* Внутренние методы класса
*/
/**
* Получение или создание уникального ключа для анонимного пользователя
*/
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 $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 = '<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> ';
$page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
$page_nav = preg_replace('/(?<!:)\/\//', '/', $page_nav);
}
else
{
$page_nav = '';
}
}
else
{
$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");
$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 = ''; // Сбрасываем, если не удалось переместить
}
}
}
// --- ПОДГОТОВКА ДАННЫХ ДЛЯ БД ---
$new_comment['parent_id'] = (int)($_POST['parent_id'] ?? 0);
$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('&amp;', '&', $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('/&#x([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("<br>\n", "<br />\n", "<br/>\n", "<br>", "<br />"), "\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;
// Очищаем буфер вывода. Если ядро системы уже что-то начало выводить,
// это может помешать чистому AJAX-ответу.
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;
// 1. ПРАВО АДМИНА
if ($user_group === 1) {
$can_delete = true;
}
else {
// 2. ПРАВО АВТОРА (профиль или анонимный ключ)
$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) {
// Вместо редиректа отдаем статус 403, чтобы JS-обработчик понял причину
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) {
// --- ВАРИАНТ А: МЯГКОЕ УДАЛЕНИЕ (есть ответы) ---
if (!empty($comment_data['comment_file'])) {
$file_path = $upload_dir . $comment_data['comment_file'];
if (file_exists($file_path)) @unlink($file_path);
}
$del_text = "<span class='comment-deleted'>Комментарий удален автором.</span>";
$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 {
// --- ВАРИАНТ Б: ПОЛНОЕ УДАЛЕНИЕ (ответов нет) ---
if (!empty($comment_data['comment_file'])) {
$file_path = $upload_dir . $comment_data['comment_file'];
if (file_exists($file_path)) @unlink($file_path);
}
$AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id = '" . $comment_id . "'");
echo "OK";
}
// Использование die() гарантирует, что ядро AVE.cms не сделает
// автоматический редирект (302), который вызывал ошибку ERR_TOO_MANY_REDIRECTS.
die();
}
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'] != '')
? '<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;
$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 = ' <a class="pnav" href="index.php?do=modules&action=modedit&mod=comment&moduleaction=1&cp=' . $session_id . '&page={s}' . $def_nav . '">{t}</a> ';
$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 '<script>window.opener.location.reload();window.close();</script>';
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));
}
}
?>