практически решил с удалением редактированием временем жизни куки и удаления редактирования комментов
This commit is contained in:
@@ -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 = '<a class="page_nav" href="index.php?id=' . $AVE_Core->curentdoc->Id
|
||||
. '&doc=' . (empty($AVE_Core->curentdoc->document_alias) ? prepare_url($AVE_Core->curentdoc->document_title) : $AVE_Core->curentdoc->document_alias)
|
||||
. ((isset($artpage) && is_numeric($artpage)) ? '&artpage=' . $artpage : '')
|
||||
. ((isset($apage) && is_numeric($apage)) ? '&apage=' . $apage : '')
|
||||
. '&page={s}">{t}</a> ';
|
||||
$page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
|
||||
$page_nav = preg_replace('/(?<!:)\/\//', '/', $page_nav);
|
||||
}
|
||||
else
|
||||
{
|
||||
$page_nav = '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$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");
|
||||
$page_nav = '';
|
||||
}
|
||||
if ($num > $limit)
|
||||
{
|
||||
$page_nav = '<a class="page_nav" href="index.php?id=' . $AVE_Core->curentdoc->Id
|
||||
. '&doc=' . (empty($AVE_Core->curentdoc->document_alias) ? prepare_url($AVE_Core->curentdoc->document_title) : $AVE_Core->curentdoc->document_alias)
|
||||
. ((isset($artpage) && is_numeric($artpage)) ? '&artpage=' . $artpage : '')
|
||||
. ((isset($apage) && is_numeric($apage)) ? '&apage=' . $apage : '')
|
||||
. '&page={s}">{t}</a> ';
|
||||
$page_nav = get_pagination(ceil($num / $limit), 'page', $page_nav, get_settings('navi_box'));
|
||||
$page_nav = preg_replace('/(?<!:)\/\//', '/', $page_nav);
|
||||
}
|
||||
else
|
||||
{
|
||||
$page_nav = '';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$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");
|
||||
$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("<br>\n", "<br />\n", "<br/>\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("<br>\n", "<br />\n", "<br/>\n", "<br>", "<br />"), "\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 = "<span class='comment-deleted'>Комментарий удален автором.</span>";
|
||||
$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)
|
||||
|
||||
@@ -155,4 +155,41 @@
|
||||
color: #fff;
|
||||
background-color: #0b5ed7;
|
||||
border-color: #0b5ed7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Фиксируем подвал комментария: высота и выравнивание вправо */
|
||||
.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; } */
|
||||
538
js/comment.js
538
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('<span class="text-danger small">Время на правку истекло</span>');
|
||||
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('<i class="bi bi-unlock-fill me-1"></i> ' + 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('<i class="bi bi-lock-fill me-1"></i> ' + 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 = '<div class="form-check mb-2 mt-2">' +
|
||||
'<input class="form-check-input" type="checkbox" id="del_img_' + cid + '" value="1">' +
|
||||
'<label class="form-check-label text-danger small" for="del_img_' + cid + '">' +
|
||||
'<i class="bi bi-trash me-1"></i>Удалить текущее фото</label>' +
|
||||
'</div>';
|
||||
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 = '<div id="edit_preview_wrapper_' + cid + '" class="d-none mt-2 mb-2">' +
|
||||
'<img id="edit_preview_img_' + cid + '" src="#" class="img-thumbnail" style="max-height: 100px; object-fit: contain;">' +
|
||||
'</div>';
|
||||
|
||||
// Поле для загрузки НОВОГО фото
|
||||
var uploadNewPhotoHtml = '<div class="mb-3 mt-2">' +
|
||||
'<label class="form-label small fw-bold mb-1">Заменить или добавить фото:</label>' +
|
||||
'<input type="file" id="new_file_' + cid + '" class="form-control form-control-sm edit-file-input" data-cid="' + cid + '" accept="image/*">' +
|
||||
editPreviewHtml +
|
||||
'</div>';
|
||||
|
||||
var textarea = '<p><textarea rows="7" id="ta_' + cid + '" class="editable form-control">' + revert + '</textarea></p>';
|
||||
var charsLeft = '<p>' + COMMENT_CHARS_LEFT + ' <span class="charsLeft badge bg-secondary" id="charsLeft_' + cid + '"></span></p>';
|
||||
var buttonSave = '<button type="button" class="btn btn-primary saveButton">' + COMMENT_BUTTON_EDIT + '</button> ';
|
||||
var buttonReset = '<button type="button" class="btn btn-secondary cancelButton">' + COMMENT_BUTTON_CANCEL + '</button>';
|
||||
|
||||
targetElement.after(
|
||||
'<div class="box"><div class="block" id="forms"><fieldset><legend class="h6">' + COMMENT_EDIT_TITLE
|
||||
+ '</legend>' + textarea + deletePhotoHtml + uploadNewPhotoHtml + charsLeft + buttonSave + buttonReset + '</fieldset></div></div>'
|
||||
).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(/<br\s*\/?>/mg, "\n").trim();
|
||||
var currentImg = $('#comment_wrapper_' + cid).find('.mod_comment_attached_image img').attr('src');
|
||||
|
||||
var editHtml = `
|
||||
<div class="edit-form-container border rounded p-3 bg-light mt-2 mb-2">
|
||||
<textarea rows="5" id="ta_${cid}" class="form-control mb-2">${cleanText}</textarea>
|
||||
${currentImg ? `<div class="form-check mb-2"><input class="form-check-input" type="checkbox" id="del_img_${cid}"><label class="form-check-label small text-danger" for="del_img_${cid}">Удалить фото</label></div>` : ''}
|
||||
<input type="file" id="new_file_${cid}" class="form-control form-control-sm mb-3" accept="image/*">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-primary saveButton" data-id="${cid}">Сохранить</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary cancelButton">Отмена</button>
|
||||
<small class="ms-auto text-muted">Осталось: <span id="charsLeft_${cid}"></span></small>
|
||||
</div>
|
||||
</div>`;
|
||||
$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('<div class="mod_comment_text editable_text">' + txt + '</div>').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('<div class="mod_comment_text editable_text">' + cancel + '</div>').remove();
|
||||
setClickable();
|
||||
}
|
||||
}
|
||||
|
||||
function displayNewComment(data){
|
||||
if (data === 'wrong_securecode'){
|
||||
$('#captcha').after('<div class="alert alert-danger">'+COMMENT_WRONG_CODE+'</div>');
|
||||
$('#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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
18
sql.php
18
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`);";
|
||||
|
||||
?>
|
||||
@@ -251,6 +251,10 @@
|
||||
var aveabspath = '{$ABS_PATH}';
|
||||
</script>
|
||||
<script src="{$ABS_PATH}modules/comment/js/comment.js" type="text/javascript"></script>
|
||||
<style>
|
||||
.fade-out { opacity: 0; transition: opacity 0.5s ease; }
|
||||
.italic { font-style: italic; }
|
||||
</style>
|
||||
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -1,12 +1,12 @@
|
||||
{foreach from=$subcomments item=c}
|
||||
|
||||
<div class="card mb-3 mod_comment_comment{if $c.parent_id} ms-4{/if}">
|
||||
<div class="card mb-3 mod_comment_comment{if $c.parent_id} ms-4{/if}" id="comment_wrapper_{$c.Id}">
|
||||
|
||||
{if isset($smarty.request.subaction) && $smarty.request.subaction=='showonly' && isset($smarty.request.comment_id) && $smarty.request.comment_id==$c.Id}
|
||||
<div class="border border-warning border-3 rounded p-0">
|
||||
{/if}
|
||||
|
||||
<div id="{$c.Id}" class="mod_comment_box">
|
||||
<div id="comment_{$c.Id}" class="mod_comment_box">
|
||||
|
||||
<div class="card-body p-3 d-flex align-items-start">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<span class="ms-2"><i class="bi bi-clock me-1"></i> {$c.comment_published}</span>
|
||||
|
||||
{* НОВОЕ: Динамические поля в мета-данных комментария *}
|
||||
{* Динамические поля в мета-данных комментария *}
|
||||
{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,15 +44,19 @@
|
||||
<span class="ms-2 text-secondary">• IP:{$c.comment_author_ip}</span>
|
||||
{/if}
|
||||
|
||||
<span class="mod_comment_changed">{if isset($c.comment_changed) && $c.comment_changed > 1} (<span class="text-secondary">{#COMMENT_TEXT_CHANGED#} {$c.comment_changed}</span>){/if}</span>
|
||||
<span class="mod_comment_changed" 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}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{* Текст комментария *}
|
||||
<div class="mod_comment_text{if $smarty.const.UGROUP==1 || $c.comment_author_id==$smarty.session.user_id|default:'*'} editable_text{/if}">{$c.comment_text|escape|nl2br}</div>
|
||||
<div class="mod_comment_text comment-text-content">{$c.comment_text|stripslashes|nl2br}</div>
|
||||
|
||||
{* НОВОЕ: ВЫВОД ПРИКРЕПЛЕННОГО ИЗОБРАЖЕНИЯ *}
|
||||
{* ВЫВОД ПРИКРЕПЛЕННОГО ИЗОБРАЖЕНИЯ *}
|
||||
{if !empty($c.comment_file)}
|
||||
<div class="mod_comment_attached_image mt-3">
|
||||
<div class="mod_comment_attached_image mt-3" id="image_container_{$c.Id}">
|
||||
<a href="{$ABS_PATH}uploads/comments/{$c.comment_file}" target="_blank" class="comment-img-link">
|
||||
<img src="{$ABS_PATH}uploads/comments/{$c.comment_file}"
|
||||
class="img-fluid rounded border shadow-sm"
|
||||
@@ -66,35 +70,59 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-footer mod_comment_actions d-flex justify-content-end align-items-center bg-white border-top p-2">
|
||||
<div class="card-footer mod_comment_actions d-flex justify-content-between align-items-center bg-white border-top p-2">
|
||||
|
||||
{if $c.comment_author_id!=$smarty.session.user_id|default:'*' && (($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1)}
|
||||
<a class="mod_comment_answer p-2 text-primary" href="javascript:void(0);" rel="{$c.Id}" title="{#COMMENT_ANSWER_LINK#}">
|
||||
<i class="bi bi-reply-fill me-1"></i> {#COMMENT_ANSWER_LINK#}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{if $smarty.const.UGROUP==1 || $c.comment_author_id==$smarty.session.user_id|default:'*'}
|
||||
<a class="mod_comment_edit p-2 text-secondary" href="javascript:void(0);" title="{#COMMENT_EDIT_LINK#}">
|
||||
<i class="bi bi-pencil-square me-1"></i> Редактировать
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{if $smarty.const.UGROUP==1}
|
||||
{if $c.comment_status!=1}
|
||||
<a class="mod_comment_unlock p-2 text-success" href="javascript:void(0);" title="{#COMMENT_UNLOCK_LINK#}">
|
||||
<i class="bi bi-unlock-fill me-1"></i>
|
||||
</a>
|
||||
{else}
|
||||
<a class="mod_comment_lock p-2 text-dark" href="javascript:void(0);" title="{#COMMENT_LOCK_LINK#}">
|
||||
<i class="bi bi-lock-fill me-1"></i>
|
||||
</a>
|
||||
{* ЛЕВАЯ ЧАСТЬ: Таймер (если доступно редактирование) *}
|
||||
<div class="edit-timer-zone px-2">
|
||||
{if $c.can_edit && isset($c.edit_time_left) && $c.edit_time_left > 0}
|
||||
<div class="edit-timer-wrapper text-muted small" id="timer_container_{$c.Id}" title="Время на редактирование">
|
||||
<i class="bi bi-hourglass-split text-primary"></i>
|
||||
<span class="timer-count fw-bold" data-left="{$c.edit_time_left}" id="timer_{$c.Id}">
|
||||
{math equation="floor(x/60)" x=$c.edit_time_left}:{if ($c.edit_time_left%60) < 10}0{/if}{math equation="x%60" x=$c.edit_time_left}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{* ПРАВАЯ ЧАСТЬ: Кнопки действий *}
|
||||
<div class="actions-buttons">
|
||||
{* Кнопка ответа *}
|
||||
{* ПРАВА НА ОТВЕТ *}
|
||||
{if ($cancomment==1 && $closed!=1) || $smarty.const.UGROUP==1}
|
||||
{* Строгая проверка: показываем кнопку только если автор сообщения НЕ текущий пользователь *}
|
||||
{if $c.comment_author_name != $smarty.session.user_name}
|
||||
<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}
|
||||
<span id="controls_{$c.Id}">
|
||||
<a class="p-2 text-warning text-decoration-none mod_comment_edit" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_EDIT_LINK#}">
|
||||
<i class="bi bi-pencil-square me-1"></i> Редактировать
|
||||
</a>
|
||||
|
||||
<a class="p-2 text-danger text-decoration-none mod_comment_delete" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_DELETE_LINK#}">
|
||||
<i class="bi bi-trash me-1"></i> Удалить
|
||||
</a>
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<a class="mod_comment_delete p-2 text-danger" href="javascript:void(0);" title="{#COMMENT_DELETE_LINK#}">
|
||||
<i class="bi bi-trash me-1"></i>
|
||||
</a>
|
||||
{/if}
|
||||
{* УПРАВЛЕНИЕ СТАТУСОМ (ТОЛЬКО ДЛЯ АДМИНА) *}
|
||||
{if $smarty.const.UGROUP==1}
|
||||
{if $c.comment_status!=1}
|
||||
<a class="mod_comment_unlock p-2 text-success" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_UNLOCK_LINK#}">
|
||||
<i class="bi bi-unlock-fill me-1"></i>
|
||||
</a>
|
||||
{else}
|
||||
<a class="mod_comment_lock p-2 text-dark" href="javascript:void(0);" data-id="{$c.Id}" title="{#COMMENT_LOCK_LINK#}">
|
||||
<i class="bi bi-lock-fill me-1"></i>
|
||||
</a>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user