Мне понравился плагин от imgBB для вставки картинок на форум. Но там не всё гладко - иногда россиянам без квн не видно фотки. А увидев эту тему, подумалось, а почему бы не сделать такое самому. Тем более, нынче самому-то ничего и делать не надо - всё делает ИИ, надо только придумать. И вот вчера мы с Google-ai за полчаса состряпали такое. Рядом с окном редактирования сообщения появляется кнопка с облачком "Загрузить фото". Нажимаем, выбираем файлы, они загружаются на сервер форума, сжимаются до 1200x1200 и сохраняются в папку
/img/, где раскладываются в подпапки по месяцам. После загрузки в текст сообщения сразу вставляется ссылка на изображение, обрамлённая тегами
[img][/img]. И вуаля! Там ещё во время загрузки красота происходит - чессссслово, это уже не я придумал, а сам ИИ, я бы до такого и не догадался. Попробуйте, изменений минимум - удобняк бешеный!

В корневой папке форума создаём папку
/img с правами 755 или 777.

В корневой папке форума создаём файл
upload_compress.php и записываем в него код:
Код: Выделить всё
<?php
define('UPLOAD_TOKEN', 'ЗАМЕНИТЬ_ТОКЕН');
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$base_upload_dir = 'img/';
$max_width = 1200;
$max_height = 1200;
$uploaded_links = [];
if (!isset($_POST['token']) || $_POST['token'] !== UPLOAD_TOKEN) {
http_response_code(403);
echo json_encode(['error' => 'Доступ запрещен']);
exit;
}
if (!extension_loaded('gd')) {
http_response_code(500);
echo json_encode(['error' => 'Библиотека GD не найдена']);
exit;
}
// Создаем подпапку года/месяца
$sub_dir = date('Ym') . '/';
$target_dir = $base_upload_dir . $sub_dir;
if (!is_dir($target_dir)) {
@mkdir($target_dir, 0755, true);
}
if (empty($_FILES['images']['name'])) {
http_response_code(400);
echo json_encode(['error' => 'Файлы не получены']);
exit;
}
foreach ($_FILES['images']['tmp_name'] as $key => $tmp_name) {
if (empty($tmp_name)) continue;
$orig_name = $_FILES['images']['name'][$key];
$image_info = @getimagesize($tmp_name);
if (!$image_info) continue;
list($width, $height, $type) = $image_info;
$original_name_clean = pathinfo($orig_name, PATHINFO_FILENAME);
$original_name_clean = preg_replace('/[^a-zA-Z0-9а-яА-ЯёЁ_\-\.]/u', '', $original_name_clean);
$short_time = base_convert(str_replace('.', '', microtime(true)), 10, 36);
$file_name = $short_time . '_' . $original_name_clean . '.jpg';
$target_file = $target_dir . $file_name;
// Чтение исходного изображения в зависимости от формата
switch ($type) {
case IMAGETYPE_JPEG: $src = @imagecreatefromjpeg($tmp_name); break;
case IMAGETYPE_PNG: $src = @imagecreatefrompng($tmp_name); break;
case IMAGETYPE_WEBP: $src = @imagecreatefromwebp($tmp_name); break;
default: continue 2;
}
if (!$src) continue;
// --- БЛОК АВТОПОВОРОТА НА ОСНОВЕ EXIF ---
// Автоповорот имеет смысл только для JPEG/JPG, так как именно туда телефоны пишут тег Orientation
if ($type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
$exif = @exif_read_data($tmp_name);
if (!empty($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 3:
$src = imagerotate($src, 180, 0);
break;
case 6:
$src = imagerotate($src, -90, 0);
// Меняем местами ширину и высоту исходника, так как картинка повернулась на бок
$tmp = $width; $width = $height; $height = $tmp;
break;
case 8:
$src = imagerotate($src, 90, 0);
// Меняем местами ширину и высоту
$tmp = $width; $width = $height; $height = $tmp;
break;
}
}
}
// ----------------------------------------
// Расчет пропорций под максимум 1200x1200px (уже с учетом правильной ориентации)
$ratio = min($max_width / $width, $max_height / $height);
if ($ratio < 1) {
$new_width = round($width * $ratio);
$new_height = round($height * $ratio);
} else {
$new_width = $width;
$new_height = $height;
}
$dst = imagecreatetruecolor($new_width, $new_height);
// Создаем белый фон под прозрачные элементы PNG/WEBP
$white = imagecolorallocate($dst, 255, 255, 255);
imagefill($dst, 0, 0, $white);
// Ресайз и перенос пикселей
imagecopyresampled($dst, $src, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
// Сохраняем в JPG с качеством 83%
$success = imagejpeg($dst, $target_file, 83);
imagedestroy($src);
imagedestroy($dst);
if ($success) {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$full_url = $protocol . $_SERVER['HTTP_HOST'] . '/' . $target_file;
$uploaded_links[] = $full_url;
}
}
echo json_encode(['links' => $uploaded_links]);
exit;

В папке шаблона
/styles/prosilver/template создаём файл
fast_upload.js и записываем в него код:
Код: Выделить всё
(function() {
let globalFileInput = null;
const SECRET_TOKEN = 'ЗАМЕНИТЬ_ТОКЕН';
// Шаблон иконки облака, чтобы возвращать её на место
const ORIGINAL_ICON = '<i class="icon fa-cloud-upload fa-fw" aria-hidden="true"></i>';
function getOrCreateFileInput() {
if (globalFileInput) return globalFileInput;
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.id = 'fast-image-file-input';
fileInput.multiple = true;
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
globalFileInput = fileInput;
return fileInput;
}
function injectUploadButton(colorButton, textarea) {
if (!colorButton || !textarea || colorButton.parentNode.querySelector('.phpbb-fast-upload-trigger')) return;
const newButton = document.createElement('button');
newButton.type = 'button';
newButton.className = 'button button-icon-only phpbb-fast-upload-trigger';
newButton.title = 'Загрузить фото';
newButton.innerHTML = ORIGINAL_ICON;
newButton.style.marginLeft = '4px';
// Вставляем кнопку в ряд BB-кодов (без дополнительных статусных спанов)
colorButton.parentNode.insertBefore(newButton, colorButton.nextSibling);
newButton.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
const fileInput = getOrCreateFileInput();
fileInput.onchange = null;
fileInput.onchange = function() {
if (!fileInput.files || fileInput.files.length === 0) return;
const formData = new FormData();
formData.append('token', SECRET_TOKEN);
for (let i = 0; i < fileInput.files.length; i++) {
formData.append('images[]', fileInput.files[i]);
}
// Изменяем саму кнопку: ставим часики и отключаем клик
newButton.innerHTML = '⏳';
newButton.disabled = true;
fetch('/upload_compress.php', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) throw new Error('Ошибка сервера ' + response.status);
return response.json();
})
.then(data => {
if (data.links && data.links.length > 0) {
let bbcodeInsert = '\n\n';
data.links.forEach((link, index) => {
bbcodeInsert += `[img]${link}[/img]`;
if (index < data.links.length - 1) {
bbcodeInsert += '\n\n\n';
}
});
bbcodeInsert += '\n\n';
const startPos = textarea.selectionStart;
const endPos = textarea.selectionEnd;
const textBefore = textarea.value.substring(0, startPos);
const textAfter = textarea.value.substring(endPos, textarea.value.length);
textarea.value = textBefore + bbcodeInsert + textAfter;
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = startPos + bbcodeInsert.length;
// При успехе выводим галочку прямо в кнопку
newButton.innerHTML = '✅';
} else {
newButton.innerHTML = '❌';
}
})
.catch(error => {
console.error('Ошибка:', error.message);
newButton.innerHTML = '❌';
})
.finally(() => {
fileInput.value = '';
// Через 2.5 секунды возвращаем родную иконку облака и включаем кнопку обратно
setTimeout(() => {
newButton.innerHTML = ORIGINAL_ICON;
newButton.disabled = false;
}, 2500);
});
};
fileInput.click();
};
}
function autoScanAndInject() {
const colorButtons = document.querySelectorAll('[name="bbpalette"], #bbpalette, .bbcode-color');
if (colorButtons.length > 0) {
colorButtons.forEach(colorBtn => {
const parentForm = colorBtn.closest('form') || colorBtn.closest('.quickreply') || colorBtn.closest('#qr_postform') || document;
const textarea = parentForm.querySelector('textarea');
if (textarea) {
injectUploadButton(colorBtn, textarea);
}
});
}
}
autoScanAndInject();
const observer = new MutationObserver(() => autoScanAndInject());
observer.observe(document.body, { childList: true, subtree: true });
})();

В обоих файлах находим текст
ЗАМЕНИТЬ_ТОКЕН и меняем его на что-то своё уникальное буквенно-цифровое абракадабренное, но обязательно одинаковое в обоих файлах.

В папке той же папке шаблона из пункта 3
/styles/prosilver/template в самом конце файла
overall_footer.html перед закрывающим тегом
</html> вставляем одну строку:

Заходим в админку форума: Общие - Очистить кэш.

Идём писать сообщение на своём форуме, нажимаем кнопку

, выбираем файл изображения для загрузки. Попробуйте загрузить очень большой файл - там красивое.