[3.2][3.3] Вставка картинок

У вас есть идея для расширения функциональности phpBB? Расскажите о ней здесь!
Правила форума
Местная Конституция | Шаблон запроса | Документация (phpBB3) | Мини [FAQ] по phpBB3.1.x/3.3.x | FAQ | Как задавать вопросы | Как устанавливать расширения

Ваш вопрос может быть удален без объяснения причин, если на него есть ответы по приведённым ссылкам (а вы рискуете получить предупреждение ;) ).
Аватара пользователя
Siava
Поддержка
Поддержка
Сообщения: 5557
Стаж: 21 год 8 месяцев
Откуда: Питер
Благодарил (а): 187 раз
Поблагодарили: 776 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Siava »

Михаил Молчанов, добавьте в админке это расширение и будет поддерживать.
Еще одно нарушение правил и будете забанены. © Mr. Anderson
Ты очистил кеш? © Sheer
https://siava.ru (phpbb 2.0.x 3.5.x)
Аватара пользователя
Michel
Поддержка
Поддержка
Сообщения: 650
Стаж: 14 лет 8 месяцев
Откуда: Липецк
Благодарил (а): 144 раза
Поблагодарили: 81 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Michel »

Михаил Молчанов писал(а): 18.09.2026 23:53 Форум поддерживает такие фото файлы? jfif
По умолчанию не поддерживает. Но можно попробовать форум заставить поддерживать данный формат. Вопрос зачем? Ведь формат jfif является тем же самым jpeg. Можно изменить окончание файла на jpg и форум его примет как родного.
phpBB 3.3.17 • php 8.5.10 • MySQL(i) 8.0.25-15
Расширения для phpBBСтили для phpBB
Аватара пользователя
Михаил Молчанов
phpBB 1.4.4
Сообщения: 181
Стаж: 2 года 1 месяц
Откуда: Москва
Благодарил (а): 3 раза
Поблагодарили: 7 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Михаил Молчанов »

У меня jfif добавлено через админку. но всё равно не поддерживается
Создал и раскручиваю свой форум! :D

Изображение
Аватара пользователя
Michel
Поддержка
Поддержка
Сообщения: 650
Стаж: 14 лет 8 месяцев
Откуда: Липецк
Благодарил (а): 144 раза
Поблагодарили: 81 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Michel »

Siava писал(а): 19.09.2026 0:08 Михаил Молчанов, добавьте в админке это расширение и будет поддерживать.
В движке phpBB встроена строгая система безопасности, которая сверяет реальный тип файла с его расширением. Для системы расширение .jfif является неизвестным "чужаком", хотя внутри находится стандартный MIME-тип image/jpeg. Чтобы подружить phpBB с форматом .jfif, нужно выполнить два шага: один в панели администратора, а второй — в коде самого форума.
Если просто добавить формат в админке, то при загрузке мы получим ошибку "Несовпадение типа изображения: ожидаемое расширение jpg вместо полученного jfif."

Отправлено спустя 25 минут 26 секунд:
Михаил Молчанов писал(а): 19.09.2026 0:37 У меня jfif добавлено через админку. но всё равно не поддерживается
Измените окончание файла с jfif на jpg и всё.
Так как:
Michel писал(а): 19.09.2026 0:38 а второй — в коде самого форума
Менять код движка не рекомендуется!!!

Отправлено спустя 9 минут 15 секунд:
Но если сильно хочется, то нужно отредактировать файл

Код: Выделить всё

phpbb/files/upload.php
Найти:

Код: Выделить всё

			IMAGETYPE_JPEG		=> ['jpg', 'jpeg'],
Заменить:

Код: Выделить всё

			IMAGETYPE_JPEG		=> ['jpg', 'jpeg', 'jfif'],
Очистить кэш форума! Запомните что данную манипуляцию нужно будет делать каждый раз когда будете обновлять форум!
phpBB 3.3.17 • php 8.5.10 • MySQL(i) 8.0.25-15
Расширения для phpBBСтили для phpBB
Аватара пользователя
rxu
phpBB Guru
phpBB Guru
Сообщения: 17256
Стаж: 20 лет 4 месяца
Откуда: Красноярск
Благодарил (а): 587 раз
Поблагодарили: 1783 раза

Re: [3.2][3.3] Вставка картинок

Сообщение rxu »

