Author SHA1 Message Date
Panfilov Konstantin d489bbcd59 #258 Изменить подпись примеров на Index 2026-10-13 22:03:09 +03:00
Panfilov Konstantin 85e27ff2f2 docs: добавить инструкции агентов
deploy-preland / deploy barbariskatest (push) Failing after 4s
2026-10-08 13:41:52 +03:00
Panfilov Konstantin ff09b43996 Merge feature/requisites-config: реквизиты из config.json, автодеплой на несколько доменов
deploy-preland / deploy barbariskatest (push) Successful in 19s
2026-10-05 20:09:00 +03:00
Panfilov KonstantinandClaude Opus 5.5 98a82fa123 Автодеплой на несколько доменов с разными хостингами: цели — matrix (url, префикс секретов, каталог на FTP)
Цель — домен со своим хостингом: секреты <prefix>_FTP_HOST/USER/PASS ищутся по имени (проверено пробой на раннере),
dir — каталог сайта на FTP ("" — корень). Цели независимы (fail-fast: false), проверка страницы — у каждой своя.
config.json и requisites.html живут на хостинге каждой цели (в репозитории — только config.json.example), деплой их
не трогает; без config.json проверка новой цели падает. README: как добавить домен.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:08:33 +03:00
Panfilov KonstantinandClaude Opus 5.5 6cd01f8714 Реквизиты в подвале — из config.json (requisites), затем requisites.html, затем заглушка
Ключ requisites — список строк, каждая отдельным абзацем, текст экранируется. Без ключа — как было: requisites.html,
а если он пуст — заглушка «ИП Иванов» (решение заказчика). config.json.example — формат ключа; README.

Проверка — .extras/checks-preland-claim/t_requisites.sh (index.php в php:8.3-cli): до правки FAIL=3, после 6/6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:44:53 +03:00
9 changed files with 144 additions and 28 deletions

No files matched your search

