From a1ef4c2358539d3b3a23eef2e63b460c4fca02e8 Mon Sep 17 00:00:00 2001 From: Repellent Date: Sat, 17 Jan 2026 13:12:43 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=BC=D0=BE=D1=82=D0=BD=D0=BE=D0=B5=20=D0=BC?= =?UTF-8?q?=D1=8F=D0=B3=D0=BA=D0=BE=D0=B5=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20=D1=80=D0=BE=D0=B4=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=B9=20=D1=81=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=B8=20=D0=B2=20=D0=B2=D0=B5=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- class/comment.php | 76 ++++-- css/mod_comment_styles.css | 8 + js/comment.js | 142 ++++++------ lang/ru.txt | 4 + templates/admin_comments.tpl | 35 ++- templates/comments_tree_sub.tpl | 396 +++++++++++++++++--------------- 6 files changed, 373 insertions(+), 288 deletions(-) diff --git a/class/comment.php b/class/comment.php index 5645791..9d7aba8 100644 --- a/class/comment.php +++ b/class/comment.php @@ -969,7 +969,9 @@ function commentPostDelete($comment_id) $comment_id = (int)$comment_id; if ($comment_id <= 0) die('Ошибка: Неверный ID'); - // --- ПОЛУЧЕНИЕ ДАННЫХ И ПРОВЕРКА ПРАВ --- + // Читаем тип удаления из запроса (для админки) + $delete_type = $_REQUEST['admin_action_type'] ?? 'auto'; + $this->_commentSettingsGet(); $comment_data = $AVE_DB->Query(" @@ -985,7 +987,6 @@ function commentPostDelete($comment_id) $anon_key = $this->_getAnonKey(); $can_delete = false; - if ($user_group === 1) { $can_delete = true; } else { @@ -993,10 +994,7 @@ function commentPostDelete($comment_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'] < (int)$this->conf_edit_time); - - if ($is_author && $is_time_ok) { - $can_delete = true; - } + if ($is_author && $is_time_ok) $can_delete = true; } if (!$can_delete) { @@ -1006,10 +1004,13 @@ function commentPostDelete($comment_id) $has_children = $AVE_DB->Query("SELECT COUNT(*) FROM " . PREFIX . "_module_comment_info WHERE parent_id = '" . $comment_id . "'")->GetCell(); $upload_dir = BASE_DIR . '/uploads/comments/'; + + // Проверка на Ajax + $is_ajax = (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'); - // --- МЯГКОЕ УДАЛЕНИЕ (если есть ответы и удаляет не админ) --- - if ($has_children > 0 && $user_group != 1) { - // Удаляем файлы физически, если они есть + // --- ЛОГИКА МЯГКОГО УДАЛЕНИЯ --- + if ($has_children > 0 && $delete_type !== 'full') { + // 1. Физически удаляем файлы, если они были if (!empty($comment_data['comment_file'])) { $files_to_del = explode(',', $comment_data['comment_file']); foreach ($files_to_del as $f_name) { @@ -1018,22 +1019,31 @@ function commentPostDelete($comment_id) } } - $del_text = "Комментарий удален автором."; + $status_marker = ($user_group === 1) ? '__DEL_BY_ADM__' : '__DEL_BY_AUT__'; + $AVE_DB->Query(" UPDATE " . PREFIX . "_module_comment_info - SET comment_text = '" . addslashes($del_text) . "', + SET comment_text = '" . $status_marker . "', + comment_author_name = '__DELETED__', + comment_author_id = '0', + comment_author_email = '', + comment_author_ip = '0.0.0.0', comment_file = '', anon_key = '', + user_rating = '0', + rating_sum = '0', + rating_count = '0', + comment_status = '1', comment_changed = '" . time() . "' WHERE Id = '" . $comment_id . "' "); - echo "OK_SOFT"; + + echo $is_ajax ? "OK_SOFT" : "OK"; } else { - // --- ПОЛНОЕ УДАЛЕНИЕ (Админ или нет вложенных ответов) --- + // --- ПОЛНОЕ УДАЛЕНИЕ --- $ids_to_delete = [$comment_id]; - // Если админ — собираем всю ветку детей для удаления if ($user_group == 1) { $all_child_ids = []; $parent_ids = [$comment_id]; @@ -1053,7 +1063,6 @@ function commentPostDelete($comment_id) $final_ids_str = implode(',', $ids_to_delete); - // Массовое удаление всех файлов во всех удаляемых комментариях $files_res = $AVE_DB->Query("SELECT comment_file FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)"); while ($f = $files_res->FetchAssocArray()) { if (!empty($f['comment_file'])) { @@ -1065,7 +1074,6 @@ function commentPostDelete($comment_id) } } - // Удаляем записи из БД $AVE_DB->Query("DELETE FROM " . PREFIX . "_module_comment_info WHERE Id IN ($final_ids_str)"); echo "OK"; @@ -1460,13 +1468,47 @@ function commentAdminListShow($tpl_dir) $all_items[$row['Id']] = $row; } - // --- ПОСТРОЕНИЕ ДЕРЕВА --- + +// --- ПОСТРОЕНИЕ ДЕРЕВА --- $child_map = []; foreach ($all_items as $id => $item) { $p_id = (int)$item['parent_id']; if ($p_id > 0) $child_map[$p_id][] = $id; } + // Проходим по всем элементам и помечаем тех, у кого есть дети в child_map + foreach ($all_items as $id => &$item_for_check) { + $item_for_check['has_children'] = isset($child_map[$id]) ? 1 : 0; + } + unset($item_for_check); // очистка ссылки + + $docs = []; + $processed = []; + + $buildTree = function($parent_id, $level) use (&$buildTree, &$docs, &$processed, &$all_items, &$child_map) { + if (isset($child_map[$parent_id])) { + foreach ($child_map[$parent_id] as $child_id) { + if (!in_array($child_id, $processed)) { + $item = $all_items[$child_id]; + $item['depth_level'] = $level; + // has_children уже записан в $all_items выше + $docs[] = $item; + $processed[] = $child_id; + $buildTree($child_id, $level + 1); + } + } + } + }; + + foreach ($all_items as $id => $item) { + if ($item['parent_id'] == 0 && !in_array($id, $processed)) { + $item['depth_level'] = 0; + $docs[] = $item; + $processed[] = $id; + $buildTree($id, 1); + } + } + $docs = []; $processed = []; diff --git a/css/mod_comment_styles.css b/css/mod_comment_styles.css index de17c47..ed371b3 100644 --- a/css/mod_comment_styles.css +++ b/css/mod_comment_styles.css @@ -290,3 +290,11 @@ html { font-weight: normal; margin-left: 5px; } + +/* Стиль для кнопки удаления всей ветки */ +.ico_delete_all { + background-position: -48px -48px; /* Используем стандартную иконку крестика, но... */ + filter: hue-rotate(140deg) saturate(3); /* ...меняем её цвет на фиолетово-бордовый */ + opacity: 0.8; +} +.ico_delete_all:hover { opacity: 1; transform: scale(1.1); } diff --git a/js/comment.js b/js/comment.js index b69699f..7a417c9 100644 --- a/js/comment.js +++ b/js/comment.js @@ -178,84 +178,82 @@ if ($fileInput.length && $fileInput[0].files.length > 0) { } /* --- ДЕЙСТВИЯ (DELETE, LOCK, OPEN/CLOSE, VOTE) --- */ - function cAction(obj, action) { - var $btn = $(obj); - var cid = $btn.data('id'); - - if (!cid && action !== 'open' && action !== 'close') return; - - $.get(aveabspath + 'index.php', { - module: 'comment', - action: action, - docid: typeof DOC_ID !== 'undefined' ? DOC_ID : '', - Id: cid ? cid : '' - }, function() { - if (action === 'close') { - $btn.attr('id', 'mod_comment_open') - .removeClass('btn-outline-danger').addClass('btn-outline-success') - .html(' ' + COMMENT_SITE_OPEN); - $('#mod_comment_new').fadeOut(300); - } - else if (action === 'open') { - $btn.attr('id', 'mod_comment_close') - .removeClass('btn-outline-success').addClass('btn-outline-danger') - .html(' ' + COMMENT_SITE_CLOSE); - $('#mod_comment_new').fadeIn(300); - } - if (action === 'delete') { - var $commentBlock = $btn.closest('.mod_comment_comment'); - $commentBlock.fadeOut(300, function() { $(this).remove(); }); - } - -// показать / скрыть комментарий -if (action === 'unlock' || action === 'lock') { - var $icon = $btn.find('i'); +function cAction(obj, action) { + var $btn = $(obj); + var cid = $btn.data('id'); - // ищем внутренний бокс, чтобы не красить детей - var $commentBox = $btn.closest('.mod_comment_box'); - // поиск всей карточки для очистки старых стилей - var $card = $btn.closest('.mod_comment_comment'); - - if (action === 'lock') { - // --- Статус 0: СКРЫТО --- - $icon.attr('class', 'bi bi-eye-slash'); - $btn.removeClass('text-success text-muted').addClass('text-danger').attr('title', COMMENT_ICON_SHOW); + if (!cid && action !== 'open' && action !== 'close') return; + + $.get(aveabspath + 'index.php', { + module: 'comment', + action: action, + docid: typeof DOC_ID !== 'undefined' ? DOC_ID : '', + Id: cid ? cid : '' + }, function(data) { // Добавляем аргумент data для получения ответа от PHP - // красим внутренний блок сообщения - $commentBox.addClass('opacity-75 border border-warning rounded'); - - // текст - var $alert = $commentBox.find('.alert-warning'); - if ($alert.length === 0) { - var alertHtml = ''; - // Вставляем в именно этого сообщения - $commentBox.find('.flex-grow-1').first().prepend(alertHtml); - $alert = $commentBox.find('.alert-warning'); + if (action === 'close') { + $btn.attr('id', 'mod_comment_open') + .removeClass('btn-outline-danger').addClass('btn-outline-success') + .html(' ' + COMMENT_SITE_OPEN); + $('#mod_comment_new').fadeOut(300); + } + else if (action === 'open') { + $btn.attr('id', 'mod_comment_close') + .removeClass('btn-outline-success').addClass('btn-outline-danger') + .html(' ' + COMMENT_SITE_CLOSE); + $('#mod_comment_new').fadeIn(300); } - $alert.stop(true, true).fadeIn(300); - - } else { - // --- Статус 1: ВИДИМО --- - $icon.attr('class', 'bi bi-eye'); - $btn.removeClass('text-danger text-muted').addClass('text-success').attr('title', COMMENT_ICON_HIDE); - - // чистим бокс и карточку - var classesToRemove = 'opacity-75 border border-warning rounded'; - $commentBox.removeClass(classesToRemove); - $card.removeClass(classesToRemove + ' border-warning'); // Чистим старый класс из твоего кода - - // удаляем плашку: - $commentBox.find('.alert-warning').first().stop(true, true).fadeOut(300, function() { - $(this).remove(); - }); - } -} +// --- ОБНОВЛЕННЫЙ БЛОК УДАЛЕНИЯ --- + if (action === 'delete') { + var response = data ? data.trim() : ''; + var $commentBlock = $btn.closest('.mod_comment_comment'); // Вся ветка (включая детей) + var $commentBox = $btn.closest('.mod_comment_box'); // Только текущая карточка + + if (response === 'OK_SOFT') { + // обновляем страницу + location.reload(); + } else { + // удаляем совсем комментарий + $commentBlock.fadeOut(300, function() { $(this).remove(); }); + } + } + + // показать / скрыть комментарий + if (action === 'unlock' || action === 'lock') { + var $icon = $btn.find('i'); + var $commentBox = $btn.closest('.mod_comment_box'); + var $card = $btn.closest('.mod_comment_comment'); + + if (action === 'lock') { + $icon.attr('class', 'bi bi-eye-slash'); + $btn.removeClass('text-success text-muted').addClass('text-danger').attr('title', COMMENT_ICON_SHOW); + $commentBox.addClass('opacity-75 border border-warning rounded'); + + var $alert = $commentBox.find('.alert-warning'); + if ($alert.length === 0) { + var alertHtml = ''; + $commentBox.find('.flex-grow-1').first().prepend(alertHtml); + $alert = $commentBox.find('.alert-warning'); + } + $alert.stop(true, true).fadeIn(300); + } else { + $icon.attr('class', 'bi bi-eye'); + $btn.removeClass('text-danger text-muted').addClass('text-success').attr('title', COMMENT_ICON_HIDE); + var classesToRemove = 'opacity-75 border border-warning rounded'; + $commentBox.removeClass(classesToRemove); + $card.removeClass(classesToRemove + ' border-warning'); + + $commentBox.find('.alert-warning').first().stop(true, true).fadeOut(300, function() { + $(this).remove(); }); } + } + }); +} /* --- ИНИЦИАЛИЗАЦИЯ --- */ $(document).ready(function() { diff --git a/lang/ru.txt b/lang/ru.txt index 91f311c..ceeaaee 100644 --- a/lang/ru.txt +++ b/lang/ru.txt @@ -122,6 +122,10 @@ COMMENT_JS_ERR_SRV = "Ошибка связи с сервером" COMMENT_WAITING_MODERATION = "Ваш комментарий на проверке и виден пока только вам." COMMENT_LAST_TITEL = "Последние комментарии" COMMENT_HIDDEN_BY_MODERATOR = "Комментарий скрыт модератором и находится на проверке." +COMMENT_TEXT_DEL_BY_ADMIN = "Комментарий удален администратором." +COMMENT_TEXT_DEL_BY_AUTHOR = "Комментарий удален автором." +COMMENT_STATUS_DELETED = "Удалено" + [admin] diff --git a/templates/admin_comments.tpl b/templates/admin_comments.tpl index 0a74616..2f2548d 100644 --- a/templates/admin_comments.tpl +++ b/templates/admin_comments.tpl @@ -125,18 +125,29 @@ [#] Посмотреть -
- - {if $row.comment_status=='1'}Одобрен{else}Скрыт{/if} - - {if $row.comment_status == '1'} - - {else} - - {/if} - - -
+
+ + {if $row.comment_status=='1'}Одобрен{else}Скрыт{/if} + + + {if $row.comment_status == '1'} + + {else} + + {/if} + + + + + + {if $row.has_children} + + {/if} +
{$row.comment_text|nl2br}
diff --git a/templates/comments_tree_sub.tpl b/templates/comments_tree_sub.tpl index c32d2f8..01eae40 100644 --- a/templates/comments_tree_sub.tpl +++ b/templates/comments_tree_sub.tpl @@ -1,5 +1,11 @@ {foreach from=$subcomments item=c name=sub_loop} -
{/if} -
+
{* БЛОК АВАТАРА *}
- {if isset($c.avatar) && $c.avatar != ''} - {$c.comment_author_name|escape} - {else} -
- {$c.first_letter|default:'?'|upper} -
- {/if} + {if !$is_deleted} + {if isset($c.avatar) && $c.avatar != ''} + {$c.comment_author_name|escape} + {else} +
+ {$c.first_letter|default:'?'|upper} +
+ {/if} +{else} +
+ {if $c.comment_text == '__DEL_BY_ADM__'} + + {else} + + {/if} +
+{/if}
-{* ПРОВЕРКА НА МОДЕРАЦИЮ ДЛЯ АВТОРА *} -{if $c.comment_status == 0} -
- - - {#COMMENT_WAITING_MODERATION#} - -
-{/if} - {* Информация об авторе и дате *} -
- - - {$c.comment_author_name|stripslashes|escape} -{* ПРОВЕРКА ИСТОРИИ ИМЕН АНОНИМА *} -{if !empty($c.past_names)} - - {#COMMENT_CHECK_NAME_TRUE#} - -{/if} - - {$c.comment_published} - - {if $smarty.const.UGROUP==1} - • IP:{$c.comment_author_ip} - {/if} - - {if isset($c.comment_changed) && $c.comment_changed && $c.comment_changed != '0'} - {#COMMENT_CHECK_NAME_EDIT#} {$c.comment_changed} - {/if} -
- - {* Текст комментария *} -
- {$c.comment_text|stripslashes|nl2br} -
- - {* Доп. поля (Сайт, Город) *} - {if $c.comment_author_website || $c.comment_author_city} -
- {if $c.comment_author_website}
{$c.comment_author_website}
{/if} - {if $c.comment_author_city}
{$c.comment_author_city}
{/if} -
+ {* ПРОВЕРКА НА МОДЕРАЦИЮ ДЛЯ АВТОРА *} + {if $c.comment_status == 0 && !$is_deleted} +
+ + + {#COMMENT_WAITING_MODERATION#} + +
{/if} -{* Вывод файлов (Картинки ПЕРВЫМИ, остальное в КОНЦЕ) *} -{if !empty($c.comment_file)} -
- {assign var="all_files" value=","|explode:$c.comment_file} - {assign var="img_exts" value=['jpg', 'jpeg', 'png', 'gif', 'webp']} + {* Информация об авторе и дате *} +
+ {if !$is_deleted} + + + {$c.comment_author_name|stripslashes|escape} + {* ПРОВЕРКА ИСТОРИИ ИМЕН АНОНИМА *} + {if !empty($c.past_names)} + + {#COMMENT_CHECK_NAME_TRUE#} + + {/if} + + {$c.comment_published} -{* 1. Сначала выводим только ИЗОБРАЖЕНИЯ *} -{foreach from=$all_files item=file} - {assign var="f_name" value=$file|trim} - {if $f_name} - {assign var="ext_parts" value="."|explode:$f_name} - {assign var="f_ext" value=$ext_parts[$ext_parts|@count-1]|lower} - - {if in_array($f_ext, $img_exts)} -
- - {#COMMENT_FILE_IMAGE#} - - -
- - {$f_name|regex_replace:"/_[0-9]+(?=\.[a-z0-9]+$)/i":""} - -
-
- {/if} + {if $smarty.const.UGROUP==1} + • IP:{$c.comment_author_ip} + {/if} + + {if isset($c.comment_changed) && $c.comment_changed && $c.comment_changed != '0'} + {#COMMENT_CHECK_NAME_EDIT#} {$c.comment_changed} + {/if} + {else} + + {#COMMENT_STATUS_DELETED#} + + {$c.comment_published} + {/if} +
+ +{* Текст комментария *} +
+ {if $c.comment_text == '__DEL_BY_ADM__'} + {#COMMENT_TEXT_DEL_BY_ADMIN#} + {elseif $c.comment_text == '__DEL_BY_AUT__'} + {#COMMENT_TEXT_DEL_BY_AUTHOR#} + {elseif $is_deleted} + {#COMMENT_STATUS_DELETED#} + {else} + {$c.comment_text|stripslashes|nl2br} {/if} -{/foreach} - - {* 2. Затем выводим все ОСТАЛЬНЫЕ файлы *} - {foreach from=$all_files item=file} - {assign var="f_name" value=$file|trim} - {if $f_name} - {assign var="ext_parts" value="."|explode:$f_name} - {assign var="f_ext" value=$ext_parts[$ext_parts|@count-1]|lower} - - {if !in_array($f_ext, $img_exts)} -
- -
- {$f_ext} -
-
- -
- - {$f_name|regex_replace:"/_[0-9]+(?=\.[a-z0-9]+$)/i":""} - -
- {/if} - {/if} - {/foreach} -
-{/if} + + {* Доп. поля и Файлы (Выводим только если НЕ удален) *} + {if !$is_deleted} + {if $c.comment_author_website || $c.comment_author_city} +
+ {if $c.comment_author_website}
{$c.comment_author_website}
{/if} + {if $c.comment_author_city}
{$c.comment_author_city}
{/if} +
+ {/if} + + {if !empty($c.comment_file)} +
+ {assign var="all_files" value=","|explode:$c.comment_file} + {assign var="img_exts" value=['jpg', 'jpeg', 'png', 'gif', 'webp']} + + {* 1. ИЗОБРАЖЕНИЯ *} + {foreach from=$all_files item=file} + {assign var="f_name" value=$file|trim} + {if $f_name} + {assign var="ext_parts" value="."|explode:$f_name} + {assign var="f_ext" value=$ext_parts[$ext_parts|@count-1]|lower} + {if in_array($f_ext, $img_exts)} +
+ + {#COMMENT_FILE_IMAGE#} + +
+ + {$f_name|regex_replace:"/_[0-9]+(?=\.[a-z0-9]+$)/i":""} + +
+
+ {/if} + {/if} + {/foreach} + + {* 2. ОСТАЛЬНЫЕ файлы *} + {foreach from=$all_files item=file} + {assign var="f_name" value=$file|trim} + {if $f_name} + {assign var="ext_parts" value="."|explode:$f_name} + {assign var="f_ext" value=$ext_parts[$ext_parts|@count-1]|lower} + {if !in_array($f_ext, $img_exts)} +
+ +
+ {$f_ext} +
+
+
+ + {$f_name|regex_replace:"/_[0-9]+(?=\.[a-z0-9]+$)/i":""} + +
+
+ {/if} + {/if} + {/foreach} +
+ {/if} + {/if}
{* ФУТЕР *} - {* --- ВСТАВЛЯЕМ КНОПКУ AJAX ОТВЕТОВ --- *} + {* --- ВСТАВЛЯЕМ КНОПКУ AJAX ОТВЕТОВ --- *} {if $smarty.foreach.sub_loop.last && isset($more_counts[$parentId])}