PredatorZ писал(а): 11.09.2026 16:17 А без дополнительных "регистраций" и условий нет ничего ?


Оно же, но надо проверять на работоспособность.
Re: [3.1][3.2] BB3ImageUpload: загрузка изображений на внешние хостинги изображений
Изображение
Аватара пользователя
Kuskow
phpBB 2.0.6
Сообщения: 513
Стаж: 10 лет 3 месяца
Откуда: 🇰🇿 Караганда
Благодарил (а): 30 раз
Поблагодарили: 48 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Kuskow »

Что-то пользователи без энтузиазма восприняли нововведение. Некоторые сказали, что им недостаточно выбирать файлы, нужен копипаст (перетаскивать выбранные файлы из проводника в окно загрузки понравилось). Но мы с Google-ИИ не стали это реализовывать, а пошли чуть дальше и сделали копипаст изображения прямо в текст сообщения. Теперь можно, например, сделать скриншот и вставить его в своё сообщение, не сохраняя на локальном диске. Или, листая свой web-фотоальбом, скопировать оттуда изображение и вставить в окно редактирования сообщения на форуме. Ну и в проводнике можно выбрать несколько локальных файлов, скопировать список и вставить их прямо в сообщение - файлы загрузятся на сервер, а в окне редактирования появятся ссылки на загруженные файлы, обрамлённые тегами img. Красота красивая!

Кому надо так же, измените код в этом файле. Не забудьте вместо контекста "ЗАМЕНИТЬ_ТОКЕН" подставить своё, что у вас там, чтобы было одинаково со вторым файлом.
Kuskow писал(а): 18.09.2026 5:433️⃣ В папке шаблона /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;
    }

    // Универсальная функция для отправки файлов на сервер и вставки BB-кодов
    function uploadAndInsertImages(files, newButton, statusSpan, textarea) {
        if (!files || files.length === 0) return;

        const formData = new FormData();
        formData.append('token', SECRET_TOKEN);
        
        for (let i = 0; i < files.length; i++) {
            formData.append('images[]', files[i]);
        }

        // Анимация на кнопке
        if (newButton) {
            newButton.innerHTML = '⏳';
            newButton.disabled = true;
        }
        if (statusSpan) statusSpan.textContent = '⏳';

        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) {
                const startPos = textarea.selectionStart;
                const endPos = textarea.selectionEnd;
                const hasTextBefore = textarea.value.substring(0, startPos).trim().length > 0;
                
                let bbcodeInsert = hasTextBefore ? '\r\n\r\n' : '';
                
                data.links.forEach((link, index) => {
                    bbcodeInsert += `[img]${link}[/img]`;
                    if (index < data.links.length - 1) {
                        bbcodeInsert += '\r\n\r\n\r\n';
                    }
                });
                
                bbcodeInsert += '\r\n\r\n';

                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;

                if (newButton) newButton.innerHTML = '✅';
                if (statusSpan) statusSpan.textContent = '✅';
            } else {
                if (newButton) newButton.innerHTML = '❌';
                if (statusSpan) statusSpan.textContent = '❌';
            }
        })
        .catch(error => {
            console.error('Ошибка:', error.message);
            if (newButton) newButton.innerHTML = '❌';
            if (statusSpan) statusSpan.textContent = '❌';
        })
        .finally(() => {
            setTimeout(() => { 
                if (newButton) {
                    newButton.innerHTML = ORIGINAL_ICON;
                    newButton.disabled = false;
                }
                if (statusSpan) statusSpan.textContent = '';
            }, 2500);
        });
    }

    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';

        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;
                uploadAndInsertImages(fileInput.files, newButton, null, textarea);
                fileInput.value = '';
            };

            fileInput.click();
        };

        // НАЧАЛО БЛОКА КОПИПАСТА (Ctrl+V)
        // Вешаем слушатель события вставки прямо на текстовое поле ввода сообщения
        textarea.addEventListener('paste', function(e) {
            // Проверяем, есть ли бинарные данные (файлы) в буфере обмена
            if (!e.clipboardData || !e.clipboardData.files || e.clipboardData.files.length === 0) {
                return; // Если вставили обычный текст — ничего не делаем, работает стандартно
            }

            const pastedFiles = [];
            for (let i = 0; i < e.clipboardData.files.length; i++) {
                const file = e.clipboardData.files[i];
                // Нам нужны только изображения
                if (file.type.indexOf('image/') !== -1) {
                    pastedFiles.push(file);
                }
            }

            if (pastedFiles.length > 0) {
                // Предотвращаем стандартное поведение браузера (чтобы в textarea не вставился мусор)
                e.preventDefault();
                
                // Запускаем процесс отправки скопированных картинок
                uploadAndInsertImages(pastedFiles, newButton, null, textarea);
            }
        });
        // КОНЕЦ БЛОКА КОПИПАСТА
    }

    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 });
})();
Аватара пользователя
Evangelion
phpBB 2.0.5
Сообщения: 478
Стаж: 14 лет 9 месяцев
Благодарил (а): 58 раз
Поблагодарили: 104 раза

