Вложения¶
Клиенты и сотрудники могут добавлять файлы к ответам и заметкам: скриншоты, логи, PDF, выгрузки. Helpdesk Pro считает каждый файл приватными данными. Файл отправляет только ваш сайт, после той же проверки доступа, которая решает, кто может читать его сообщение, и ничто, что Helpdesk Pro отрисовывает, возвращает из API или вкладывает в письмо, не содержит адреса хранилища, бакета или CDN.
Кто видит файл¶
Файл принадлежит одному сообщению и наследует его видимость.
- Файл в публичном ответе может получить каждый, кто может читать заявку: её заявитель, её копии, клиенты, которые могут читать общие заявки проекта, и сотрудники, работающие с проектом.
- Файл во внутренней заметке тоже внутренний. Получить его могут только сотрудники, работающие с проектом. Он никогда не появляется в портале, в письме клиенту или в представлении API для клиента.
- Черновик (загруженный, но ещё не отправленный) может получить только загрузивший его человек, чтобы редактор мог показать предпросмотр.
- Доступ следует за заявкой в момент запроса. Копия, убранная из заявки, сразу теряет доступ к её файлам; файлы удалённого сообщения доступны только сотрудникам; файлы удалённой заявки исчезают для клиентов.
Каждый отказ — обычный 404 Not Found, существует ли файл на самом деле, принадлежит ли он нечитаемой заявке, лежит ли в заметке или является чужим черновиком.
Как отдаются файлы¶
Два маршрута передают файлы, и оба выполняют проверку выше при каждом запросе:
| Маршрут | Кто использует | Чем подтверждается вход |
|---|---|---|
GET {mount}/_file/{id}/{name} |
Клиентский портал и ссылки в письмах клиентам | Сессией сайта. Посетителя без входа просят войти, после чего он возвращается к файлу. |
GET /api/v1/helpdesk-pro/attachments/{id} |
Рабочая область сотрудников | Токеном API или сессией плюс helpdesk-pro.desk |
Часть {name} нужна только чтобы браузер сохранил файл под понятным именем; какой файл отправить, решает id.
Каждый ответ отправляется с:
Cache-Control: private, max-age=300для файлов публичных ответов иprivate, no-storeдля внутренних файлов и черновиков, чтобы общий кэш или CDN не хранил копию.X-Content-Type-Options: nosniffи типом содержимого, который Helpdesk Pro определил при загрузке (по проверенному расширению), а не тем, который заявил браузер загружающего.Content-Disposition: inlineтолько для растровых изображений (png, jpg, gif, webp). Всё остальное, включая SVG, HTML и текстовые файлы, скачивается.- Песочницей
Content-Security-Policy,Cross-Origin-Resource-Policy: same-origin,Referrer-Policy: no-referrerиX-Robots-Tag: noindex, nofollow.
Перенаправления нет никогда. Когда файлы также хранятся в бакете, сайт получает байты и отправляет их сам.
В рабочей области и портале изображения показываются миниатюрами, открывающимися в полном размере, а другие файлы — именованными чипами. Изображение, вставленное в письмо, показывается там, где оно стояло в сообщении, и не повторяется ниже. Читатель, которому нельзя открыть этот файл, видит только его имя.
Ограничения загрузки и типы файлов¶
| Правило | Откуда берётся |
|---|---|
| Наибольший файл | attachments.max_mb (10 МБ). upload_max_filesize и post_max_size в PHP должны быть не меньше, иначе PHP отбросит загрузку первым. |
| Файлов на сообщение | attachments.max_per_message (10). |
| Разрешённые типы | attachments.extensions. Тип берётся из расширения и проверяется по содержимому: изображения должны декодироваться как заявленный тип, а PDF, zip, Office и gzip-файлы должны начинаться с сигнатуры своего формата. |
| Никогда не разрешены | Программы и скрипты (php, phtml, phar, exe, js, sh, bat, jar, ps1, .htaccess и другие), даже если указаны в attachments.extensions. |
| Активные в браузере типы | SVG, HTML и XML можно разрешить, но они всегда скачиваются и никогда не показываются inline. |
| Частота загрузки | attachments.uploads_per_hour (60) на человека или на хешированный IP-адрес для гостей. |
Опасное расширение, спрятанное в имени файла, обезвреживается: invoice.php.pdf сохраняется как invoice_php.pdf. Части пути, кавычки и управляющие символы удаляются из имён. Вставленное изображение без полезного имени называется pasted-image-<date>-<time>.png (или с правильным расширением).
Загружать может любой вошедший незаблокированный человек, а сотрудники — через API. Гости могут загружать, только пока включён portal.guest_submissions и хотя бы один публичный проект принимает новые обращения. При выключенном attachments.enabled никто не может загружать, но уже прикреплённые файлы остаются читаемыми.
Хранение¶
Файлы хранятся по одному разу на содержимое: имя файла на диске — SHA-256 его байтов, поэтому один скриншот, отправленный в три заявки, хранится один раз. Они лежат в user/data/helpdesk-pro/files/ (рядом с базой данных). Папка получает запрещающий всё .htaccess, а файлы на диске не имеют расширения, поэтому оттуда ничего никогда не выполняется и не отдаётся веб-сервером напрямую.
Предупреждение
На nginx или Caddy держите user/data недоступной, как в рекомендуемой конфигурации Grav. См. Установку.
Приватный бакет (необязательно)¶
Когда включён attachments.storage.s3.enabled и заполнены endpoint, bucket, key и secret, каждый новый файл также загружается в S3-совместимый бакет (Cloudflare R2, Amazon S3, Backblaze B2, MinIO). Локальная копия остаётся той, что отдаётся. Когда файла нет локально (пересобранный сервер, перенесённый сайт), он забирается из бакета при первом запросе.
Бакет должен быть приватным: Helpdesk Pro никогда не выдаёт URL бакета. Объекты хранятся под attachments.storage.s3.prefix с Cache-Control: private, no-store и Content-Disposition: attachment, поэтому даже случайно открытый бакет не отрисует их. Секрет никогда не возвращается ни одним эндпоинтом.
Очистка¶
Ежечасное задание attachments.gc:
- удаляет черновики, которые никто не отправил, через
attachments.draft_hours(24 часа); - пересчитывает число ссылок на каждый сохранённый файл, исправляя счётчики, оставшиеся после безвозвратного удаления заявки;
- удаляет сохранённые файлы, которые больше никто не использует, локально и из бакета, когда прошло
attachments.grace_days(7 дней). Повторная загрузка тех же байтов в льготный период возвращает файл.
Файл, на который ссылаются как на исходник письма, хранится столько, сколько существует это письмо.
Настройки¶
На вкладке Attachments настроек плагина:
| Ключ | По умолчанию | Что делает |
|---|---|---|
attachments.enabled |
true |
Позволяет клиентам и сотрудникам добавлять файлы. При выключении существующие файлы остаются читаемыми. |
attachments.max_mb |
10 |
Наибольший принимаемый файл в мегабайтах. |
attachments.max_per_message |
10 |
Сколько файлов может нести одно сообщение. |
attachments.extensions |
[png, jpg, jpeg, gif, webp, pdf, txt, log, csv, zip, json, md] |
Расширения, которые можно загружать. |
attachments.uploads_per_hour |
60 |
Загрузок на человека (или на гостевой адрес) в час. 0 отключает ограничение. |
attachments.draft_hours |
24 |
Черновики, не отправленные с сообщением, удаляются через столько часов. |
attachments.grace_days |
7 |
Неиспользуемый сохранённый файл хранится столько дней перед удалением. |
attachments.storage.s3.enabled |
false |
Также хранить копию каждого файла в приватном S3-совместимом бакете. |
attachments.storage.s3.endpoint |
'' |
HTTPS-эндпоинт сервиса бакета без имени бакета. |
attachments.storage.s3.bucket |
'' |
Бакет. |
attachments.storage.s3.region |
auto |
auto для Cloudflare R2; регион бакета в остальных случаях. |
attachments.storage.s3.key |
'' |
Идентификатор ключа доступа. |
attachments.storage.s3.secret |
'' |
Секретный ключ доступа. Никогда не отправляется в браузер. |
attachments.storage.s3.prefix |
helpdesk |
Папка внутри бакета. |
Процесс загрузки¶
Поле ответа и форма портала, а также редактор рабочей области загружают одинаково. Файлы отправляются сразу при выборе, перетаскивании или вставке и прикрепляются при отправке сообщения.
- Загрузите каждый файл как черновик. У первой загрузки нет токена; ответ несёт его. Отправляйте этот токен с каждой следующей загрузкой для того же сообщения.
- Отправьте сообщение с токеном как
attachments_token. Эндпоинт сообщения забирает черновики отправителя под этим токеном и прикрепляет их с видимостью сообщения. Токен используется один раз.
Переносятся только черновики, загруженные тем же человеком под этим токеном, и клиент никогда не может прикрепить файл к заметке. Файлы сверх attachments.max_per_message остаются черновиками и истекают.
| Метод и путь | Кто | Что делает |
|---|---|---|
POST {mount}/_upload |
Вошедшие люди; гости, пока возможны гостевые обращения | Загружает черновик. Multipart-поля file, token, filename, inline (1 для вставленного изображения) и nonce helpdesk-portal (или заголовок X-Helpdesk-Nonce). Отвечает 201 с черновиком; 400 для истёкшего nonce, 403, когда посетитель не может загружать, 422 для файла, нарушающего правило. |
POST {mount}/_upload/{id}/remove |
Загрузивший черновик | Удаляет черновик. |
POST /helpdesk-pro/attachments |
helpdesk-pro.desk |
Загружает черновик из рабочей области. Те же поля, без nonce. |
GET /helpdesk-pro/attachments/{id} |
helpdesk-pro.desk, и файл должен быть читаемым |
Передаёт файл в рабочую область. |
DELETE /helpdesk-pro/attachments/{id} |
helpdesk-pro.desk, загрузивший черновик |
Удаляет черновик (204). |
Без JavaScript формы портала отправляют обычный <input type="file" name="attachments[]" multiple> вместе с сообщением, и файлы сохраняются и забираются так же.
У трёх маршрутов API нет инструментов MCP. ИИ-клиенты видят файлы как список attachments в каждом сообщении хронологии.