добален рейтинг со звездами

This commit is contained in:
2025-12-20 20:43:53 +05:00
parent 5df4a119c4
commit cc9bb25a86
5 changed files with 218 additions and 32 deletions

View File

@@ -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;

View File

@@ -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('<span class="text-success small fw-bold">Спасибо!</span>');
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);

View File

@@ -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))

50
sql.php
View File

@@ -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;";
?>

View File

@@ -22,13 +22,32 @@
<div class="flex-grow-1">
{* Информация об авторе и дате *}
<div class="mod_comment_meta mb-2 text-muted small">
<div class="mod_comment_meta mb-2 text-muted small d-flex align-items-center flex-wrap">
<i class="bi bi-person me-1"></i> {#COMMENT_USER_ADD#} <a title="{#COMMENT_INFO#}" href="javascript:void(0);" onclick="popup('{$ABS_PATH}index.php?module=comment&action=postinfo&pop=1&Id={$c.Id}&theme={$theme}','comment','500','300','1');" class="fw-bold link-dark">{$c.comment_author_name|stripslashes|escape}</a>
<span class="me-2"><i class="bi bi-person me-1"></i> {#COMMENT_USER_ADD#} <a title="{#COMMENT_INFO#}" href="javascript:void(0);" onclick="popup('{$ABS_PATH}index.php?module=comment&action=postinfo&pop=1&Id={$c.Id}&theme={$theme}','comment','500','300','1');" class="fw-bold link-dark">{$c.comment_author_name|stripslashes|escape}</a></span>
<span class="ms-2"><i class="bi bi-clock me-1"></i> {$c.comment_published}</span>
<span class="me-2"><i class="bi bi-clock me-1"></i> {$c.comment_published}</span>
{* ----- БЛОК РЕЙТИНГА (ЗВЕЗДЫ) ----- *}
<div class="comment-rating-container d-inline-flex align-items-center ms-2 py-1 px-2 bg-light rounded border" data-id="{$c.Id}" style="line-height: 1;">
<div class="comment-stars d-inline-flex" style="cursor: pointer; color: #ffc107; font-size: 0.9rem;">
{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}
<i class="star-item bi {if $smarty.section.star.index <= $avg_rating}bi-star-fill{else}bi-star{/if} {if !$smarty.section.star.last}me-1{/if}"
data-value="{$smarty.section.star.index}"
title="Оценить на {$smarty.section.star.index}"></i>
{/section}
</div>
{if isset($c.rating_count)}
<span class="ms-2 text-muted fw-bold" style="font-size: 0.8rem;">({$c.rating_count})</span>
{/if}
</div>
{* --------------------------------- *}
{* Динамические поля в мета-данных комментария *}
{if $comment_show_f1 == 1 && $c.comment_author_website}
<span class="ms-2 d-inline-block">
<i class="bi bi-link-45deg"></i> <strong>{$comment_name_f1|default:#COMMENT_YOUR_SITE#}:</strong> {$c.comment_author_website|stripslashes|escape}
@@ -44,7 +63,7 @@
<span class="ms-2 text-secondary">• IP:{$c.comment_author_ip}</span>
{/if}
<span class="mod_comment_changed" id="changed_{$c.Id}">
<span class="mod_comment_changed ms-2" id="changed_{$c.Id}">
{if isset($c.comment_changed) && $c.comment_changed > 1}
(<span class="text-secondary">{#COMMENT_TEXT_CHANGED#} {$c.comment_changed}</span>)
{/if}
@@ -87,15 +106,13 @@
{* ПРАВАЯ ЧАСТЬ: Кнопки действий *}
<div class="actions-buttons">
{* Кнопка ответа *}
{* ПРАВА НА ОТВЕТ *}
{if ($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1}
{* Показываем кнопку только если это НЕ мой собственный комментарий *}
{if !$c.is_my_own}
<a class="mod_comment_answer p-2 text-primary text-decoration-none" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_ANSWER_LINK#}">
<i class="bi bi-reply-fill me-1"></i> {#COMMENT_ANSWER_LINK#}
</a>
{/if}
{/if}
{if ($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1}
{if !(isset($c.is_my_own) && $c.is_my_own)}
<a class="mod_comment_answer p-2 text-primary text-decoration-none" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_ANSWER_LINK#}">
<i class="bi bi-reply-fill me-1"></i> {#COMMENT_ANSWER_LINK#}
</a>
{/if}
{/if}
{* ПРАВА НА РЕДАКТИРОВАНИЕ *}
{if $c.can_edit}