Re: [3.2][3.3] Вставка картинок

Сообщение Evangelion »

Kuskow, имеется расширения для вставки изображений из буфера обмена https://www.phpbb.com/customise/db/exte ... oardimage/
Аватара пользователя
Kuskow
phpBB 2.0.6
Сообщения: 513
Стаж: 10 лет 3 месяца
Откуда: 🇰🇿 Караганда
Благодарил (а): 30 раз
Поблагодарили: 48 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Kuskow »

Я не хочу пользоваться вложениями. Это крайне неудобно. А конкретно на этом сайте phpBBguru ещё и часто глючно. Мне тут приходится перезаливать вложения каждый раз при редактировании сообщения. Нельзя выбрать и загрузить сразу несколько файлов и т.д.

К тому же, вложения не изменяют я в размере. Ну и когда всё это отдельно начинаешь думать-собирать, получается совсем неприлично. А тут простейшие решение очень лёгкое само по себе и не требует много ресурсов.

Когда (если) попробуете, как оно работает, уже не захочется возвращаться к вложениям.
Аватара пользователя
Татьяна5
Поддержка
Поддержка
Сообщения: 13153
Стаж: 15 лет 1 месяц
Благодарил (а): 167 раз
Поблагодарили: 2397 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Татьяна5 »

Kuskow писал(а): Вчера 11:54 Мне тут приходится перезаливать вложения каждый раз при редактировании сообщения
Зачем?
У меня без перезаливки всё работает как надо
Kuskow писал(а): Вчера 11:54 Нельзя выбрать и загрузить сразу несколько файлов
Можно, несколькими способами
Kuskow писал(а): Вчера 11:54 К тому же, вложения не изменяют я в размере
Изменяются, если это картинки
Аватара пользователя
Kuskow
phpBB 2.0.6
Сообщения: 513
Стаж: 10 лет 3 месяца
Откуда: 🇰🇿 Караганда
Благодарил (а): 30 раз
Поблагодарили: 48 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Kuskow »

Ну вот у меня без перезаливки не получается отредактировать сообщение здесь - вложение исчезает. Наверное, браузер у меня такой странный. Но я не жалуюсь - понимаю, что такое бывает, мне не сложно, да и не часто я тут вложения вкладываю.
Аватара пользователя
Kuskow
phpBB 2.0.6
Сообщения: 513
Стаж: 10 лет 3 месяца
Откуда: 🇰🇿 Караганда
Благодарил (а): 30 раз
Поблагодарили: 48 раз

Re: [3.2][3.3] Вставка картинок

Сообщение Kuskow »

После успешного тестирования первой версии мы с Google-ИИ посовещались и решили, что сжатие и перекодирование файлов нужно делать на стороне клиента, чтобы в путешествиях экономить трафик и скорость на мобильном интернете. Поэтому вот вторая версия быстрого загрузчика фотографий и других изображений на форум. И ещё добавили авторизацию по сессии, так что, больше никаких токенов.

Рядом с кнопкой IMG появляется ещё одна кнопка с нарисованным облачком и стрелочкой, означающая загрузку фотофайлов. Файлы можно выбрать из списка, который появляется при нажатии на кнопку или скопировать в окно редактирования сообщения. Так же можно скопировать не файл, а изображение прям в окно редактирования, оно превратится в файл на сервере.

Все загружаемые изображения преобразуются в JPEG, из них вырезается весь EXIF, после чего они сжимаются до 1024 пикселей (по ширине или высоте) и сохраняются в папку /img/, где раскладываются в подпапки по месяцам. После загрузки в текст сообщения сразу вставляется ссылка на изображение, обрамлённая тегами [img][/img].

