From cc9bb25a867dd50fbc721c263e33c46f989f4ea5 Mon Sep 17 00:00:00 2001 From: Repellent Date: Sat, 20 Dec 2025 20:43:53 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B9=D1=82=D0=B8=D0=BD=D0=B3=20=D1=81=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=B2=D0=B5=D0=B7=D0=B4=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- class/comment.php | 96 +++++++++++++++++++++++++++++++++ js/comment.js | 54 +++++++++++++++++-- module.php | 5 ++ sql.php | 50 ++++++++++++----- templates/comments_tree_sub.tpl | 45 +++++++++++----- 5 files changed, 218 insertions(+), 32 deletions(-) diff --git a/class/comment.php b/class/comment.php index c554d44..7c8d270 100644 --- a/class/comment.php +++ b/class/comment.php @@ -777,6 +777,102 @@ function commentPostDelete($comment_id) die(); } +/** + * Метод для обработки голосования за комментарий + */ + function commentVote() + { + global $AVE_DB; + + $comment_id = (int)($_POST['comment_id'] ?? 0); + $vote_value = (int)($_POST['vote'] ?? 0); + $ajax = (isset($_POST['ajax']) && $_POST['ajax'] == 1); + + // Базовая проверка значения (от 1 до 5 звезд) + if ($comment_id <= 0 || $vote_value < 1 || $vote_value > 5) { + if ($ajax) { + if (ob_get_length()) ob_end_clean(); + echo 'error'; + exit; + } + return; + } + + // 1. Идентификация пользователя + $user_id = empty($_SESSION['user_id']) ? 0 : (int)$_SESSION['user_id']; + $anon_key = $this->_getAnonKey(); + + // 2. Получаем данные о комментарии (FetchRow исправлен) + $comment_data = $AVE_DB->Query(" + SELECT comment_author_id, anon_key + FROM " . PREFIX . "_module_comment_info + WHERE Id = '" . $comment_id . "' + ")->FetchRow(); + + if (!$comment_data) { + if ($ajax) { + if (ob_get_length()) ob_end_clean(); + echo 'error'; + exit; + } + return; + } + + // 3. Запрет голосовать за свой же комментарий + $is_author = false; + if ($user_id > 0 && $user_id == $comment_data->comment_author_id) $is_author = true; + if ($user_id == 0 && $anon_key == $comment_data->anon_key) $is_author = true; + + if ($is_author) { + if ($ajax) { + if (ob_get_length()) ob_end_clean(); + echo 'own_comment'; + exit; + } + return; + } + + // 4. Проверка на повторное голосование + $sql_check = "SELECT id FROM " . PREFIX . "_module_comment_votes WHERE comment_id = '" . $comment_id . "' AND "; + if ($user_id > 0) { + $sql_check .= "user_id = '" . $user_id . "'"; + } else { + $sql_check .= "anon_key = '" . $anon_key . "'"; + } + + if ($AVE_DB->Query($sql_check)->GetCell()) { + if ($ajax) { + if (ob_get_length()) ob_end_clean(); + echo 'already_voted'; + exit; + } + return; + } + + // 5. Записываем голос в лог + $AVE_DB->Query(" + INSERT INTO " . PREFIX . "_module_comment_votes + (comment_id, user_id, anon_key, vote_value, date_voted) + VALUES + ('" . $comment_id . "', '" . $user_id . "', '" . $anon_key . "', '" . $vote_value . "', '" . time() . "') + "); + + // 6. Обновляем агрегированные данные + $AVE_DB->Query(" + UPDATE " . PREFIX . "_module_comment_info + SET rating_sum = rating_sum + " . $vote_value . ", + rating_count = rating_count + 1 + WHERE Id = '" . $comment_id . "' + "); + + // 7. ФИНАЛ: Очищаем мусор и отдаем чистый ответ + if ($ajax) { + if (ob_get_length()) ob_end_clean(); // Выбрасываем все Warning-и и HTML + echo 'success'; + exit; + } + } + function commentAdminDelete($comment_id) { global $AVE_DB; diff --git a/js/comment.js b/js/comment.js index 7c81dcc..c9678a2 100644 --- a/js/comment.js +++ b/js/comment.js @@ -76,7 +76,7 @@ return true; } - /* --- ДЕЙСТВИЯ (DELETE, LOCK, OPEN/CLOSE) --- */ + /* --- ДЕЙСТВИЯ (DELETE, LOCK, OPEN/CLOSE, VOTE) --- */ function cAction(obj, action) { var $btn = $(obj); var cid = $btn.data('id'); @@ -119,6 +119,54 @@ initCommentTimers(); var $doc = $(document); + // --- ЛОГИКА ГОЛОСОВАНИЯ (РЕЙТИНГ) --- + $doc.on('mouseenter', '.star-item', function() { + $(this).prevAll().addBack().removeClass('bi-star').addClass('bi-star-fill'); + $(this).nextAll().removeClass('bi-star-fill').addClass('bi-star'); + }); + + $doc.on('mouseleave', '.comment-stars', function() { + // При уходе мыши визуально ничего не меняем до клика или рефреша + }); + + $doc.on('click', '.star-item', function() { + var $container = $(this).closest('.comment-rating-container'); + var commentId = $container.data('id'); + var voteValue = $(this).data('value'); + + $.ajax({ + // Используем относительный путь для избежания проблем с протоколом HTTPS/HTTP + url: 'index.php?module=comment&action=vote&ajax=1', + type: 'POST', + data: { + comment_id: commentId, + vote: voteValue, + ajax: 1 // Дублируем для надежности + }, + success: function(response) { + var res = response.toString().trim(); + + // Проверяем наличие ключевого слова в ответе (защита от варнингов PHP) + if (res.indexOf('success') !== -1) { + $container.html('Спасибо!'); + setTimeout(function(){ location.reload(); }, 1000); + } else if (res.indexOf('already_voted') !== -1) { + alert('Вы уже голосовали за этот комментарий.'); + } else if (res.indexOf('own_comment') !== -1) { + alert('Нельзя голосовать за свой собственный комментарий.'); + } else { + console.warn("Server response:", response); + alert('Ошибка при обработке голоса сервером.'); + } + }, + error: function(xhr) { + console.error("XHR Status:", xhr.status); + alert('Ошибка связи с сервером. Статус: ' + xhr.status); + } + }); + }); + // ------------------------------------ + // Глобальные кнопки управления $doc.on('click', '#mod_comment_close', function(e) { e.preventDefault(); cAction(this, 'close'); @@ -127,17 +175,15 @@ e.preventDefault(); cAction(this, 'open'); }); - // КНОПКА ОТВЕТА (ИСПРАВЛЕНО: перемещает форму, а не шлет AJAX) + // КНОПКА ОТВЕТА $doc.off('click', '.mod_comment_answer').on('click', '.mod_comment_answer', function(e) { e.preventDefault(); var cid = $(this).data('id'); var $form = $('#mod_comment_new'); - // Переносим форму и ставим parent_id $form.insertAfter('#comment_' + cid).show(); $('#parent_id').val(cid); - // Скролл к форме $('html, body').animate({ scrollTop: $form.offset().top - 150 }, 500); diff --git a/module.php b/module.php index 3433fe9..b328d8f 100644 --- a/module.php +++ b/module.php @@ -62,6 +62,11 @@ if (!defined('ACP') && isset($_REQUEST['module']) && $_REQUEST['module'] == 'com $comment->commentPostNew($tpl_dir); break; + // --- НОВОЕ: ОБРАБОТКА ГОЛОСОВАНИЯ (РЕЙТИНГ) --- + case 'vote': + $comment->commentVote(); + break; + // Если edit, тогда открываем форму для редактирования текста комментария case 'edit': if (!empty(UGROUP)) diff --git a/sql.php b/sql.php index bbacebd..1083092 100644 --- a/sql.php +++ b/sql.php @@ -3,8 +3,8 @@ /** * AVE.cms - Модуль Комментарии * - * Обновленная структура с поддержкой произвольных полей, загрузки изображений - * и идентификации анонимных пользователей (anon_key) + * Обновленная структура с поддержкой рейтинга (звезд), + * идентификации анонимных пользователей и загрузки файлов. */ $module_sql_install = array(); @@ -13,6 +13,7 @@ $module_sql_update = array(); $module_sql_deinstall[] = "DROP TABLE IF EXISTS `%%PRFX%%_module_comments`;"; $module_sql_deinstall[] = "DROP TABLE IF EXISTS `%%PRFX%%_module_comment_info`;"; +$module_sql_deinstall[] = "DROP TABLE IF EXISTS `%%PRFX%%_module_comment_votes`;"; // ================================================================================= // 1. УСТАНОВКА МОДУЛЯ (CREATE TABLE) @@ -27,14 +28,12 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comments` ( `comment_use_antispam` enum('1','0') NOT NULL default '1', `comment_use_page_nav` enum('1','0') NOT NULL default '1', `comment_page_nav_count` varchar(5) NOT NULL, - /* УНИВЕРСАЛЬНЫЕ ПОЛЯ */ `comment_show_f1` tinyint(1) NOT NULL default '1', `comment_req_f1` tinyint(1) NOT NULL default '0', `comment_name_f1` varchar(255) NOT NULL default '', `comment_show_f2` tinyint(1) NOT NULL default '1', `comment_req_f2` tinyint(1) NOT NULL default '0', `comment_name_f2` varchar(255) NOT NULL default '', - /* ПОЛЯ ДЛЯ ЗАГРУЗКИ ФАЙЛОВ */ `comment_allow_files` tinyint(1) NOT NULL default '0', `comment_file_max_size` int(10) NOT NULL default '2048', PRIMARY KEY (`Id`) @@ -50,15 +49,16 @@ $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, `comment_status` tinyint(1) unsigned NOT NULL default '1', `comments_close` tinyint(1) unsigned NOT NULL default '0', - /* ПУТЬ К ПРИКРЕПЛЕННОМУ ФАЙЛУ */ `comment_file` varchar(255) NOT NULL default '', + /* ПОЛЯ РЕЙТИНГА */ + `rating_sum` int(10) NOT NULL default '0', + `rating_count` int(10) NOT NULL default '0', PRIMARY KEY (`Id`), KEY `document_id` (`document_id`), KEY `parent_id` (`parent_id`), @@ -66,7 +66,20 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comment_info` ( KEY `anon_key` (`anon_key`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 PACK_KEYS=0;"; -// Начальные данные +// Таблица для хранения истории голосов (защита от повторов) +$module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comment_votes` ( + `id` int(10) unsigned NOT NULL auto_increment, + `comment_id` int(10) unsigned NOT NULL, + `user_id` int(10) unsigned DEFAULT '0', + `anon_key` varchar(32) DEFAULT '', + `vote_value` tinyint(1) NOT NULL, + `date_voted` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `comment_id` (`comment_id`), + KEY `anon_key` (`anon_key`), + KEY `user_id` (`user_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + $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);"; // ================================================================================= @@ -83,13 +96,22 @@ $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';"; +// Добавляем поля рейтинга в существующую таблицу +$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comment_info` ADD `rating_sum` INT(10) NOT NULL DEFAULT '0';"; +$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comment_info` ADD `rating_count` INT(10) NOT NULL DEFAULT '0';"; -// НОВОЕ: Добавляем поле 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`);"; +// Создаем таблицу голосов, если её еще нет +$module_sql_update[] = "CREATE TABLE IF NOT EXISTS `%%PRFX%%_module_comment_votes` ( + `id` int(10) unsigned NOT NULL auto_increment, + `comment_id` int(10) unsigned NOT NULL, + `user_id` int(10) unsigned DEFAULT '0', + `anon_key` varchar(32) DEFAULT '', + `vote_value` tinyint(1) NOT NULL, + `date_voted` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `comment_id` (`comment_id`), + KEY `anon_key` (`anon_key`), + KEY `user_id` (`user_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8;"; ?> \ No newline at end of file diff --git a/templates/comments_tree_sub.tpl b/templates/comments_tree_sub.tpl index dcafbdc..f204454 100644 --- a/templates/comments_tree_sub.tpl +++ b/templates/comments_tree_sub.tpl @@ -22,13 +22,32 @@
{* Информация об авторе и дате *} -
+
- {#COMMENT_USER_ADD#} {$c.comment_author_name|stripslashes|escape} + {#COMMENT_USER_ADD#} {$c.comment_author_name|stripslashes|escape} - {$c.comment_published} + {$c.comment_published} + + {* ----- БЛОК РЕЙТИНГА (ЗВЕЗДЫ) ----- *} +
+
+ {assign var="avg_rating" value=0} + {if isset($c.rating_count) && $c.rating_count > 0} + {math equation="round(x / y)" x=$c.rating_sum y=$c.rating_count assign="avg_rating"} + {/if} + + {section name=star start=1 loop=6} + + {/section} +
+ {if isset($c.rating_count)} + ({$c.rating_count}) + {/if} +
+ {* --------------------------------- *} - {* Динамические поля в мета-данных комментария *} {if $comment_show_f1 == 1 && $c.comment_author_website} {$comment_name_f1|default:#COMMENT_YOUR_SITE#}: {$c.comment_author_website|stripslashes|escape} @@ -44,7 +63,7 @@ • IP:{$c.comment_author_ip} {/if} - + {if isset($c.comment_changed) && $c.comment_changed > 1} ({#COMMENT_TEXT_CHANGED#} {$c.comment_changed}) {/if} @@ -87,15 +106,13 @@ {* ПРАВАЯ ЧАСТЬ: Кнопки действий *}
{* Кнопка ответа *} -{* ПРАВА НА ОТВЕТ *} -{if ($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1} - {* Показываем кнопку только если это НЕ мой собственный комментарий *} - {if !$c.is_my_own} - - {#COMMENT_ANSWER_LINK#} - - {/if} -{/if} + {if ($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1} + {if !(isset($c.is_my_own) && $c.is_my_own)} + + {#COMMENT_ANSWER_LINK#} + + {/if} + {/if} {* ПРАВА НА РЕДАКТИРОВАНИЕ *} {if $c.can_edit}