diff --git a/class/comment.php b/class/comment.php
index 5ee98e5..021b31e 100644
--- a/class/comment.php
+++ b/class/comment.php
@@ -99,10 +99,34 @@ class Comment
*/
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;
+ }
+ }
+
/**
* Метод, предназначенный для получения основных настроек модуля,которые задаются в Панели управления.
*
@@ -174,129 +198,157 @@ class Comment
*/
/**
- * Метод, предназначенный для получения из БД всех комментариев, относящихся к указанному
- * документу с последующим выводом в Публичной части.
- *
- * @param string $tpl_dir - путь к шаблонам модуля
- *
- * @todo Вывод информации о авторе комментария
- */
+ * Метод, предназначенный для получения из БД всех комментариев, относящихся к указанному
+ * документу с последующим выводом в Публичной части.
+ *
+ * @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;
+{
+ 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();
+ // Получаем ВСЕ настройки модуля разом
+ $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 (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;
- }
+ if (!in_array($user_group, $read_groups))
+ {
+ $assign['no_read_permission'] = 1;
+ }
- $assign['display_comments'] = 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'];
+ 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();
+ $comments = array();
- if ($settings['comment_use_page_nav'] == 1)
- {
- $limit = $settings['comment_page_nav_count'];
- $start = get_current_page() * $limit - $limit;
+ 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();
+ $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 . "
- ");
+ $sql = $AVE_DB->Query("
+ SELECT *
+ FROM " . PREFIX . "_module_comment_info
+ WHERE document_id = '" . $document_id . "'
+ " . ($user_group == 1 ? '' : "AND comment_status = '1'") . "
+ ORDER BY comment_published ASC
+ LIMIT " . (int)$start . "," . (int)$limit . "
+ ");
- if ($num > $limit)
- {
- $page_nav = '{t} ';
- $page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
- $page_nav = preg_replace('/(?Query("SELECT * FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' " . ($user_group == 1 ? '' : "AND comment_status = '1'") . " ORDER BY comment_published ASC");
- $page_nav = '';
- }
+ if ($num > $limit)
+ {
+ $page_nav = '{t} ';
+ $page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
+ $page_nav = preg_replace('/(?Query("SELECT * FROM " . PREFIX . "_module_comment_info WHERE document_id = '" . $document_id . "' " . ($user_group == 1 ? '' : "AND comment_status = '1'") . " ORDER BY comment_published ASC");
+ $page_nav = '';
+ }
- $date_time_format = $AVE_Template->get_config_vars('COMMENT_DATE_TIME_FORMAT');
- 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['comment_published'] = ave_date_format($date_time_format, $row['comment_published']);
- $row['comment_changed'] = ave_date_format($date_time_format, $row['comment_changed']);
+ $date_time_format = $AVE_Template->get_config_vars('COMMENT_DATE_TIME_FORMAT');
+ $now = time();
- $comments[$row['parent_id']][] = $row;
- }
- }
- else
- {
- $comments = array();
- $page_nav = '';
- }
+ 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'] = '';
+ }
- $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;
+ // --- ИСПРАВЛЕННАЯ ЛОГИКА ТАЙМЕРА И ПРАВ ---
+ $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;
- // --- ВОТ ЭТА СТРОКА РЕШАЕТ ВСЁ ---
- // Она передает ВСЕ настройки модуля (включая твои новые поля) в шаблон
- $AVE_Template->assign($settings);
- // --------------------------------
+ // Проверяем авторство (зарегистрированный или аноним по ключу)
+ $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);
- $AVE_Template->assign($assign);
- $AVE_Template->assign('page_nav', $page_nav);
+ if ($is_admin) {
+ $row['can_edit'] = 1;
+ } elseif ($is_author) {
+ // Автор может править, только если время еще есть
+ if ($row['edit_time_left'] > 0) {
+ $row['can_edit'] = 1;
+ }
+ }
+ // ------------------------------------------
- $AVE_Template->display($tpl_dir . $this->_comments_tree_tpl);
- }
- }
+ $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);
+ }
+}
/**
* Метод, предназначенный для отображения формы при добавлении нового комментария.
*
@@ -345,7 +397,10 @@ function commentListShow($tpl_dir)
$session_captcha = $_SESSION['captcha_keystring'] ?? null;
$settings = $this->_commentSettingsGet();
-
+
+ // --- ПОЛУЧАЕМ КЛЮЧ АНОНИМА ---
+ $anon_key = $this->_getAnonKey();
+
if (! $ajax)
{
$link = rewrite_link(base64_decode($page));
@@ -400,7 +455,7 @@ function commentListShow($tpl_dir)
}
$allowed_mime = ['image/jpeg', 'image/png', 'image/gif'];
- $file_info = getimagesize($_FILES['comment_image']['tmp_name']);
+ $file_info = @getimagesize($_FILES['comment_image']['tmp_name']);
$file_size = $_FILES['comment_image']['size'];
// Проверка: это реально картинка и её размер не более 2Мб
@@ -420,13 +475,17 @@ function commentListShow($tpl_dir)
$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; // НАША НОВАЯ КОЛОНКА
+ $new_comment['comment_file'] = $comment_file_name;
$comment_text_raw = $_POST['comment_text'] ?? '';
$comment_max_chars = $settings['comment_max_chars'];
@@ -463,6 +522,11 @@ function commentListShow($tpl_dir)
{
$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;
@@ -479,193 +543,227 @@ function commentListShow($tpl_dir)
/**
* Метод, предназначенный для редактирования комментария в Публичной части
- * с поддержкой удаления и замены изображения
+ * с поддержкой анонимных пользователей и ограничением по времени.
*/
function commentPostEdit($comment_id)
{
global $AVE_DB;
- // --- 1. ОПРЕДЕЛЯЕМ ПОЛЬЗОВАТЕЛЯ ---
- // Используем (int), чтобы гарантированно иметь число для SQL
+ // 1. Инициализация данных
+ $comment_id = (int)$comment_id;
$user_id = (int)($_SESSION['user_id'] ?? 0);
$user_group = (int)(defined('UGROUP') ? UGROUP : 0);
-
- // Разрешаем вход всем системным группам (1, 2, 3, 4).
- // Если группа не определена (0), значит это "неопознанная сущность" — выходим.
- if ($user_group <= 0) exit;
+ $anon_key = $this->_getAnonKey(); // Наш метод получения куки анонима
- $post_text = $_POST['text'] ?? '';
- $delete_file = (isset($_POST['delete_image']) && $_POST['delete_image'] == 1);
-
- // Очищаем ID комментария
- $comment_id = (int)$comment_id;
- if ($comment_id <= 0) exit;
-
- // --- 2. ПОЛУЧАЕМ ДАННЫЕ С ПРОВЕРКОЙ ПРАВ ---
- // Если не админ (группа 1), то база вернет результат ТОЛЬКО если автор совпадает.
- // Для анонимов (группа 2) здесь подставится автор_id = 0, что корректно.
- $sql_author_check = ($user_group !== 1) ? "AND msg.comment_author_id = '" . $user_id . "'" : "";
+ if ($comment_id <= 0 || $user_group <= 0) exit('INVALID_ID');
+ // 2. Получаем данные комментария и настройки модуля (JOIN)
+ // Нам нужно знать время создания и кто автор, а также лимиты из настроек
$row = $AVE_DB->Query("
- SELECT
- msg.parent_id,
- msg.comment_text,
- msg.comment_file,
- cmnt.comment_user_groups,
- cmnt.comment_max_chars,
- cmnt.comment_need_approve
- FROM
- " . PREFIX . "_module_comment_info AS msg,
- " . PREFIX . "_module_comments AS cmnt
- WHERE cmnt.comment_active = '1'
- AND msg.Id = '" . $comment_id . "'
- " . $sql_author_check . "
+ 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 !== false)
- {
- $comment_max_chars = ($row['comment_max_chars'] != '' && $row['comment_max_chars'] > 10) ? $row['comment_max_chars'] : 200;
-
- // --- ТВОЯ ОРИГИНАЛЬНАЯ ОБРАБОТКА ТЕКСТА ---
- $comment_text = $post_text;
- $comment_text = preg_replace_callback('/([0-9a-f]{1,7});/', 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);
- $comment_text = str_replace(array("
\n", "
\n", "
\n"), "\n", $comment_text);
- $comment_text = strip_tags($comment_text);
-
- $comment_text_cut = mb_substr($comment_text, 0, $comment_max_chars);
- $message_length = mb_strlen($comment_text_cut);
- if (mb_strlen($comment_text) > $comment_max_chars) $comment_text_cut .= '…';
+ if (!$row) exit('NOT_FOUND');
- $is_admin = ($user_group == 1);
- // Проверяем, разрешено ли группе (2 или 4) редактирование в настройках модуля
- $is_allowed_group = in_array($user_group, explode(',', $row['comment_user_groups']));
-
- if ($is_admin || ($is_allowed_group && $message_length > 3))
- {
- $upload_dir = BASE_DIR . '/uploads/comments/';
- $new_file_sql = "";
+ // 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);
- // 1. ЗАМЕНА ФАЙЛА
- if (isset($_FILES['comment_image']) && $_FILES['comment_image']['error'] == UPLOAD_ERR_OK) {
- if (!is_dir($upload_dir)) @mkdir($upload_dir, 0777, true);
-
- // Удаляем старый файл, если он был
- if (!empty($row['comment_file'])) {
- $old_file = $upload_dir . $row['comment_file'];
- if (file_exists($old_file)) @unlink($old_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) . "'";
- $delete_file = false;
- }
- }
- // 2. УДАЛЕНИЕ ФАЙЛА
- elseif ($delete_file && !empty($row['comment_file'])) {
- $old_file = $upload_dir . $row['comment_file'];
- if (file_exists($old_file)) @unlink($old_file);
- $new_file_sql = ", comment_file = ''";
- }
-
- // --- ОБНОВЛЕНИЕ БАЗЫ ---
- $AVE_DB->Query("
- UPDATE " . PREFIX . "_module_comment_info
- SET
- comment_changed = '" . time() . "',
- comment_status = '" . intval(!(bool)$row['comment_need_approve']) . "',
- comment_text = '" . addslashes($comment_text_cut) . "'
- $new_file_sql
- WHERE
- Id = '" . $comment_id . "'
- ");
-
- // Возвращаем текст для обновления на странице
- echo htmlspecialchars($comment_text_cut, ENT_QUOTES);
+ // Если не админ, проверяем: автор ли это и не вышло ли время
+ if (!$is_admin) {
+ if (!$is_author) exit('NOT_AUTHOR');
+ if (!$is_time_ok) {
+ echo "TIME_EXPIRED"; // Этот ответ поймает JS
exit;
}
- // Если прав на редактирование нет, возвращаем старый текст
- echo htmlspecialchars($row['comment_text'], ENT_QUOTES);
}
+
+ // 4. Обработка текста (ТВОИ ОРИГИНАЛЬНЫЕ РЕГУЛЯРКИ)
+ $comment_text = $_POST['text'] ?? '';
+
+ // Декодируем HEX и десятичные сущности (твоя логика)
+ $comment_text = preg_replace_callback('/([0-9a-f]{1,7});/i', function($matches) { return chr(hexdec($matches[1])); }, $comment_text);
+ $comment_text = preg_replace_callback('/([0-9]{1,7});/', function($matches) { return chr($matches[1]); }, $comment_text);
+
+ $comment_text = stripslashes($comment_text);
+ // Заменяем брейки на переносы строк для корректной работы mb_substr и strip_tags
+ $comment_text = str_replace(array("
\n", "
\n", "
\n", "
", "
"), "\n", $comment_text);
+ $comment_text = strip_tags($comment_text);
+
+ $max = ($row['comment_max_chars'] > 10) ? (int)$row['comment_max_chars'] : 1000;
+ $comment_text_cut = mb_substr($comment_text, 0, $max);
+ if (mb_strlen($comment_text) > $max) $comment_text_cut .= '…';
+
+ // 5. Работа с изображениями
+ $new_file_sql = "";
+ $upload_dir = BASE_DIR . '/uploads/comments/';
+
+ // Вариант А: Загрузка нового файла взамен старого
+ if (isset($_FILES['comment_image']) && $_FILES['comment_image']['error'] == UPLOAD_ERR_OK) {
+ if (!is_dir($upload_dir)) @mkdir($upload_dir, 0775, true);
+
+ // Удаляем старый файл физически
+ if (!empty($row['comment_file']) && file_exists($upload_dir . $row['comment_file'])) {
+ @unlink($upload_dir . $row['comment_file']);
+ }
+
+ $file_ext = strtolower(pathinfo($_FILES['comment_image']['name'], PATHINFO_EXTENSION));
+ $new_file_name = 'comm_' . time() . '_' . rand(100, 999) . '.' . $file_ext;
+
+ if (move_uploaded_file($_FILES['comment_image']['tmp_name'], $upload_dir . $new_file_name)) {
+ $new_file_sql = ", comment_file = '" . addslashes($new_file_name) . "'";
+ }
+ }
+ // Вариант Б: Просто удаление изображения (если стоит галочка в форме)
+ elseif (isset($_POST['delete_image']) && $_POST['delete_image'] == 1) {
+ if (!empty($row['comment_file']) && file_exists($upload_dir . $row['comment_file'])) {
+ @unlink($upload_dir . $row['comment_file']);
+ }
+ $new_file_sql = ", comment_file = ''";
+ }
+
+ // 6. Обновление базы данных
+ // Если в настройках стоит "нужна премодерация", после правки статус меняется на 0 (если не админ)
+ $new_status = $is_admin ? (int)$row['comment_status'] : ($row['comment_need_approve'] == '1' ? 0 : 1);
+
+ $AVE_DB->Query("
+ UPDATE " . PREFIX . "_module_comment_info
+ SET
+ comment_changed = '" . time() . "',
+ comment_text = '" . addslashes($comment_text_cut) . "',
+ comment_status = '" . $new_status . "'
+ $new_file_sql
+ WHERE
+ Id = '" . $comment_id . "'
+ ");
+
+ // Возвращаем текст для JS, чтобы он обновил блок без перезагрузки страницы
+ echo htmlspecialchars($comment_text_cut, ENT_QUOTES);
exit;
}
/**
- * Метод, предназначенный для удаления комментария. Если комментарий содержал какие-либо ответы на него,
- * то все ответы также будут удалены вместе с родительским комментарием.
- *
- * @param int $comment_id - идентификатор комментария
- */
+ * Метод, предназначенный для удаления комментария.
+ * Реализует логику мягкого удаления (затирки) при наличии ответов
+ * и полную очистку при их отсутствии.
+ *
+ * @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) exit;
+ if ($comment_id <= 0) die('Ошибка: Неверный ID');
- // --- ШАГ 0. ПРОВЕРКА ПРАВ (БЕЗОПАСНОСТЬ) ---
-
- // Сначала узнаем, кто автор комментария, который хотят удалить
+ // --- ШАГ 0. ПОЛУЧЕНИЕ ДАННЫХ И ПРОВЕРКА ПРАВ ---
+
$comment_data = $AVE_DB->Query("
- SELECT comment_author_id
+ SELECT comment_author_id, anon_key, comment_published, comment_file
FROM " . PREFIX . "_module_comment_info
WHERE Id = '" . $comment_id . "'
")->FetchAssocArray();
- if (!$comment_data) exit; // Если комментария нет, и удалять нечего
+ if (!$comment_data) die('Ошибка: Комментарий не найден');
- $author_id = (int)$comment_data['comment_author_id'];
+ // Определяем пользователя и его группу (приоритет сессии над константой)
$current_user_id = (int)($_SESSION['user_id'] ?? 0);
- $user_group = (int)(defined('UGROUP') ? UGROUP : 0);
+ $user_group = (int)($_SESSION['user_group'] ?? (defined('UGROUP') ? UGROUP : 0));
+ $anon_key = $this->_getAnonKey();
$can_delete = false;
- // ПРАВО АДМИНА: Если группа 1 — разрешаем всё сразу
+ // 1. ПРАВО АДМИНА
if ($user_group === 1) {
$can_delete = true;
- }
- // ПРАВО АВТОРА: Если залогинен и ID совпадает с автором — разрешаем
- elseif ($current_user_id > 0 && $current_user_id === $author_id) {
- $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 (!$can_delete) {
- exit('Доступ запрещен');
- }
-
- // --- ШАГ 1. ОЧИСТКА ФАЙЛОВ (ТОЛЬКО ЕСЛИ ПРАВА ЕСТЬ) ---
-
- $upload_dir = BASE_DIR . '/uploads/comments/';
-
- $res = $AVE_DB->Query("
- SELECT comment_file
- FROM " . PREFIX . "_module_comment_info
- WHERE (Id = '" . $comment_id . "' OR parent_id = '" . $comment_id . "')
- AND comment_file != ''
- ");
-
- while ($row = $res->FetchAssocArray()) {
- $file_path = $upload_dir . $row['comment_file'];
- if (file_exists($file_path)) {
- @unlink($file_path);
+ if ($is_author && $is_time_ok) {
+ $can_delete = true;
}
}
- // --- ШАГ 2. УДАЛЕНИЕ ИЗ БАЗЫ ---
+ if (!$can_delete) {
+ // Вместо редиректа отдаем статус 403, чтобы JS-обработчик понял причину
+ header('HTTP/1.1 403 Forbidden');
+ die('Доступ запрещен или время на удаление истекло');
+ }
- // Удаляем сам коммент
- $AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id = '" . $comment_id . "'");
+ // --- ШАГ 1. ПРОВЕРКА НАЛИЧИЯ ОТВЕТОВ ---
+
+ $has_children = $AVE_DB->Query("
+ SELECT COUNT(*)
+ FROM " . PREFIX . "_module_comment_info
+ WHERE parent_id = '" . $comment_id . "'
+ ")->GetCell();
- // Удаляем все ответы на него
- $AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE parent_id = '" . $comment_id . "' AND parent_id != 0");
+ $upload_dir = BASE_DIR . '/uploads/comments/';
- echo "OK"; // Даем сигнал фронтенду, что всё прошло успешно
- exit;
+ 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 = "Комментарий удален автором.";
+ $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)
diff --git a/css/mod_comment_styles.css b/css/mod_comment_styles.css
index 0446f6f..7bd35a4 100644
--- a/css/mod_comment_styles.css
+++ b/css/mod_comment_styles.css
@@ -155,4 +155,41 @@
color: #fff;
background-color: #0b5ed7;
border-color: #0b5ed7;
-}
\ No newline at end of file
+}
+
+
+
+/* Фиксируем подвал комментария: высота и выравнивание вправо */
+.mod_comment_footer,
+[id^="timer_container_"],
+.card-footer {
+ display: flex !important;
+ align-items: center !important;
+ /* ПРИЖИМАЕМ КНОПКИ ВПРАВО */
+ justify-content: flex-end !important;
+
+ /* ФИКСИРОВАННАЯ ВЫСОТА (чтобы не прыгало как на Screenshot 5) */
+ min-height: 48px !important;
+ height: 48px !important;
+
+ padding: 0 1.25rem !important;
+ background-color: #f8f9fa !important;
+ border-top: 1px solid rgba(0,0,0,.125) !important;
+}
+
+/* Отступы между кнопками, чтобы они не слипались справа */
+.mod_comment_footer > a,
+.mod_comment_footer > button,
+.mod_comment_footer .btn {
+ margin-left: 15px !important; /* Отступ слева, так как кнопки прижаты вправо */
+ margin-right: 0 !important;
+ text-decoration: none !important;
+}
+
+/* Гарантируем, что текст таймера тоже будет справа */
+[id^="timer_container_"] {
+ text-align: right !important;
+}
+
+/* Если нужно, чтобы кнопки "Ответить" и "Удалить" были разнесены (одну влево, другие вправо) */
+/* .mod_comment_footer { justify-content: space-between !important; } */
\ No newline at end of file
diff --git a/js/comment.js b/js/comment.js
index 2bee965..e0b7aa4 100644
--- a/js/comment.js
+++ b/js/comment.js
@@ -1,428 +1,220 @@
/* ====================================================================
ОБЕРТКА ДЛЯ ОЖИДАНИЯ JQUERY
- ==================================================================== */
+ ==================================================================== */
(function waitForJQuery() {
if (typeof jQuery === 'undefined') {
setTimeout(waitForJQuery, 10);
} else {
- (function($){
+ (function($) {
- if (typeof aveabspath !== 'undefined') {
- // aveabspath = 'https://bag.local/';
+ if (typeof aveabspath === 'undefined') {
+ window.aveabspath = '/';
}
- /* Limit symbols */
- (function($){
- $.fn.extend({
- limit: function(limit, element){
- var interval, f;
+ /* --- ТАЙМЕРЫ --- */
+ function initCommentTimers() {
+ $('.timer-count:not(.timer-running)').each(function() {
+ var $timer = $(this);
+ $timer.addClass('timer-running');
+ var timeLeft = parseInt($timer.attr('data-left'));
+ var cid = $timer.attr('id').replace('timer_', '');
+
+ var countdown = setInterval(function() {
+ timeLeft--;
+ if (timeLeft <= 0) {
+ clearInterval(countdown);
+ $('#controls_' + cid).fadeOut(300);
+ $('#timer_container_' + cid).html('Время на правку истекло');
+ return;
+ }
+ var minutes = Math.floor(timeLeft / 60);
+ var seconds = timeLeft % 60;
+ $timer.text(minutes + ':' + (seconds < 10 ? '0' : '') + seconds);
+ $timer.attr('data-left', timeLeft);
+ }, 1000);
+ $timer.data('interval-id', countdown);
+ });
+ }
+
+ /* Limit Plugin (Исправленный с возвратом контекста) */
+ $.fn.extend({
+ limit: function(limit, element) {
+ return this.each(function() {
var self = $(this);
-
- function substring(){
- var val = $(self).val();
- var length = val.length;
-
- if(length > limit){
- $(self).val($(self).val().substring(0,limit));
- length = limit;
- }
-
- if(typeof element !== 'undefined'){
- if($(element).html() != limit - length){
- $(element).html((limit - length <= 0) ? '0' : limit - length);
- }
+ function substring() {
+ var val = self.val();
+ if (val.length > limit) self.val(val.substring(0, limit));
+ if (typeof element !== 'undefined') {
+ var remaining = limit - self.val().length;
+ $(element).html(remaining < 0 ? '0' : remaining);
}
}
-
- $(this).focus(function(){
- interval = window.setInterval(substring, 100);
- });
-
- $(this).blur(function(){
- clearInterval(interval);
- substring();
- });
-
+ self.on('focus keyup paste', substring);
substring();
- }
- })
- })(jQuery);
+ });
+ }
+ });
- function getCaptha(){
+ function getCaptha() {
var now = new Date();
$('#captcha img').attr('src', aveabspath + 'inc/captcha.php?cd=' + now.getTime());
}
- function cAction(obj, action){
- var cid;
-
- if (action === 'open' || action === 'close') {
- cid = DOC_ID;
- } else {
- var $link = $(obj).closest('a.mod_comment_answer');
- cid = $link.attr('rel');
-
- if (typeof cid === 'undefined' || cid === false || cid === '') {
- cid = $(obj).parents('.mod_comment_box').attr('id');
- }
- }
-
- if (typeof cid === 'undefined' || cid === false || cid === '') {
- console.error("Comment ID not found for action: " + action);
- return;
- }
-
- if (action === 'answer'){
- $('#parent_id').val(cid);
- $('#mod_comment_new').insertBefore('#end' + cid).show();
- return;
- }
-
- // Удаление и модерация (Отправляем запрос, PHP проверит права)
- $.get(aveabspath + 'index.php', {
- module: 'comment',
- action: action,
- docid: DOC_ID,
- Id: cid
- }, function(data){
- if (action === 'delete'){
- $(obj).parents('.mod_comment_comment').eq(0).remove();
- }
-
- if (action === 'open'){
- var $openButton = $('#mod_comment_open');
- $openButton.attr('id', 'mod_comment_close').html(' ' + COMMENT_SITE_CLOSE);
- $openButton.removeClass('btn-outline-success').addClass('btn-outline-danger');
- }
-
- if (action === 'close'){
- var $closeButton = $('#mod_comment_close');
- $closeButton.attr('id', 'mod_comment_open').html(' ' + COMMENT_SITE_OPEN);
- $closeButton.removeClass('btn-outline-danger').addClass('btn-outline-success');
- }
-
- if (action === 'unlock'){
- $(obj).removeClass('mod_comment_unlock text-success')
- .addClass('mod_comment_lock text-dark')
- .attr('title', COMMENT_LOCK_LINK)
- .find('i')
- .removeClass('bi-unlock-fill').addClass('bi-lock-fill');
- }
- if (action === 'lock'){
- $(obj).removeClass('mod_comment_lock text-dark')
- .addClass('mod_comment_unlock text-success')
- .attr('title', COMMENT_UNLOCK_LINK)
- .find('i')
- .removeClass('bi-lock-fill').addClass('bi-unlock-fill');
- }
- });
- }
-
- function validate(formData, jqForm, options){
- $('.alert').remove();
- var form = jqForm ? jqForm[0] : $('#mod_comment_new form')[0];
-
- if (form.comment_author_name && !form.comment_author_name.value){
- alert(COMMENT_ERROR_AUTHOR);
- $(form.comment_author_name).focus();
- return false;
- }
-
- if (form.comment_author_email && !form.comment_author_email.value){
- alert(COMMENT_ERROR_EMAIL);
- $(form.comment_author_email).focus();
- return false;
- }
-
- if (typeof REQ_F1 !== 'undefined' && REQ_F1 == '1') {
- if (form.comment_author_website && !form.comment_author_website.value) {
- alert("Пожалуйста, заполните поле: " + NAME_F1);
- $(form.comment_author_website).focus();
+ function validate(form) {
+ var checks = [
+ { field: form.comment_author_name, msg: "Введите имя" },
+ { field: form.comment_author_email, msg: "Введите Email" },
+ { field: form.comment_text, msg: "Введите текст" }
+ ];
+ for (var i = 0; i < checks.length; i++) {
+ if (checks[i].field && !checks[i].field.value.trim()) {
+ alert(checks[i].msg);
+ $(checks[i].field).focus();
return false;
}
}
-
- if (typeof REQ_F2 !== 'undefined' && REQ_F2 == '1') {
- if (form.comment_author_city && !form.comment_author_city.value) {
- alert("Пожалуйста, заполните поле: " + NAME_F2);
- $(form.comment_author_city).focus();
- return false;
- }
- }
-
- if (!form.comment_text || !form.comment_text.value){
- alert(COMMENT_ERROR_TEXT);
- if (form.comment_text) $(form.comment_text).focus();
- return false;
- }
-
- if (IS_IM && form.securecode && !form.securecode.value){
- alert(COMMENT_ERROR_CAPTCHA);
- $(form.securecode).focus();
- return false;
- }
-
return true;
}
- // Функция для создания формы редактирования
- function createEditForm(cid, revert, targetElement) {
- var currentImg = $('#' + cid).find('.mod_comment_attached_image img').attr('src');
- var deletePhotoHtml = '';
+ /* --- ДЕЙСТВИЯ (DELETE, ANSWER, LOCK) --- */
+ function cAction(obj, action) {
+ var $btn = $(obj);
+ var cid = $btn.data('id');
+ if (!cid) return;
- if (currentImg) {
- deletePhotoHtml = '
' + COMMENT_CHARS_LEFT + '
'; - var buttonSave = ' '; - var buttonReset = ''; - targetElement.after( - '