Как установить на своём форуме:

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

2️⃣ В корневой папке форума создаём файл upload_compress.php и записываем в него код:

Код: Выделить всё

<?php
### phpBB Image Download v2.0 by Kuskow & AI on Google Search
// --- БЛОК НАСТРОЕК СЕРВЕРА ---
define('MAX_WIDTH', 1024);                               // Максимальная ширина из JS
define('MAX_HEIGHT', 1024);                              // Максимальная высота из JS
define('GABARIT_MARGIN', 5);                             // Разрешенный допуск в пикселях (+5)
// -----------------------------

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

$phpbb_root_path = './';
$base_upload_dir = 'img/';
$uploaded_links = [];

// 1. Читаем настройки подключения к базе данных напрямую из config.php
if (!file_exists($phpbb_root_path . 'config.php')) {
    http_response_code(500);
    echo json_encode(['error' => 'Конфигурационный файл не найден']);
    exit;
}

@include($phpbb_root_path . 'config.php');

if (!isset($dbhost) || !isset($dbname) || !isset($dbuser) || !isset($dbpasswd)) {
    http_response_code(500);
    echo json_encode(['error' => 'Не удалось прочитать конфигурацию базы данных']);
    exit;
}

// 2. Ищем куку сессии phpBB по суффиксу '_sid'
$sid = '';
foreach ($_COOKIE as $key => $value) {
    if (substr($key, -4) === '_sid') {
        $sid = preg_replace('/[^a-zA-Z0-9]/', '', $value);
        break;
    }
}

if (empty($sid)) {
    http_response_code(403);
    echo json_encode(['error' => 'Доступ запрещен: авторизация не найдена']);
    exit;
}

// 3. Подключаемся к базе данных через родной mysqli
$port = isset($dbport) ? (int)$dbport : 3306;
$mysqli = @new mysqli($dbhost, $dbuser, $dbpasswd, $dbname, $port);

if ($mysqli->connect_error) {
    http_response_code(500);
    echo json_encode(['error' => 'Ошибка подключения к базе данных']);
    exit;
}
$mysqli->set_charset("utf8mb4");

// 4. Проверяем сессию в таблице phpBB.
$session_table = $table_prefix . 'sessions';
$safe_sid = $mysqli->real_escape_string($sid);

$query = "SELECT session_user_id FROM {$session_table} WHERE session_id = '{$safe_sid}' AND session_user_id <> 1 LIMIT 1";
$result = $mysqli->query($query);

if (!$result || $result->num_rows === 0) {
    $mysqli->close();
    http_response_code(403);
    echo json_encode(['error' => 'Доступ запрещен: сессия недействительна']);
    exit;
}

$mysqli->close();

// --- НАДЕЖНАЯ ПРОВЕРКА ФАЙЛОВ-ФАЕРВОЛ ---

if (empty($_FILES['images']['name'])) {
    http_response_code(400);
    echo json_encode(['error' => 'Файлы не получены']);
    exit;
}

// Создаем подпапку года/месяца
$sub_dir = date('Ym') . '/';
$target_dir = $base_upload_dir . $sub_dir;

if (!is_dir($target_dir)) {
    @mkdir($target_dir, 0755, true);
}

