diff --git a/class/comment.php b/class/comment.php
index c00166d..6bfb915 100644
--- a/class/comment.php
+++ b/class/comment.php
@@ -257,6 +257,7 @@ function commentListShow($tpl_dir)
$assign['im'] = $settings['comment_use_antispam'];
$assign['comment_allowed_extensions'] = $settings['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif';
$assign['comment_max_file_size'] = $settings['comment_max_file_size'] ?? 2048;
+ $assign['comment_max_files'] = (int)($settings['comment_max_files'] ?? 5);
$comments = array();
if ($settings['comment_use_page_nav'] == 1)
@@ -482,6 +483,21 @@ function commentListShow($tpl_dir)
$uploaded_files = [];
if ($settings['comment_allow_files'] == 1 && isset($_FILES['comment_image']))
{
+ // --- ДОБАВЛЯЕМ ПРОВЕРКУ ЛИМИТА КОЛИЧЕСТВА ---
+ $max_files_limit = (int)($settings['comment_max_files'] ?? 5);
+ $total_incoming = 0;
+ if (is_array($_FILES['comment_image']['name'])) {
+ foreach($_FILES['comment_image']['name'] as $fname) if(!empty($fname)) $total_incoming++;
+ } elseif(!empty($_FILES['comment_image']['name'])) {
+ $total_incoming = 1;
+ }
+
+ if ($total_incoming > $max_files_limit) {
+ if ($ajax) { echo 'error_max_files'; exit; }
+ else { header('Location:' . $link . '#end'); exit; }
+ }
+ // --- КОНЕЦ ПРОВЕРКИ ---
+
$upload_path = BASE_DIR . '/uploads/comments/';
if (!is_dir($upload_path)) {
@mkdir($upload_path, 0775, true);
@@ -612,6 +628,7 @@ function commentPostEdit($comment_id)
if ($comment_id <= 0 || $user_group <= 0) exit('INVALID_ID');
// 2. Получаем данные комментария и настройки модуля (JOIN)
+ // ДОБАВЛЕНО: cmnt.comment_max_files в выборку
$row = $AVE_DB->Query("
SELECT
msg.*,
@@ -620,7 +637,8 @@ function commentPostEdit($comment_id)
cmnt.comment_user_groups,
cmnt.comment_edit_time,
cmnt.comment_allowed_extensions,
- cmnt.comment_max_file_size
+ cmnt.comment_max_file_size,
+ cmnt.comment_max_files
FROM " . PREFIX . "_module_comment_info AS msg
JOIN " . PREFIX . "_module_comments AS cmnt ON cmnt.Id = 1
WHERE msg.Id = '" . $comment_id . "'
@@ -685,10 +703,24 @@ function commentPostEdit($comment_id)
}
}
- // --- Б. Загрузка новых файлов (если есть) ---
- // Проверяем, что пришел массив файлов
+ // --- Б. Загрузка новых файлов (с проверкой лимита) ---
if (isset($_FILES['comment_image']) && is_array($_FILES['comment_image']['name'])) {
+ // 1. Считаем, сколько реально новых файлов пытаются загрузить
+ $new_files_to_upload_count = 0;
+ foreach ($_FILES['comment_image']['name'] as $k => $fname) {
+ if (!empty($fname) && $_FILES['comment_image']['error'][$k] == UPLOAD_ERR_OK) {
+ $new_files_to_upload_count++;
+ }
+ }
+
+ // 2. Проверка лимита: (Оставшиеся старые + Новые) не должно быть больше MAX
+ $max_limit = (int)($row['comment_max_files'] ?? 5);
+ if ((count($current_files) + $new_files_count) > $max_limit) {
+ echo "MAX_FILES_LIMIT_EXCEEDED"; // Это поймает JS
+ exit;
+ }
+
$allowed_ext_str = $row['comment_allowed_extensions'] ?? 'jpg,jpeg,png,gif,webp';
$allowed_extensions = array_map('trim', explode(',', strtolower($allowed_ext_str)));
$max_kb = (int)($row['comment_max_file_size'] ?? 2048);
diff --git a/js/comment.js b/js/comment.js
index 5ad4d6e..e7f0e2e 100644
--- a/js/comment.js
+++ b/js/comment.js
@@ -530,26 +530,27 @@ if ($foundImgs.length > 0) {
}
// --- СБОРКА ФОРМЫ ---
- var editHtml = `
-
-
- ${starsEditBlock}
-
-
Загруженные файлы:
- ${existingImagesHtml}
-
-
-
-
-
-
-
-
-
-
- Осталось:
-
-
`;
+var editHtml = `
+
+
+ ${starsEditBlock}
+
+
Загруженные файлы:
+ ${existingImagesHtml}
+
+
+
+
+
+
+
+
+
+
+
+ Осталось:
+
+
`;
$textBlock.hide();
$attachedImagesBlock.hide();
@@ -562,51 +563,69 @@ if ($foundImgs.length > 0) {
$wrapper.find('.edit-form-container').data('pendingFiles', pendingFiles);
- $('#new_file_' + cid).on('change', function() {
- var files = this.files;
- var $previewContainer = $('#new_files_preview_' + cid);
-
- if ($previewContainer.length === 0) {
- $(this).after(``);
- $previewContainer = $('#new_files_preview_' + cid);
- }
-
- // Мы НЕ удаляем старые превью ($previewContainer.empty() убрали),
- // а просто добавляем новые в массив и в контейнер
- if (files) {
- $.each(files, function(i, file) {
- if (!file.type.match('image.*')) return;
+$('#new_file_' + cid).on('change', function() {
+ var files = this.files;
+ var $input = $(this);
+ var $currentEditForm = $input.closest('.edit-form-container');
+
+ // ИЩЕМ ТОЛЬКО ПО КЛАССУ ОТНОСИТЕЛЬНО ИНПУТА
+ var $errorDisplay = $input.prev('.js-file-error');
+
+ var maxLimit = parseInt(typeof MAX_FILES_COUNT !== 'undefined' ? MAX_FILES_COUNT : 5);
+
+ if (files) {
+ // Очищаем при каждом новом выборе
+ $errorDisplay.text('').hide();
+
+ $.each(files, function(i, file) {
+ var alreadyInPost = $currentEditForm.find('[id^="existing_wrapper_"]').length;
+ var inPending = pendingFiles.filter(f => f !== null).length;
+
+ if ((alreadyInPost + inPending) >= maxLimit) {
+ // ВЫВОДИМ ТЕКСТ
+ $errorDisplay.text('⚠️ Лимит: ' + maxLimit + ' шт. Лишние файлы проигнорированы.').show();
- // Добавляем файл в наш список
- pendingFiles.push(file);
- var currentIndex = pendingFiles.length - 1;
+ $input.val('');
+ return false;
+ }
- var reader = new FileReader();
- reader.onload = function(e) {
- $previewContainer.append(`
-
-
- NEW
-
-
`);
- };
- reader.readAsDataURL(file);
- });
- }
- // Очищаем инпут, чтобы браузер позволил выбрать те же файлы снова
- $(this).val('');
- });
+ if (!file.type.match('image.*')) return true;
- // Обработчик удаления НОВОГО файла из превью (до сохранения)
- $doc.off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
- var $item = $(this).closest('.new-file-item');
- var index = $item.data('index');
- pendingFiles[index] = null; // Помечаем как удаленный
- $item.remove();
- });
+ pendingFiles.push(file);
+ var currentIndex = pendingFiles.length - 1;
+
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ var $previewContainer = $('#new_files_preview_' + cid);
+ if ($previewContainer.length === 0) {
+ $input.after(``);
+ $previewContainer = $('#new_files_preview_' + cid);
+ }
+ $previewContainer.append(`
+
+
+
+
`);
+ };
+ reader.readAsDataURL(file);
+ });
+ }
+});
+
+// Обработчик удаления НОВОГО файла из превью (до сохранения)
+$doc.off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
+ var $item = $(this).closest('.new-file-item');
+ var index = $item.data('index');
+
+ pendingFiles[index] = null; // Помечаем как удаленный
+ $item.remove();
+
+ // ЗАМЕНЯЕМ СТАРУЮ СТРОКУ НА ЭТУ:
+ // Ищем через ближайший контейнер формы, чтобы точно попасть в нужный блок текста
+ $(this).closest('.edit-form-container').find('.js-file-error').hide().text('');
+});
// --- КОНЕЦ ВСТАВКИ ---
// Добавил поддержку лимита символов
@@ -622,10 +641,12 @@ if ($foundImgs.length > 0) {
$doc.on('click', '.remove-existing-img', function() {
var cid = $(this).data('id');
- var imgPath = $(this).data('img-path'); // Имя файла
+ var imgPath = $(this).data('img-path');
var targetId = $(this).data('target');
- // Добавляем имя файла в скрытый инпут для сервера
+ // 1. Очищаем текст ошибки, так как место освободилось
+ $('#file_error_' + cid).addClass('d-none').text('');
+
var $deleteInput = $('#delete_files_' + cid);
var currentDeleteList = $deleteInput.val() ? $deleteInput.val().split(',') : [];
@@ -634,29 +655,34 @@ $doc.on('click', '.remove-existing-img', function() {
$deleteInput.val(currentDeleteList.join(','));
}
- // Скрываем блок с картинкой
- $('#' + targetId).fadeOut(300);
+ // 2. Скрываем и УДАЛЯЕМ элемент, чтобы счетчик .length его больше не видел
+ $('#' + targetId).fadeOut(300, function() {
+ $(this).remove();
+ });
});
// Обработка выбора НОВОГО файла при редактировании
$doc.on('change', 'input[id^="new_file_"]', function() {
var cid = $(this).attr('id').replace('new_file_', '');
var input = this;
- var $errorBox = $('#file_error_' + cid);
+
+ // 1. Сначала находим блок (БЕЗ ОШИБОК В ПЕРЕМЕННЫХ)
+ var $errorBox = $('#file_error_' + cid);
var $previewWrapper = $('#edit_preview_wrapper_' + cid);
- // Очищаем превью перед новым выбором
- $previewWrapper.find('.preview-item-new').remove();
- $errorBox.addClass('d-none').text('');
-
if (input.files && input.files.length > 0) {
+ // 2. НЕ ОЧИЩАЕМ ТЕКСТ СРАЗУ, даем checkFileConsistency шанс вывести ошибку
+ $previewWrapper.find('.preview-item-new').remove();
$previewWrapper.removeClass('d-none');
Array.from(input.files).forEach(function(file) {
+ // 3. Передаем именно найденный объект
if (checkFileConsistency(file, $errorBox)) {
+ // Если файл прошел проверку - только тогда можно спрятать старую ошибку
+ $errorBox.addClass('d-none').text('');
+
var reader = new FileReader();
reader.onload = function(e) {
- // Создаем новое превью для каждого файла
var item = `
@@ -666,10 +692,6 @@ $doc.on('change', 'input[id^="new_file_"]', function() {
reader.readAsDataURL(file);
}
});
-
- // Прячем старое фото и помечаем на удаление
- $('#existing_preview_wrapper_' + cid).addClass('d-none');
- $('#del_img_' + cid).prop('checked', true);
}
});
@@ -750,6 +772,8 @@ $doc.on('click', '.saveButton', function() {
});
});
+
+
// Глобальный массив для файлов НОВОГО комментария
var newCommentPendingFiles = [];
@@ -758,27 +782,40 @@ var newCommentPendingFiles = [];
$doc.off('change', '#comment_image').on('change', '#comment_image', function() {
var files = this.files;
var $previewWrapper = $('#image_preview_wrapper');
- var $errorDisplay = $('#file_error'); // добавили переменную для ошибок
+ var $errorDisplay = $('#file_error');
+ // 1. Лимит из настроек
+ var maxLimit = parseInt(typeof MAX_FILES_COUNT !== 'undefined' ? MAX_FILES_COUNT : 5);
+
if (files) {
+ $errorDisplay.addClass('d-none').text(''); // Сбрасываем старые ошибки
+
$.each(files, function(i, file) {
- // --- ТВОЯ ВАЛИДАЦИЯ (Screenshot 7 и 8) ---
+ // 2. Считаем, сколько сейчас РЕАЛЬНО файлов в очереди (не null)
+ var currentInQueue = newCommentPendingFiles.filter(function(f) { return f !== null; }).length;
+
+ // 3. ПРОВЕРКА ЛИМИТА: если уже достигли максимума
+ if (currentInQueue >= maxLimit) {
+ $errorDisplay.removeClass('d-none').text('Достигнут лимит (' + maxLimit + ' шт.). Лишние файлы проигнорированы.');
+ return false; // break - полностью выходим из цикла $.each
+ }
+
+ // 4. Твоя существующая валидация (размер, расширение)
if (typeof checkFileConsistency === 'function') {
if (!checkFileConsistency(file, $errorDisplay)) {
- return true; // если файл плохой - пропускаем его
+ return true; // continue - этот файл плохой, идем к следующему
}
}
- // ------------------------------------------
+ // Если прошли все проверки — добавляем в очередь
newCommentPendingFiles.push(file);
var currentIndex = newCommentPendingFiles.length - 1;
var reader = new FileReader();
reader.onload = function(e) {
- // ПРОВЕРКА: если превью для этого файла уже есть - не добавляем второе
if ($previewWrapper.find(`[data-index="${currentIndex}"]`).length > 0) return;
- $previewWrapper.removeClass('d-none'); // показываем только когда есть что показать
+ $previewWrapper.removeClass('d-none');
$previewWrapper.append(`
@@ -791,7 +828,8 @@ $doc.off('change', '#comment_image').on('change', '#comment_image', function() {
reader.readAsDataURL(file);
});
}
- // Очищаем инпут
+
+ // Очищаем инпут в любом случае, чтобы можно было выбрать те же файлы снова
$(this).val('');
});
@@ -800,6 +838,7 @@ $doc.on('click', '.remove-new-comment-img', function() {
var $item = $(this).closest('.new-comment-file-item');
var index = $item.data('index');
newCommentPendingFiles[index] = null; // Помечаем удаленным
+ $('#file_error').addClass('d-none').text('');
$item.fadeOut(200, function() {
$(this).remove();
if ($('#image_preview_wrapper').children().length === 0) {
diff --git a/sql.php b/sql.php
index 5a4106d..56dc0f8 100644
--- a/sql.php
+++ b/sql.php
@@ -5,7 +5,7 @@
*
* Обновленная структура с поддержкой выбора типа рейтинга,
* идентификации анонимов, загрузки файлов и защиты по IP.
- * ДОБАВЛЕНО: Управление расширениями и максимальным размером файлов.
+ * ДОБАВЛЕНО: Управление расширениями, размером и КОЛИЧЕСТВОМ файлов.
* СИНХРОНИЗАЦИЯ: Добавлен формат webp по умолчанию.
*/
@@ -39,6 +39,7 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comments` (
`comment_allow_files` tinyint(1) NOT NULL default '0',
`comment_allowed_extensions` varchar(255) NOT NULL default 'jpg,jpeg,png,gif,webp',
`comment_max_file_size` int(10) NOT NULL default '2048',
+ `comment_max_files` tinyint(2) NOT NULL default '5',
`comment_rating_type` tinyint(1) NOT NULL default '0',
`comment_show_user_rating` tinyint(1) NOT NULL default '0',
`comment_show_user_rating_replies` tinyint(1) NOT NULL default '0',
@@ -93,8 +94,8 @@ $module_sql_install[] = "CREATE TABLE `%%PRFX%%_module_comment_votes` (
KEY `remote_addr` (`remote_addr`)
) 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, 'jpg,jpeg,png,gif,webp', 2048, 0, 1, 0, 0, 0, 0, 60, 30);";
+/* Настройки по умолчанию (добавлено значение 5 для comment_max_files) */
+$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, 'jpg,jpeg,png,gif,webp', 2048, 5, 0, 1, 0, 0, 0, 0, 60, 30);";
// =================================================================================
// 2. ОБНОВЛЕНИЕ МОДУЛЯ (ALTER TABLE)
@@ -112,5 +113,6 @@ $module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD COLUMN IF NOT
/* Новые поля для файлов при обновлении */
$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD COLUMN IF NOT EXISTS `comment_allowed_extensions` VARCHAR(255) NOT NULL DEFAULT 'jpg,jpeg,png,gif,webp';";
$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD COLUMN IF NOT EXISTS `comment_max_file_size` INT(10) NOT NULL DEFAULT '2048';";
+$module_sql_update[] = "ALTER TABLE `%%PRFX%%_module_comments` ADD COLUMN IF NOT EXISTS `comment_max_files` TINYINT(2) NOT NULL DEFAULT '5';";
?>
\ No newline at end of file
diff --git a/templates/admin_settings.tpl b/templates/admin_settings.tpl
index 41e5f4c..8c45147 100644
--- a/templates/admin_settings.tpl
+++ b/templates/admin_settings.tpl
@@ -202,7 +202,7 @@