From f8d479cd80bb7d79b8e3add07db88dc089e37379 Mon Sep 17 00:00:00 2001 From: Repellent Date: Fri, 19 Dec 2025 02:07:34 +0500 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=20=D1=80=D0=B5=D1=88=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BC=20=D0=B6=D0=B8=D0=B7=D0=BD=D0=B8=20=D0=BA?= =?UTF-8?q?=D1=83=D0=BA=D0=B8=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- class/comment.php | 596 +++++++++++++++++++------------- css/mod_comment_styles.css | 39 ++- js/comment.js | 538 +++++++++------------------- sql.php | 18 +- templates/comments_tree.tpl | 4 + templates/comments_tree_sub.tpl | 94 +++-- 6 files changed, 627 insertions(+), 662 deletions(-) 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('/&#x([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('/&#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("
\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 = '
' + - '' + - '' + - '
'; + if (action === 'answer') { + $('#parent_id').val(cid); + $('#mod_comment_new').insertBefore('#end' + cid).show(); + $('html, body').animate({ scrollTop: $('#mod_comment_new').offset().top - 150 }, 500); + return; } - - // Блок для превью нового фото - var editPreviewHtml = '
' + - '' + - '
'; - - // Поле для загрузки НОВОГО фото - var uploadNewPhotoHtml = '
' + - '' + - '' + - editPreviewHtml + - '
'; - - var textarea = '

'; - var charsLeft = '

' + COMMENT_CHARS_LEFT + '

'; - var buttonSave = ' '; - var buttonReset = ''; - targetElement.after( - '
' + COMMENT_EDIT_TITLE - + '' + textarea + deletePhotoHtml + uploadNewPhotoHtml + charsLeft + buttonSave + buttonReset + '
' - ).remove(); - - $('.saveButton').off('click').on('click', function(){saveChanges(this, false, cid);}); - $('.cancelButton').off('click').on('click', function(){saveChanges(this, revert, cid);}); - - $('#ta_' + cid).limit(MAX_CHARS, '#charsLeft_' + cid); + $.get(aveabspath + 'index.php', { + module: 'comment', + action: action, + docid: typeof DOC_ID !== 'undefined' ? DOC_ID : '', + Id: cid + }, function() { + if (action === 'delete') { + var $commentBlock = $btn.closest('.mod_comment_comment'); + if ($commentBlock.find('#mod_comment_new').length > 0) { + $('#mod_comment_new').insertAfter($('.mod_comment_comment').last()); + } + $commentBlock.fadeOut(300, function() { $(this).remove(); }); + } + if (action === 'unlock' || action === 'lock') { + var isLock = (action === 'lock'); + $btn.toggleClass('mod_comment_lock mod_comment_unlock text-dark text-success') + .attr('title', isLock ? 'Разблокировать' : 'Заблокировать') + .find('i').toggleClass('bi-lock-fill bi-unlock-fill'); + } + }); } - function setClickable(){ - $('.editable_text').off('click'); - - $('.editable_text').click(function(){ - var $this = $(this); - var cid = $this.parents('.mod_comment_box').attr('id'); - var revert = $this.html(); - createEditForm(cid, revert, $this); - }) - .attr('title', COMMENT_EDIT_LINK) - .mouseover(function(){$(this).addClass('editable');}) - .mouseout(function(){$(this).removeClass('editable');}); + /* --- ИНИЦИАЛИЗАЦИЯ --- */ + $(document).ready(function() { + initCommentTimers(); + var $doc = $(document); - $('#in_message').limit(MAX_CHARS, '#charsLeft_new'); - } + // Делегирование с защитой от двойного клика через .off() + $doc.off('click', '.mod_comment_answer').on('click', '.mod_comment_answer', function(e) { + e.preventDefault(); cAction(this, 'answer'); + }); - function saveChanges(obj, cancel, cid){ - var $box = $(obj).parents('.box'); + $doc.off('click', '.mod_comment_delete').on('click', '.mod_comment_delete', function(e) { + e.preventDefault(); + if (confirm('Удалить этот комментарий?')) cAction(this, 'delete'); + }); - if (!cancel){ - var t = $box.find('#ta_' + cid).val(); - var deleteImg = $('#del_img_' + cid).is(':checked') ? 1 : 0; + $doc.off('click', '.mod_comment_lock, .mod_comment_unlock').on('click', '.mod_comment_lock, .mod_comment_unlock', function(e) { + e.preventDefault(); + cAction(this, $(this).hasClass('mod_comment_lock') ? 'lock' : 'unlock'); + }); + + // Редактирование + $doc.off('click', '.mod_comment_edit').on('click', '.mod_comment_edit', function(e) { + e.preventDefault(); + var cid = $(this).data('id'); + var $textBlock = $('#comment_wrapper_' + cid).find('.mod_comment_text'); + $('.edit-form-container').remove(); $('.mod_comment_text').show(); + var cleanText = $textBlock.html().replace(//mg, "\n").trim(); + var currentImg = $('#comment_wrapper_' + cid).find('.mod_comment_attached_image img').attr('src'); + + var editHtml = ` +
+ + ${currentImg ? `
` : ''} + +
+ + + Осталось: +
+
`; + $textBlock.hide().after(editHtml); + $('#ta_' + cid).limit(1000, '#charsLeft_' + cid).focus(); + }); + + $doc.off('click', '.cancelButton').on('click', '.cancelButton', function() { + $(this).closest('.edit-form-container').prev('.mod_comment_text').show(); + $(this).closest('.edit-form-container').remove(); + }); + + $doc.off('click', '.saveButton').on('click', '.saveButton', function() { + var $btn = $(this); + var cid = $btn.data('id'); var fd = new FormData(); - fd.append('module', 'comment'); - fd.append('action', 'edit'); - fd.append('Id', cid); - fd.append('text', t); - fd.append('delete_image', deleteImg); - - var fileInput = $('#new_file_' + cid)[0]; - if (fileInput && fileInput.files[0]) { - fd.append('comment_image', fileInput.files[0]); - } - + fd.append('module', 'comment'); fd.append('action', 'edit'); fd.append('Id', cid); + fd.append('text', $('#ta_' + cid).val()); + fd.append('delete_image', $('#del_img_' + cid).is(':checked') ? 1 : 0); + var file = $('#new_file_' + cid)[0].files[0]; + if (file) fd.append('comment_image', file); + $.ajax({ url: aveabspath + 'index.php?ajax=1', - type: 'POST', - data: fd, - processData: false, - contentType: false, - success: function(txt){ - if (deleteImg == 1 || (fileInput && fileInput.files[0])) { - location.reload(); - } else { - $box.after('
' + txt + '
').remove(); - var now = new Date(); - var date = now.toLocaleString(); - $('#' + cid).find('.mod_comment_changed').html(' (' + COMMENT_TEXT_CHANGED + ' ' + date + ')'); - setClickable(); - } - }, - error: function(xhr, status, error) { - console.error("AJAX Error (Edit):", status, error); - alert("Ошибка при сохранении."); - } + type: 'POST', data: fd, processData: false, contentType: false, + beforeSend: function() { $btn.prop('disabled', true).text('...'); }, + success: function() { location.reload(); } }); - } - else { - $box.after('
' + cancel + '
').remove(); - setClickable(); - } - } - - function displayNewComment(data){ - if (data === 'wrong_securecode'){ - $('#captcha').after('
'+COMMENT_WRONG_CODE+'
'); - $('#securecode').focus(); - } - else { - $('#end' + $('#parent_id').val()).before(data); - $('#parent_id').val(''); - $('#mod_comment_new form')[0].reset(); - $('#image_preview_wrapper').addClass('d-none'); - $('#image_preview_img').attr('src', '#'); - - $('#mod_comment_new').insertAfter('#end'); - setClickable(); - } - getCaptha(); - } - - $(document).ready(function(){ - - setClickable(); - - // ЛОГИКА ПРЕДПРОСМОТРА ДЛЯ ФОРМ РЕДАКТИРОВАНИЯ - $(document).on('change', '.edit-file-input', function() { - var cid = $(this).data('cid'); - var file = this.files[0]; - var previewWrapper = $('#edit_preview_wrapper_' + cid); - var previewImg = $('#edit_preview_img_' + cid); - - if (file) { - var reader = new FileReader(); - reader.onload = function(e) { - previewImg.attr('src', e.target.result); - previewWrapper.removeClass('d-none'); - } - reader.readAsDataURL(file); - } else { - previewWrapper.addClass('d-none'); - } }); - // ЛОГИКА ПРЕДПРОСМОТРА ДЛЯ НОВОГО КОММЕНТАРИЯ - $(document).on('change', '#comment_image', function() { - const file = this.files[0]; - const previewWrapper = $('#image_preview_wrapper'); - const previewImg = $('#image_preview_img'); - const errorDiv = $('#file_error'); - - if (errorDiv.length) errorDiv.addClass('d-none').text(''); - - if (file) { - if (file.size > 2 * 1024 * 1024) { - if (errorDiv.length) errorDiv.removeClass('d-none').text('Файл слишком большой (макс. 2Мб)'); - this.value = ''; - previewWrapper.addClass('d-none'); - return; - } - - const reader = new FileReader(); - reader.onload = function(e) { - previewImg.attr('src', e.target.result); - previewWrapper.removeClass('d-none'); - } - reader.readAsDataURL(file); - } - }); - - $(document).on('click', '#remove_image_btn', function() { - $('#comment_image').val(''); - $('#image_preview_wrapper').addClass('d-none'); - $('#image_preview_img').attr('src', '#'); - }); - - $(document).on('click', '#captcha img', function(){getCaptha();}); - - $(document).on('click', '#reload_captcha', function(e){ + // ОТПРАВКА НОВОГО КОММЕНТАРИЯ + $doc.off('submit', '#mod_comment_new form').on('submit', '#mod_comment_new form', function(e) { e.preventDefault(); - getCaptha(); - }); + if (!validate(this)) return false; - $(document).on('click', '.mod_comment_answer', function(e){ - e.preventDefault(); - cAction(this, 'answer'); - }); - - $(document).on('click', '.mod_comment_edit', function(e){ - e.preventDefault(); - var cid = $(this).parents('.mod_comment_box').attr('id'); - var commentTextBlock = $('#' + cid).find('.mod_comment_text'); - var revert = commentTextBlock.html(); - createEditForm(cid, revert, commentTextBlock); - }); + var $form = $(this); + var $btn = $form.find('[type="submit"]'); + var originalBtnText = $btn.text(); - /* --- УДАЛЕНИЕ: Доступно всем, у кого в шаблоне есть кнопка --- */ - $(document).on('click', '.mod_comment_delete', function(e){ - e.preventDefault(); - var $deleteButton = $(this); - var $modal = $('#deleteCommentModal'); - - $('#confirmDeleteButton').off('click').on('click', function() { - cAction($deleteButton[0], 'delete'); - var modalInstance = bootstrap.Modal.getInstance($modal[0]) || new bootstrap.Modal($modal[0]); - modalInstance.hide(); - }); - - var modal = new bootstrap.Modal($modal[0]); - modal.show(); - }); - - /* --- ТОЛЬКО ДЛЯ АДМИНИСТРАТОРОВ (UGROUP == 1) --- */ - if (typeof UGROUP !== 'undefined' && UGROUP == 1) { - $(document).on('click', '.mod_comment_lock', function(e){e.preventDefault(); cAction(this, 'lock');}); - $(document).on('click', '.mod_comment_unlock', function(e){e.preventDefault(); cAction(this, 'unlock');}); - - $(document).on('click', '#mod_comment_open', function(e){e.preventDefault(); cAction(this, 'open');}); - $(document).on('click', '#mod_comment_close', function(e){e.preventDefault(); cAction(this, 'close');}); - } - - $('#mod_comment_new form').on('submit', function(e){ - e.preventDefault(); - var form = $(this); - if (!validate(null, form, null)) return false; - - var submitButton = form.find('input[type="submit"], button[type="submit"]').first(); - var originalButtonText = submitButton.text(); - var formData = new FormData(this); - $.ajax({ url: aveabspath + 'index.php?ajax=1', - type: 'POST', - data: formData, + type: 'POST', + data: new FormData(this), processData: false, - contentType: false, - beforeSend: function() { - submitButton.prop('disabled', true).text('Отправка...'); + contentType: false, + beforeSend: function() { + $btn.prop('disabled', true).text('Отправка...'); }, success: function(data) { - displayNewComment(data); - submitButton.prop('disabled', false).text(originalButtonText); + if (data.includes('wrong_securecode')) { + alert("Код капчи введен неверно!"); + getCaptha(); + $btn.prop('disabled', false).text(originalBtnText); + } else { + location.reload(); + } }, error: function() { - alert("Ошибка при отправке."); - submitButton.prop('disabled', false).text(originalButtonText); + alert("Ошибка связи с сервером"); + $btn.prop('disabled', false).text(originalBtnText); } }); - return false; }); - - $('#buttonReset').click(function(){ - $('#parent_id').val(''); - $('#mod_comment_new form')[0].reset(); - $('#image_preview_wrapper').addClass('d-none'); - $('#mod_comment_new').insertAfter('#end'); + + $doc.off('click', '#captcha img, #reload_captcha').on('click', '#captcha img, #reload_captcha', function(e) { + e.preventDefault(); getCaptha(); }); }); diff --git a/sql.php b/sql.php index 2721eb1..bbacebd 100644 --- a/sql.php +++ b/sql.php @@ -3,7 +3,8 @@ /** * AVE.cms - Модуль Комментарии * - * Обновленная структура с поддержкой произвольных полей и загрузки изображений + * Обновленная структура с поддержкой произвольных полей, загрузки изображений + * и идентификации анонимных пользователей (anon_key) */ $module_sql_install = array(); @@ -49,6 +50,8 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comment_info` ( `comment_author_city` varchar(255) NOT NULL, `comment_author_website` varchar(255) NOT NULL, `comment_author_ip` varchar(15) NOT NULL, + /* НОВОЕ ПОЛЕ: КЛЮЧ АНОНИМА (MD5 = 32 символа) */ + `anon_key` varchar(32) DEFAULT NULL, `comment_published` int(10) unsigned NOT NULL default '0', `comment_changed` int(10) unsigned NOT NULL default '0', `comment_text` text NOT NULL, @@ -59,10 +62,11 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comment_info` ( PRIMARY KEY (`Id`), KEY `document_id` (`document_id`), KEY `parent_id` (`parent_id`), - KEY `comment_status` (`comment_status`) + KEY `comment_status` (`comment_status`), + KEY `anon_key` (`anon_key`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=0;"; -// Начальные данные (Добавлено: 0 для allow_files и 2048 для размера в конце) +// Начальные данные $module_sql_install[] = "INSERT INTO `%%PRFX%%_module_comments` VALUES (1, 1000, '1,3', '1,2,3,4', '0', '1', '1' , '0', '', 1, 0, '', 1, 0, '', 0, 2048);"; // ================================================================================= @@ -79,11 +83,13 @@ $module_sql_update[] = " LIMIT 1; "; -// Добавление поля для файла в основную таблицу (если модуль уже установлен) +// Добавление поля для файла (если модуль обновляется со старой версии) $module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comment_info` ADD `comment_file` VARCHAR(255) NOT NULL DEFAULT '' AFTER `comments_close`;"; - -// Добавление настроек загрузки (если модуль уже установлен) $module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD `comment_allow_files` TINYINT(1) NOT NULL DEFAULT '0';"; $module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD `comment_file_max_size` INT(10) NOT NULL DEFAULT '2048';"; +// НОВОЕ: Добавляем поле anon_key в существующую таблицу +$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comment_info` ADD `anon_key` VARCHAR(32) DEFAULT NULL AFTER `comment_author_ip`;"; +$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comment_info` ADD INDEX `anon_key` (`anon_key`);"; + ?> \ No newline at end of file diff --git a/templates/comments_tree.tpl b/templates/comments_tree.tpl index e2d3d40..b6078dd 100644 --- a/templates/comments_tree.tpl +++ b/templates/comments_tree.tpl @@ -251,6 +251,10 @@ var aveabspath = '{$ABS_PATH}'; + {/if} {/if} \ No newline at end of file diff --git a/templates/comments_tree_sub.tpl b/templates/comments_tree_sub.tpl index 5b771b2..6704f8d 100644 --- a/templates/comments_tree_sub.tpl +++ b/templates/comments_tree_sub.tpl @@ -1,12 +1,12 @@ {foreach from=$subcomments item=c} -
+
{if isset($smarty.request.subaction) && $smarty.request.subaction=='showonly' && isset($smarty.request.comment_id) && $smarty.request.comment_id==$c.Id}
{/if} -
+
@@ -28,7 +28,7 @@ {$c.comment_published} - {* НОВОЕ: Динамические поля в мета-данных комментария *} + {* Динамические поля в мета-данных комментария *} {if $comment_show_f1 == 1 && $c.comment_author_website} {$comment_name_f1|default:#COMMENT_YOUR_SITE#}: {$c.comment_author_website|stripslashes|escape} @@ -44,15 +44,19 @@ • IP:{$c.comment_author_ip} {/if} - {if isset($c.comment_changed) && $c.comment_changed > 1} ({#COMMENT_TEXT_CHANGED#} {$c.comment_changed}){/if} + + {if isset($c.comment_changed) && $c.comment_changed > 1} + ({#COMMENT_TEXT_CHANGED#} {$c.comment_changed}) + {/if} +
{* Текст комментария *} -
{$c.comment_text|escape|nl2br}
+
{$c.comment_text|stripslashes|nl2br}
- {* НОВОЕ: ВЫВОД ПРИКРЕПЛЕННОГО ИЗОБРАЖЕНИЯ *} + {* ВЫВОД ПРИКРЕПЛЕННОГО ИЗОБРАЖЕНИЯ *} {if !empty($c.comment_file)} -
+
-