foreach ($_FILES['images']['tmp_name'] as $key => $tmp_name) {
    if (empty($tmp_name)) continue;

    $orig_name = $_FILES['images']['name'][$key];
    $file_size = $_FILES['images']['size'][$key];

    // Лимит веса в 1.5 МБ
    if ($file_size > 1.5 * 1024 * 1024) {
        continue; 
    }

    // Проверка структуры файла (защита от вирусов)
    $image_info = @getimagesize($tmp_name);
    if (!$image_info) {
        continue; 
    }

    list($width, $height, $type) = $image_info;

    // Жесткий лимит пикселей из констант (1024 + 5 = 1029px)
    if ($width > (MAX_WIDTH + GABARIT_MARGIN) || $height > (MAX_HEIGHT + GABARIT_MARGIN)) {
        continue; 
    }

    if ($type !== IMAGETYPE_JPEG) {
        continue; 
    }

    // Формируем чистое оригинальное имя файла
    $original_name_clean = pathinfo($orig_name, PATHINFO_FILENAME);
    $original_name_clean = preg_replace('/[^a-zA-Z0-9а-яА-ЯёЁ_\-\.]/u', '', $original_name_clean);

    // Возвращаем надежный уникальный маркер времени (base36 от микросекунд)
    $short_prefix = base_convert(str_replace('.', '', microtime(true)), 10, 36);
    
    // Имя файла: маркер_исходноеимя.jpg (например, 6ceohoq4w_IMG_20260920_120817.jpg)
    $file_name = $short_prefix . '_' . $original_name_clean . '.jpg';
    $target_file = $target_dir . $file_name;

    // Мгновенное безопасное перемещение файла без риска перезаписи
    $success = @move_uploaded_file($tmp_name, $target_file);

    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;

3️⃣ В папке шаблона /styles/prosilver/template создаём файл fast_upload.js и записываем в него код:

Код: Выделить всё

// phpBB Image Download v2.0 by Kuskow & AI on Google Search
(function() {
    let globalFileInput = null;
    const ORIGINAL_ICON = '<i class="icon fa-cloud-upload fa-fw" aria-hidden="true"></i>';
    
    const MAX_WIDTH = 1024;
    const MAX_HEIGHT = 1024;
    const QUALITY = 0.83;

    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;
    }

    // Функция тотального сжатия картинки через Canvas
    function compressImageClientSide(file) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function(event) {
                const img = new Image();
                img.src = event.target.result;
                img.onload = function() {
                    let width = img.width;
                    let height = img.height;

                    const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height);
                    if (ratio < 1) {
                        width = Math.round(width * ratio);
                        height = Math.round(height * ratio);
                    }

                    const canvas = document.createElement('canvas');
                    canvas.width = width;
                    canvas.height = height;
                    const ctx = canvas.getContext('2d');

                    ctx.fillStyle = '#FFFFFF';
                    ctx.fillRect(0, 0, width, height);
                    ctx.drawImage(img, 0, 0, width, height);

                    canvas.toBlob(function(blob) {
                        let origName = file.name || 'pasted_image.png';
                        let lastDot = origName.lastIndexOf('.');
                        let baseName = lastDot !== -1 ? origName.substring(0, lastDot) : origName;
                        let newFileName = baseName + '.jpg';

                        const compressedFile = new File([blob], newFileName, {
                            type: 'image/jpeg',
                            lastModified: Date.now()
                        });

                        resolve(compressedFile);
                    }, 'image/jpeg', QUALITY);
                };
            };
        });
    }

    function uploadAndInsertImages(files, textarea) {
        if (!files || files.length === 0) return;

        const compressionPromises = Array.from(files).map(file => compressImageClientSide(file));

        Promise.all(compressionPromises).then(compressedFiles => {
            const formData = new FormData();
            
            // Чистая отправка: только массив сжатых файлов, куки сессии браузер прикрепит сам
            compressedFiles.forEach(file => {
                formData.append('images[]', file);
            });

            return 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) {
                const startPos = textarea.selectionStart;
                const endPos = textarea.selectionEnd;
                const hasTextBefore = textarea.value.substring(0, startPos).trim().length > 0;
                
                let bbcodeInsert = hasTextBefore ? '\r\n\r\n' : '';
                
                data.links.forEach((link, index) => {
                    bbcodeInsert += `[img]${link}[/img]`;
                    if (index < data.links.length - 1) {
                        bbcodeInsert += '\r\n\r\n\r\n';
                    }
                });
                
                bbcodeInsert += '\r\n\r\n';

                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;
            }
        })
        .catch(error => {
            console.error('Ошибка при загрузке:', error.message);
        })
        .finally(() => {
            if (globalFileInput) globalFileInput.value = '';
        });
    }

    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';

        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;
                uploadAndInsertImages(fileInput.files, textarea);
            };

            fileInput.click();
        };

        textarea.addEventListener('paste', function(e) {
            if (!e.clipboardData || !e.clipboardData.files || e.clipboardData.files.length === 0) {
                return;
            }

            const pastedFiles = [];
            for (let i = 0; i < e.clipboardData.files.length; i++) {
                const file = e.clipboardData.files[i];
                if (file.type.indexOf('image/') !== -1) {
                    pastedFiles.push(file);
                }
            }

            if (pastedFiles.length > 0) {
                e.preventDefault();
                uploadAndInsertImages(pastedFiles, textarea);
            }
        });
    }

    function autoScanAndInject() {
        const imgButtons = document.querySelectorAll('[name="addbbcode16"], .bbcode-img');
        if (imgButtons.length > 0) {
            imgButtons.forEach(imgBtn => {
                const parentForm = imgBtn.closest('form') || imgBtn.closest('.quickreply') || imgBtn.closest('#qr_postform') || document;
                const textarea = parentForm.querySelector('textarea');
                if (textarea) {
                    // Передаем кнопку IMG в качестве ориентира для вставки
                    injectUploadButton(imgBtn, textarea);
                }
            });
        }
    }

    autoScanAndInject();
    const observer = new MutationObserver(() => autoScanAndInject());
    observer.observe(document.body, { childList: true, subtree: true });
})();
(function() {
    let globalFileInput = null;
    const SECRET_TOKEN = 'NavimbaFastImageUploadKey2026';
    const ORIGINAL_ICON = '<i class="icon fa-cloud-upload fa-fw" aria-hidden="true"></i>';
    
    const MAX_WIDTH = 1024;
    const MAX_HEIGHT = 1024;
    const QUALITY = 0.83;

    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;
    }

    // Функция тотального сжатия картинки через Canvas (без логов в консоль)
    function compressImageClientSide(file) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function(event) {
                const img = new Image();
                img.src = event.target.result;
                img.onload = function() {
                    let width = img.width;
                    let height = img.height;

                    const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height);
                    if (ratio < 1) {
                        width = Math.round(width * ratio);
                        height = Math.round(height * ratio);
                    }

                    const canvas = document.createElement('canvas');
                    canvas.width = width;
                    canvas.height = height;
                    const ctx = canvas.getContext('2d');

                    ctx.fillStyle = '#FFFFFF';
                    ctx.fillRect(0, 0, width, height);
                    ctx.drawImage(img, 0, 0, width, height);

                    canvas.toBlob(function(blob) {
                        let origName = file.name || 'pasted_image.png';
                        let lastDot = origName.lastIndexOf('.');
                        let baseName = lastDot !== -1 ? origName.substring(0, lastDot) : origName;
                        let newFileName = baseName + '.jpg';

                        const compressedFile = new File([blob], newFileName, {
                            type: 'image/jpeg',
                            lastModified: Date.now()
                        });

                        resolve(compressedFile);
                    }, 'image/jpeg', QUALITY);
                };
            };
        });
    }

    function uploadAndInsertImages(files, textarea) {
        if (!files || files.length === 0) return;

        const compressionPromises = Array.from(files).map(file => compressImageClientSide(file));

        Promise.all(compressionPromises).then(compressedFiles => {
            const formData = new FormData();
            formData.append('token', SECRET_TOKEN);
            
            compressedFiles.forEach(file => {
                formData.append('images[]', file);
            });

            return 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) {
                const startPos = textarea.selectionStart;
                const endPos = textarea.selectionEnd;
                const hasTextBefore = textarea.value.substring(0, startPos).trim().length > 0;
                
                let bbcodeInsert = hasTextBefore ? '\r\n\r\n' : '';
                
                data.links.forEach((link, index) => {
                    bbcodeInsert += `[img]${link}[/img]`;
                    if (index < data.links.length - 1) {
                        bbcodeInsert += '\r\n\r\n\r\n';
                    }
                });
                
                bbcodeInsert += '\r\n\r\n';

                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;
            }
        })
        .catch(error => {
            console.error('Ошибка при загрузке:', error.message);
        })
        .finally(() => {
            if (globalFileInput) globalFileInput.value = '';
        });
    }

    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';

        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;
                uploadAndInsertImages(fileInput.files, textarea);
            };

            fileInput.click();
        };

        textarea.addEventListener('paste', function(e) {
            if (!e.clipboardData || !e.clipboardData.files || e.clipboardData.files.length === 0) {
                return;
            }

            const pastedFiles = [];
            for (let i = 0; i < e.clipboardData.files.length; i++) {
                const file = e.clipboardData.files[i];
                if (file.type.indexOf('image/') !== -1) {
                    pastedFiles.push(file);
                }
            }

            if (pastedFiles.length > 0) {
                e.preventDefault();
                uploadAndInsertImages(pastedFiles, textarea);
            }
        });
    }

    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 });
})();

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

Код: Выделить всё

<!-- INCLUDEJS fast_upload.js -->

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

Вернуться в «Запросы расширений для phpBB»