+27 -10
View File
@@ -1,8 +1,9 @@
name: deploy-preland name: deploy-preland
# После коммита в main — заливка на хостинг преленда (beget, FTP с TLS) и проверка страницы. # После коммита в main — заливка преленда на хостинги всех целей (FTP с TLS) и проверка страницы каждой цели.
# Реквизиты — секреты репозитория PRELAND_FTP_HOST, PRELAND_FTP_USER, PRELAND_FTP_PASS (порт 21). # Цель — домен со своим хостингом (список — matrix ниже): реквизиты — секреты репозитория <prefix>_FTP_HOST,
# Заливаются файлы репозитория (git ls-files), кроме служебных (.gitea/, README.md, .gitignore) и тех, что живут # <prefix>_FTP_USER, <prefix>_FTP_PASS (порт 21). Цели независимы: сбой одного хостинга остальные не останавливает.
# Заливаются файлы репозитория (git ls-files), кроме служебных (.gitea/, README.md, AGENTS.md, CLAUDE.md, .gitignore) и тех, что живут
# только на хостинге: config.json (в git его нет) и requisites.html (реквизиты заполняют на месте — пустой файл # только на хостинге: config.json (в git его нет) и requisites.html (реквизиты заполняют на месте — пустой файл
# из git их бы затёр). На хостинге ничего не удаляется: удалённое из репозитория убирать вручную. # из git их бы затёр). На хостинге ничего не удаляется: удалённое из репозитория убирать вручную.
@@ -13,14 +14,28 @@ on:
jobs: jobs:
deploy: deploy:
name: deploy ${{ matrix.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# name — имя цели, url — адрес преленда (для проверки), prefix — префикс секретов <prefix>_FTP_*,
# dir — каталог сайта на FTP ("" — корень FTP, как на beget). Новый домен — новая запись и три секрета;
# config.json (и requisites.html) создают на хостинге цели — без него проверка страницы не пройдёт.
- name: barbariskatest
url: https://preland.anton.barbariskatest.ru
prefix: PRELAND
dir: ""
defaults: defaults:
run: run:
shell: sh shell: sh
env: env:
PRELAND_URL: https://preland.anton.barbariskatest.ru PRELAND_URL: ${{ matrix.url }}
REMOTE_DIR: ${{ matrix.dir }}
steps: steps:
- name: Install tools (Alpine) - name: Install tools (Alpine)
@@ -31,21 +46,23 @@ jobs:
- name: Upload over FTP (TLS) - name: Upload over FTP (TLS)
env: env:
FTP_HOST: ${{ secrets.PRELAND_FTP_HOST }} FTP_HOST: ${{ secrets[format('{0}_FTP_HOST', matrix.prefix)] }}
FTP_USER: ${{ secrets.PRELAND_FTP_USER }} FTP_USER: ${{ secrets[format('{0}_FTP_USER', matrix.prefix)] }}
FTP_PASS: ${{ secrets.PRELAND_FTP_PASS }} FTP_PASS: ${{ secrets[format('{0}_FTP_PASS', matrix.prefix)] }}
run: | run: |
set -eu set -eu
[ -n "$FTP_HOST" ] && [ -n "$FTP_USER" ] && [ -n "$FTP_PASS" ] || { echo "нет секретов PRELAND_FTP_*"; exit 1; } [ -n "$FTP_HOST" ] && [ -n "$FTP_USER" ] && [ -n "$FTP_PASS" ] || { echo "нет секретов ${{ matrix.prefix }}_FTP_*"; exit 1; }
dir=$(printf '%s' "$REMOTE_DIR" | sed 's#^/*##; s#/*$##')
case "$dir" in *[!A-Za-z0-9._/-]*) echo "недопустимый dir: $dir"; exit 1 ;; esac
umask 077 umask 077
netrc=$(mktemp); list=$(mktemp) netrc=$(mktemp); list=$(mktemp)
trap 'rm -f "$netrc" "$list"' EXIT trap 'rm -f "$netrc" "$list"' EXIT
printf 'machine %s login %s password %s\n' "$FTP_HOST" "$FTP_USER" "$FTP_PASS" > "$netrc" printf 'machine %s login %s password %s\n' "$FTP_HOST" "$FTP_USER" "$FTP_PASS" > "$netrc"
git ls-files | grep -vE '^(\.gitea/|README\.md$|\.gitignore$|config\.json$|requisites\.html$)' > files.txt git ls-files | grep -vE '^(\.gitea/|README\.md$|AGENTS\.md$|CLAUDE\.md$|\.gitignore$|config\.json$|requisites\.html$)' > files.txt
# имя уходит в адрес ftp:// как есть — пробелы и прочее экранирование не поддерживаем # имя уходит в адрес ftp:// как есть — пробелы и прочее экранирование не поддерживаем
if grep -q '[^A-Za-z0-9._/-]' files.txt; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' files.txt; exit 1; fi if grep -q '[^A-Za-z0-9._/-]' files.txt; then echo "имена с недопустимыми символами:"; grep '[^A-Za-z0-9._/-]' files.txt; exit 1; fi
# один curl на все файлы — одно соединение FTP+TLS вместо соединения на файл # один curl на все файлы — одно соединение FTP+TLS вместо соединения на файл
while read -r f; do printf 'upload-file = "%s"\nurl = "ftp://%s/%s"\n' "$f" "$FTP_HOST" "$f"; done < files.txt > "$list" while read -r f; do printf 'upload-file = "%s"\nurl = "ftp://%s/%s%s"\n' "$f" "$FTP_HOST" "${dir:+$dir/}" "$f"; done < files.txt > "$list"
curl -sS --ssl-reqd --netrc-file "$netrc" --ftp-create-dirs --retry 3 --fail-early -K "$list" curl -sS --ssl-reqd --netrc-file "$netrc" --ftp-create-dirs --retry 3 --fail-early -K "$list"
echo "залито файлов: $(wc -l < files.txt)" echo "залито файлов: $(wc -l < files.txt)"
+33
View File
@@ -0,0 +1,33 @@
# Инструкции агентов для `preland`
Область действия — репозиторий `preland/`. Общие правила рабочей области находятся в
[AGENTS.md](../AGENTS.md) и [CLAUDE.md](../CLAUDE.md); канонический процесс и каталог ролей —
[в общей политике](../.known-base/Соглашения/Процесс%20командной%20разработки%20и%20каталог%20ролей.md).
Этот файл и [CLAUDE.md](CLAUDE.md) содержат одинаковые локальные правила; обновлять их вместе.
## Перед работой
- Прочитать [README.md](README.md), [заметку проекта](../.known-base/Проекты/Преленд%20—%20anton-preland.md),
а для выката — `.gitea/workflows/deploy.yml` и `config.json.example`.
- Проверить состояние ветки и чужие изменения в `preland/`; изменения API сайта согласовать
с его контрактом и инструкциями `api/`.
## Локальные ограничения
- Преленд — PHP без фреймворка и сборки; `Yii::t` реализован локальной заглушкой `i18n.php`.
Сохранять существующие компоненты и передачу меток трафика на сайт.
- Загрузка идёт на `POST /api/images/public/create` с `file` и случайным `client_key`,
переход — через `/api/images/public/claim?key=…`. Ключ одноразовый, срок 30 минут;
переход напрямую на `/public/upscale` не заменяет передачу владельца через claim.
- `host` задаёт сайт назначения; HTTPS необходим для `crypto.randomUUID()`.
Не обходить серверные правила доступа к загрузке и результату обработки.
- Рабочий `config.json` каждой цели и `requisites.html` живут на хостинге: не добавлять
их в git, не перезаписывать и не удалять при деплое. В репозитории — только образец конфига.
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
в согласованном окружении, не публиковать ключи загрузок и FTP-секреты.
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
+33
View File
@@ -0,0 +1,33 @@
# Инструкции агентов для `preland`
Область действия — репозиторий `preland/`. Общие правила рабочей области находятся в
[AGENTS.md](../AGENTS.md) и [CLAUDE.md](../CLAUDE.md); канонический процесс и каталог ролей —
[в общей политике](../.known-base/Соглашения/Процесс%20командной%20разработки%20и%20каталог%20ролей.md).
Этот файл и [AGENTS.md](AGENTS.md) содержат одинаковые локальные правила; обновлять их вместе.
## Перед работой
- Прочитать [README.md](README.md), [заметку проекта](../.known-base/Проекты/Преленд%20—%20anton-preland.md),
а для выката — `.gitea/workflows/deploy.yml` и `config.json.example`.
- Проверить состояние ветки и чужие изменения в `preland/`; изменения API сайта согласовать
с его контрактом и инструкциями `api/`.
## Локальные ограничения
- Преленд — PHP без фреймворка и сборки; `Yii::t` реализован локальной заглушкой `i18n.php`.
Сохранять существующие компоненты и передачу меток трафика на сайт.
- Загрузка идёт на `POST /api/images/public/create` с `file` и случайным `client_key`,
переход — через `/api/images/public/claim?key=…`. Ключ одноразовый, срок 30 минут;
переход напрямую на `/public/upscale` не заменяет передачу владельца через claim.
- `host` задаёт сайт назначения; HTTPS необходим для `crypto.randomUUID()`.
Не обходить серверные правила доступа к загрузке и результату обработки.
- Рабочий `config.json` каждой цели и `requisites.html` живут на хостинге: не добавлять
их в git, не перезаписывать и не удалять при деплое. В репозитории — только образец конфига.
- Коммит в `main` запускает FTP-автодеплой на все цели workflow; откат через `git revert`
также запускает выкат. Коммит, push и изменения хостинга — только по прямому поручению.
- Проверки кода и браузера проводить в Docker без установки ПО на хост.
Проверка загрузки по боевому адресу вызывает реальную обработку; выполнять её только
в согласованном окружении, не публиковать ключи загрузок и FTP-секреты.
План, журнал и база знаний ведутся в корне рабочей области по общим правилам. Ссылки на
политику и документацию не подключают MCP и не дают разрешения на публикацию или выкат.
+33 -13
View File
@@ -58,7 +58,7 @@
| `components/preview.php`, `process.php`, `examples.php` | превью файла, индикатор загрузки, примеры «до/после» | | `components/preview.php`, `process.php`, `examples.php` | превью файла, индикатор загрузки, примеры «до/после» |
| `i18n.php`, `messages/{ru,en}/` | заглушка `Yii::t` и переводы; язык сейчас всегда `ru` (определение языка закомментировано) | | `i18n.php`, `messages/{ru,en}/` | заглушка `Yii::t` и переводы; язык сейчас всегда `ru` (определение языка закомментировано) |
| `docs/` | `logo.svg`, `logo-alt.svg` и документы в PDF: тарифы, оферта, соглашение, политика | | `docs/` | `logo.svg`, `logo-alt.svg` и документы в PDF: тарифы, оферта, соглашение, политика |
| `requisites.html` | реквизиты для подвала (HTML). Пустой или отсутствующий — выводится **заглушка «ИП Иванов Иван Иванович»** из `template.php`: на боевом заполнить | | `requisites.html` | реквизиты для подвала (HTML), если в `config.json` нет `requisites`. Без обоих — **заглушка «ИП Иванов Иван Иванович»** из `template.php` |
| `images/`, `fonts/`, `favicon/`, `*.css` | статика; `style.min.css` встраивается в страницу | | `images/`, `fonts/`, `favicon/`, `*.css` | статика; `style.min.css` встраивается в страницу |
| `.htaccess` | только сроки кэширования статики (Apache) | | `.htaccess` | только сроки кэширования статики (Apache) |
| `config.json.example` | образец конфига; рабочий `config.json` в git не хранится | | `config.json.example` | образец конфига; рабочий `config.json` в git не хранится |
@@ -69,6 +69,7 @@
|---|:---:|---| |---|:---:|---|
| `host` | **да** | домен сайта, куда идут загрузка и переход (`zoomzilla.ru`). Без него берётся домен самого преленда — запросы уходят на преленд и получают 404 | | `host` | **да** | домен сайта, куда идут загрузка и переход (`zoomzilla.ru`). Без него берётся домен самого преленда — запросы уходят на преленд и получают 404 |
| `utm` | да | метки: `weights` (значение → доля трафика для `rnd`) и `default` | | `utm` | да | метки: `weights` (значение → доля трафика для `rnd`) и `default` |
| `requisites` | нет | реквизиты в подвале — список строк, каждая отдельным абзацем (текст экранируется). Важнее `requisites.html`; без обоих — заглушка «ИП Иванов» |
| `assetsUriPrefix` | нет | откуда брать статику. Пусто — из своего каталога; если на хостинге нет каталога `images`, статика берётся с сайта (`https://<host>`) | | `assetsUriPrefix` | нет | откуда брать статику. Пусто — из своего каталога; если на хостинге нет каталога `images`, статика берётся с сайта (`https://<host>`) |
| `fontsUrl`, `faviconUrl`, `spritePath` | нет | адреса шрифтов, иконок и SVG-спрайта, если они не в `assetsUriPrefix` | | `fontsUrl`, `faviconUrl`, `spritePath` | нет | адреса шрифтов, иконок и SVG-спрайта, если они не в `assetsUriPrefix` |
| `logoAltExt` | нет | расширение второго логотипа (`svg` по умолчанию) | | `logoAltExt` | нет | расширение второго логотипа (`svg` по умолчанию) |
@@ -86,7 +87,7 @@
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент # дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
``` ```
3. **Конфиг и реквизиты.** На сервере, рядом с `index.php` (и заполнить `requisites.html` — иначе в подвале заглушка): 3. **Конфиг и реквизиты.** На сервере, рядом с `index.php`; реквизиты — ключ `requisites` (иначе в подвале заглушка):
```bash ```bash
cp config.json.example config.json # host — домен сайта, метки — по кампании cp config.json.example config.json # host — домен сайта, метки — по кампании
``` ```
@@ -122,18 +123,37 @@
## Автодеплой (Gitea Actions) ## Автодеплой (Gitea Actions)
`.gitea/workflows/deploy.yml`: после каждого коммита в `main` раннер заливает файлы репозитория на хостинг `.gitea/workflows/deploy.yml`: после каждого коммита в `main` раннер заливает файлы репозитория на хостинги **всех
`https://preland.anton.barbariskatest.ru` по FTP с TLS и проверяет страницу. целей** по FTP с TLS и проверяет страницу каждой. Цель — домен со своим хостингом; список целей — `matrix` в workflow:
- **Реквизиты** — секреты репозитория `PRELAND_FTP_HOST`, `PRELAND_FTP_USER`, `PRELAND_FTP_PASS` (Gitea → настройки ```yaml
репозитория → Actions → секреты; порт 21). В коде их нет. - name: barbariskatest # имя цели (видно в списке прогонов: deploy barbariskatest)
- **Не заливаются** служебные `.gitea/`, `README.md`, `.gitignore` и то, что живёт только на хостинге: `config.json` url: https://preland.anton.barbariskatest.ru # адрес преленда — для проверки после заливки
и `requisites.html`. Реквизиты заполняют на хостинге, а пустой файл из git их бы затёр. Удалённые из репозитория prefix: PRELAND # секреты PRELAND_FTP_HOST, PRELAND_FTP_USER, PRELAND_FTP_PASS
файлы на хостинге не стираются — их убирают вручную. dir: "" # каталог сайта на FTP: "" — корень FTP (beget), иначе, например, public_html
- **Проверка после заливки.** На странице должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет ```
`config.json` с `host`), и переход на `claim`. Иначе прогон падает.
- **Другой домен.** Скопировать workflow, поменять `PRELAND_URL` и завести свои секреты. Цели выкатываются независимо (`fail-fast: false`): сбой одного хостинга не останавливает остальные.
- **Откат** — `git revert` проблемного коммита в `main`: автодеплой зальёт прежнюю версию.
**Новый домен на своём хостинге:**
1. Домен и HTTPS — как в «Деплой на произвольный домен», шаг 1.
2. Секреты `<ПРЕФИКС>_FTP_HOST`, `<ПРЕФИКС>_FTP_USER`, `<ПРЕФИКС>_FTP_PASS` (Gitea → настройки репозитория →
Actions → секреты; порт 21, FTP с TLS). В коде реквизитов нет.
3. На хостинге, в каталоге сайта, — `config.json` из `config.json.example`: свой `host`, метки, `requisites`.
В репозитории только образец: `config.json` у каждого домена свой и живёт на его хостинге.
4. Запись в `matrix` — коммит в `main`, автодеплой выкатит все цели. Пока на новом хостинге нет `config.json`,
его проверка падает: загрузка ушла бы на сам преленд.
**Что заливается.** Файлы репозитория (`git ls-files`), кроме служебных (`.gitea/`, `README.md`, `.gitignore`)
и живущих на хостинге `config.json` и `requisites.html`. На хостинге ничего не удаляется: файлы, убранные
из репозитория, удалять вручную.
**Проверка после заливки.** На странице цели должен быть адрес загрузки на сайте, а не на самом преленде (иначе нет
`config.json` с `host`), и переход на `claim`. Иначе прогон этой цели падает.
**Откат** — `git revert` проблемного коммита в `main`: автодеплой зальёт прежнюю версию на все цели.
**SFTP.** Сейчас только FTP с TLS. Хостинг без FTP — поле `protocol` в цели и `lftp` вместо curl в шаге заливки.
**Откат вручную.** Перед заменой файлов — копия изменяемых файлов (`index.php`, `components/*`) с хостинга; вернуть их той же **Откат вручную.** Перед заменой файлов — копия изменяемых файлов (`index.php`, `components/*`) с хостинга; вернуть их той же
загрузкой. Новый преленд требует на сайте `/api/images/public/claim` (api с 2026-10-05): со старым api переход загрузкой. Новый преленд требует на сайте `/api/images/public/claim` (api с 2026-10-05): со старым api переход
+2 -2
View File
@@ -45,7 +45,7 @@ $resultOrig = $getAssetUrl('1/result.avif');
</div> </div>
<div class="image-examples"> <div class="image-examples">
<div class="image-examples-label"><?= Yii::t('app', 'Примеры изображений') ?></div> <div class="image-examples-label"><?= Yii::t('app', 'Примеры улучшения') ?></div>
<div class="image-examples-list"> <div class="image-examples-list">
<?php for ($i = 1; $i <= 5; $i++): <?php for ($i = 1; $i <= 5; $i++):
$thumbMob = $getAssetUrl("{$i}/thumb_mob.avif"); $thumbMob = $getAssetUrl("{$i}/thumb_mob.avif");
@@ -310,4 +310,4 @@ $resultOrig = $getAssetUrl('1/result.avif');
} else { } else {
initStrictImagePipeline(); initStrictImagePipeline();
} }
</script> </script>
+7
View File
@@ -1,6 +1,13 @@
{ {
"host": "zoomzilla.ru", "host": "zoomzilla.ru",
"requisites": [
"ИП Иванов Иван Иванович",
"123123, г. Москва, Пресненская наб., 12",
"ИНН: 123404385406",
"ОГРНИП: 123420000019175"
],
"utm": { "utm": {
"indpic": { "indpic": {
"weights": { "weights": {
+1 -1
View File
@@ -265,7 +265,7 @@ return [
'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.' => 'After setup, you can submit images for processing through the API. Pricing is based on the upscale factor.', 'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.' => 'After setup, you can submit images for processing through the API. Pricing is based on the upscale factor.',
'Привяжите карту' => 'Add a Payment Method', 'Привяжите карту' => 'Add a Payment Method',
'Привязать карту' => 'Add a Payment Method', 'Привязать карту' => 'Add a Payment Method',
'Примеры изображений' => 'Image Examples', 'Примеры улучшения' => 'Enhancement examples',
'Проверьте папку "Спам" и "Корзина".' => 'Check your Spam and Trash folders.', 'Проверьте папку "Спам" и "Корзина".' => 'Check your Spam and Trash folders.',
'Проверяем PIN и выполняем оплату...' => 'Verifying the code and processing your payment...', 'Проверяем PIN и выполняем оплату...' => 'Verifying the code and processing your payment...',
'Проверяем email...' => 'Checking your email...', 'Проверяем email...' => 'Checking your email...',
+1 -1
View File
@@ -275,7 +275,7 @@ return [
'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.' => 'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.', 'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.' => 'После подключения можно передавать изображения на обработку по API. Стоимость считается по масштабу улучшения.',
'Привяжите карту' => 'Привяжите карту', 'Привяжите карту' => 'Привяжите карту',
'Привязать карту' => 'Привязать карту', 'Привязать карту' => 'Привязать карту',
'Примеры изображений' => 'Примеры изображений', 'Примеры улучшения' => 'Примеры улучшения',
'Проверьте папку "Спам" и "Корзина".' => 'Проверьте папку "Спам" и "Корзина".', 'Проверьте папку "Спам" и "Корзина".' => 'Проверьте папку "Спам" и "Корзина".',
'Проверяем PIN и выполняем оплату...' => 'Проверяем PIN и выполняем оплату...', 'Проверяем PIN и выполняем оплату...' => 'Проверяем PIN и выполняем оплату...',
'Проверяем email...' => 'Проверяем email...', 'Проверяем email...' => 'Проверяем email...',
+7 -1
View File
@@ -104,8 +104,14 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="h5 mb-3"><?= Yii::t('app', 'Наши реквизиты') ?></div> <div class="h5 mb-3"><?= Yii::t('app', 'Наши реквизиты') ?></div>
<?php <?php
// реквизиты: config.json (requisites — строки) → requisites.html → образец
$requisites = array_filter((array) ($config['requisites'] ?? []), fn ($line) => is_scalar($line) && trim((string) $line) !== '');
$requisitesFile = __DIR__ . '/requisites.html'; $requisitesFile = __DIR__ . '/requisites.html';
if (file_exists($requisitesFile) && filesize($requisitesFile) > 0) { if ($requisites) {
foreach ($requisites as $line) {
?><p class="mb-0"><?= htmlspecialchars((string) $line, ENT_QUOTES, 'UTF-8') ?></p>
<?php }
} elseif (file_exists($requisitesFile) && filesize($requisitesFile) > 0) {
echo file_get_contents($requisitesFile); echo file_get_contents($requisitesFile);
} else { } else {
?> ?>