добавлена загрузка файлов

This commit is contained in:
2026-01-03 21:39:48 +05:00
parent 63dff7fb34
commit 1fe7a99e98
4 changed files with 150 additions and 84 deletions

View File

@@ -648,17 +648,19 @@ function commentPostFormShow($tpl_dir)
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (in_array($ext, $allowed_extensions)
&& $file['size'] > 0
&& $file['size'] <= $max_file_size_bytes
&& strpos($mime, 'image/') === 0)
{
$new_name = time() . '_' . rand(100, 999) . '.' . $ext;
if (move_uploaded_file($file['tmp_name'], $upload_path . $new_name)) {
$uploaded_files[] = $new_name;
$processed_hashes[] = $file_hash;
}
}
// Проверки расширения и безопасности
$is_allowed_ext = in_array($ext, $allowed_extensions);
$is_dangerous = in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
// Условие: расширение разрешено, файл не опасен и проходит по размеру
if ($is_allowed_ext && !$is_dangerous && $file['size'] > 0 && $file['size'] <= $max_file_size_bytes)
{
$new_name = time() . '_' . rand(100, 999) . '.' . $ext;
if (move_uploaded_file($file['tmp_name'], $upload_path . $new_name)) {
$uploaded_files[] = $new_name;
$processed_hashes[] = $file_hash;
}
}
}
}
@@ -850,17 +852,18 @@ function commentPostEdit($comment_id)
$mime = finfo_file($finfo, $tmp_name);
finfo_close($finfo);
if (in_array($file_ext, $allowed_extensions)
&& $file_size > 0
&& $file_size <= ($max_kb * 1024)
&& strpos($mime, 'image/') === 0)
// Проверки расширения и опасных файлов
$is_allowed_ext = in_array($file_ext, $allowed_extensions);
$is_dangerous = in_array($file_ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'exe', 'pl', 'cgi']);
if ($is_allowed_ext && !$is_dangerous && $file_size > 0 && $file_size <= ($max_kb * 1024))
{
if (!is_dir($upload_dir)) @mkdir($upload_dir, 0775, true);
$new_file_name = 'comm_' . time() . '_' . rand(1000, 9999) . '.' . $file_ext;
if (move_uploaded_file($tmp_name, $upload_dir . $new_file_name)) {
$current_files[] = $new_file_name; // Добавляем новый файл к списку
$current_files[] = $new_file_name;
}
}
}

View File

@@ -494,33 +494,39 @@ $doc.off('click', '.mod_comment_edit').on('click', '.mod_comment_edit', function
var cleanText = $textBlock.html().replace(/<br\s*\/?>/mg, "\n").trim();
// --- СБОР ТЕКУЩИХ КАРТИНОК (ТОЧНЫЙ ПОИСК ПО КОНТЕЙНЕРУ) ---
// --- СБОР ТЕКУЩИХ ВЛОЖЕНИЙ ---
var existingImagesHtml = '';
// Ищем картинки строго внутри контейнера вложений конкретного комментария
var $imageContainer = $('#image_container_' + cid);
var $foundImgs = $imageContainer.find('img');
if ($foundImgs.length > 0) {
existingImagesHtml = '<div class="d-flex flex-wrap gap-2 mb-3" style="border: 1px dashed #ccc; padding: 10px; background: #fff; border-radius: 8px;">';
// Ищем именно "карточки" из твоего шаблона (класс .comment-image-item)
var $foundItems = $imageContainer.find('.comment-image-item');
if ($foundItems.length > 0) {
existingImagesHtml = '<div class="d-flex flex-wrap gap-2 mb-3 p-2 bg-white border rounded border-dashed">';
$foundImgs.each(function(index) {
var imgSrc = $(this).attr('src');
if (!imgSrc) return;
$foundItems.each(function(index) {
var $item = $(this);
var $link = $item.find('a');
var fileUrl = $link.attr('href');
if (!fileUrl) return;
var fileName = fileUrl.split('/').pop();
var fileExt = fileName.split('.').pop().toLowerCase();
var isImg = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
// Используем твой же стиль превью
var previewContent = isImg
? `<img src="${fileUrl}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover;">`
: `<div class="d-flex align-items-center justify-content-center bg-light text-dark rounded border small fw-bold"
style="width: 80px; height: 80px; text-transform: uppercase;">${fileExt}</div>`;
// Извлекаем имя файла из пути для передачи на сервер
var fileName = imgSrc.split('/').pop();
existingImagesHtml += `
<div id="existing_wrapper_${cid}_${index}" class="position-relative">
<img src="${imgSrc}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover;">
${previewContent}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-existing-img"
data-id="${cid}"
data-img-path="${fileName}"
data-target="existing_wrapper_${cid}_${index}"
style="padding: 0 5px; margin: 2px; line-height: 1; z-index: 10;"
title="Удалить">
<i class="bi bi-x-lg" style="font-size: 0.8rem;"></i>
data-id="${cid}" data-img-path="${fileName}" data-target="existing_wrapper_${cid}_${index}"
style="padding: 0 4px; margin: 1px; line-height: 1; z-index: 10;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`;
});
@@ -566,7 +572,7 @@ var editHtml = `
<div class="mb-2">
<label class="small text-muted mb-1">Добавить новые:</label>
<div id="file_error_${cid}" class="js-file-error text-danger small fw-bold mb-1" style="display:none;"></div>
<input type="file" name="comment_image[]" id="new_file_${cid}" class="form-control form-control-sm" accept="image/*" multiple>
<input type="file" name="comment_image[]" id="new_file_${cid}" class="form-control form-control-sm" multiple>
</div>
<input type="hidden" name="delete_files" id="delete_files_${cid}" value="">
@@ -577,9 +583,14 @@ var editHtml = `
</div>
</div>`;
$textBlock.hide();
$attachedImagesBlock.hide();
$foundImgs.hide();
// Прячем текст комментария
$textBlock.hide();
// Прячем ВЕСЬ контейнер с оригиналами (и картинки, и упрямый ZIP)
$('#image_container_' + cid).attr('style', 'display: none !important;');
// Для надежности скрываем всё остальное
$wrapper.find('.mod_comment_attached_images, .comment-files').hide();
$textBlock.after(editHtml);
@@ -592,14 +603,10 @@ $('#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 $errorDisplay = $currentEditForm.find('.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) {
@@ -607,17 +614,17 @@ $('#new_file_' + cid).on('change', function() {
var inPending = pendingFiles.filter(f => f !== null).length;
if ((alreadyInPost + inPending) >= maxLimit) {
// ВЫВОДИМ ТЕКСТ
$errorDisplay.text(' Лимит: ' + maxLimit + ' шт. Лишние файлы проигнорированы.').show();
$input.val('');
$errorDisplay.text(' Лимит: ' + maxLimit + ' шт.').show();
return false;
}
if (!file.type.match('image.*')) return true;
// ПРОВЕРКА ЧЕРЕЗ ФУНКЦИЮ (она теперь берет ALLOWED_EXTENSIONS)
if (!checkFileConsistency(file, $errorDisplay)) return true;
pendingFiles.push(file);
var currentIndex = pendingFiles.length - 1;
var fileExt = file.name.split('.').pop().toLowerCase();
var isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
var reader = new FileReader();
reader.onload = function(e) {
@@ -626,15 +633,23 @@ $('#new_file_' + cid).on('change', function() {
$input.after(`<div id="new_files_preview_${cid}" class="d-flex flex-wrap gap-2 mt-2"></div>`);
$previewContainer = $('#new_files_preview_' + cid);
}
var content = isImage
? `<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #0d6efd;">`
: `<div class="d-flex align-items-center justify-content-center bg-light text-dark rounded border shadow-sm"
style="width: 80px; height: 80px; font-weight: bold; text-transform: uppercase; font-size: 12px; border: 2px solid #0d6efd !important;">${fileExt}</div>`;
$previewContainer.append(`
<div class="position-relative new-file-item" data-index="${currentIndex}">
<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #0d6efd;">
${content}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-file" style="padding: 0 5px; margin: 2px; line-height: 1;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`);
};
reader.readAsDataURL(file);
if (isImage) reader.readAsDataURL(file);
else reader.onload({target: {result: ''}});
});
}
});
@@ -730,18 +745,23 @@ $doc.on('change', 'input[id^="new_file_"]', function() {
// Обработка кнопки "Отмена" при редактировании
$doc.on('click', '.cancelButton', function() {
var $container = $(this).closest('.edit-form-container');
// Находим главную обертку этого конкретного комментария
var $wrapper = $container.closest('[id^="comment_wrapper_"]');
var cid = $container.data('cid'); // Берем ID для обращения к контейнеру файлов
// 1. Возвращаем текст
$wrapper.find('.mod_comment_text').show();
// 2. Возвращаем оригинальный блок с картинками
// Ищем все возможные варианты названий блоков, которые мы скрывали
$wrapper.find('.mod_comment_attached_image, .comment-files, .attached-images').show();
// СНАЧАЛА ЧИСТИМ ИНЛАЙН СТИЛЬ (тот самый !important)
$('#image_container_' + cid).attr('style', '');
// Затем стандартный показ
$wrapper.find('.mod_comment_attached_image, .mod_comment_attached_images, .comment-files, .attached-images').show();
// 3. Если мы скрывали изображения напрямую через $foundImgs.hide(), показываем их
$wrapper.find('img').show();
// И ссылки (наш ZIP) тоже на всякий случай
$wrapper.find('a').show();
// 4. Удаляем форму редактирования
$container.remove();
@@ -837,19 +857,28 @@ $doc.off('change', '#comment_image').on('change', '#comment_image', function() {
var currentIndex = newCommentPendingFiles.length - 1;
var reader = new FileReader();
reader.onload = function(e) {
if ($previewWrapper.find(`[data-index="${currentIndex}"]`).length > 0) return;
reader.onload = function(e) {
if ($previewWrapper.find(`[data-index="${currentIndex}"]`).length > 0) return;
$previewWrapper.removeClass('d-none');
$previewWrapper.append(`
<div class="position-relative new-comment-file-item" data-index="${currentIndex}">
<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #198754;">
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-comment-img"
style="padding: 0 5px; margin: 2px; line-height: 1;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`);
};
// Определяем, что вставить: картинку или заглушку с текстом расширения
var fileExt = file.name.split('.').pop().toLowerCase();
var isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileExt);
// Формируем контент: либо твой оригинальный img, либо блок с расширением
var content = isImage
? `<img src="${e.target.result}" class="img-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border: 2px solid #198754;">`
: `<div class="d-flex align-items-center justify-content-center bg-primary text-white rounded" style="width: 80px; height: 80px; font-weight: bold; text-transform: uppercase; border: 2px solid #0d6efd;">${fileExt}</div>`;
$previewWrapper.removeClass('d-none');
$previewWrapper.append(`
<div class="position-relative new-comment-file-item" data-index="${currentIndex}">
${content}
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 remove-new-comment-img"
style="padding: 0 5px; margin: 2px; line-height: 1;">
<i class="bi bi-x-lg" style="font-size: 0.7rem;"></i>
</button>
</div>`);
};
reader.readAsDataURL(file);
});
}

View File

@@ -281,7 +281,7 @@
var RATING_ANON_SET = '{$comment_rating_anon_set|default:0}';
var ALLOW_FILES_ANON = '{$comment_allow_files_anon|default:0}';
// --- ПЕРЕМЕННЫЕ ДЛЯ ВАЛИДАЦИИ ФАЙЛОВ ---
var ALLOWED_EXTENSIONS = '{$comment_allowed_extensions|default:"jpg,jpeg,png,gif"}';
var ALLOWED_EXTENSIONS = '{$comment_allowed_extensions|default:"jpg,jpeg,png,gif,webp"}';
var MAX_FILE_SIZE_KB = '{$comment_max_file_size|default:2048}';
var MAX_FILES_COUNT = '{$comment_max_files|default:5}';
</script>

View File

@@ -64,24 +64,58 @@
</div>
{/if}
{* Вывод изображения (ИСПРАВЛЕНО ДЛЯ MULTIPLE) *}
{if !empty($c.comment_file)}
<div class="mod_comment_attached_images mt-2 d-flex flex-wrap gap-2" id="image_container_{$c.Id}">
{assign var="photos" value=","|explode:$c.comment_file}
{foreach from=$photos item=photo}
{if $photo|trim}
<div class="comment-image-item">
<a href="{$ABS_PATH}uploads/comments/{$photo|trim}" target="_blank" class="d-block">
<img src="{$ABS_PATH}uploads/comments/{$photo|trim}"
class="img-fluid rounded border shadow-sm"
style="max-height: 150px; width: auto; object-fit: cover;"
alt="Изображение" />
</a>
</div>
{/if}
{/foreach}
{* Вывод файлов (Картинки ПЕРВЫМИ, остальное в КОНЦЕ) *}
{if !empty($c.comment_file)}
<div class="mod_comment_attached_images mt-2 d-flex flex-wrap gap-2" id="image_container_{$c.Id}">
{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)}
<div class="comment-image-item">
<a href="{$ABS_PATH}uploads/comments/{$f_name}" target="_blank" class="d-block">
<img src="{$ABS_PATH}uploads/comments/{$f_name}"
class="img-fluid rounded border shadow-sm"
style="width: 100px; height: 100px; object-fit: cover;"
alt="Изображение" />
</a>
</div>
{/if}
{/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)}
<div class="comment-image-item d-flex flex-column align-items-center">
<a href="{$ABS_PATH}uploads/comments/{$f_name}" target="_blank" class="text-decoration-none d-block text-center">
{* Блок с расширением *}
<div class="d-flex align-items-center justify-content-center bg-light text-dark rounded border shadow-sm mb-1"
style="width: 100px; height: 100px; font-weight: bold; text-transform: uppercase; font-size: 14px;">
<i class="bi bi-file-earmark-arrow-down me-1"></i>{$f_ext}
</div>
{* Название файла *}
<div class="text-muted small text-truncate" style="max-width: 100px;" title="{$f_name}">
{$f_name}
</div>
</a>
</div>
{/if}
{/if}
{/foreach}
</div>
{/if}
</div>
</div>