// статьи / статья
#статья #админка#php#модерация#роли

> Админка, которая не мешает: роли, модерация, предпросмотр

Один PHP-кабинет для команды и админа: права, статусы статей и почему «Опубликованные» больше не смешиваются с «На проверке».

SkilinPur 25.10.2026 5 мин чтения серия: как это устроено 👁
Админка, которая не мешает: роли, модерация, предпросмотр

Один кабинет для команды и админа. Не отдельная «CMS», не панель на React. PHP, SQLite, права, статусы и предпросмотр — минимально достаточно, чтобы не мешать работать.

Почему одна админка

Сначала хотелось разделить: «админка» для админа, «личный кабинет» для участника. На практике это два одинаковых интерфейса, которые всё время расходятся.

В итоге остался один кабинет с разными возможностями:

  • Админ видит всё: участников, проекты, статьи, блог, музыку, серверы, бейджи, репорты, настройки, аудит.
  • Участник видит только своё: профиль, свои проекты, свои записи, свои статьи.
  • Общие разделы — дашборд и профиль — есть у всех, но наполнение зависит от роли.

Это не «идеальная RBAC-система». Это практичный компромисс: одна кодовая база, один набор компонентов, одна логика входа.

Роли и меню

Роль — одно поле users.role: admin или member. Этого хватает. Не потому что сложные роли не нужны, а потому что пока не нужны. Если появятся редакторы, модераторы и «только просмотр», тогда будет отдельная матрица прав. Сейчас — два уровня.

Меню строится из массива $pages в admin/index.php. Там же — маршрут, заголовок и флаг «только для админа». А вкладки в шапке — отдельный массив $navAdmin в admin/pages/layout_top.php.

И вот первая грабля: навигация живёт в двух местах. Добавляешь раздел — надо не забыть оба. Один раз забыл: страница есть, роут есть, а вкладки нет. Пользователь до неё не дойдёт. С тех пор в проектной памяти записано: новый раздел — править $pages и $navAdmin.

Почему не «просто форма»

Админка не должна быть набором форм «сохранить/удалить». Иначе она начинает мешать: слишком много кликов, слишком мало контекста.

Что реально помогает:

  • Дашборд со счётчиками: сколько статей на проверке, сколько репортов, сколько участников, что происходит.
  • Фильтры и поиск в списках. Не надо листать всю таблицу, чтобы найти одного человека.
  • Статусы прямо в списке. Видно, что черновик, что на проверке, что опубликовано.
  • Быстрые действия — опубликовать, снять, отклонить, вернуть в черновики.
  • Модалка с полным текстом там, где строки мало: репорт, очередь Telegram, статья.
  • Аудит. Кто что сделал и когда. Не для слежки, а чтобы разбираться в инцидентах.

Админка — это продукт для команды. Если в ней больно работать, команда пишет в чат «сделай за меня», и админ становится бутылочным горлышком.

Модерация: черновик → проверка → публикация

У статьи есть внутренние статусы:

  • draft — черновик;
  • pending — отправлена на проверку;
  • published — опубликована;
  • rejected — отклонена с причиной.

Публично виден только published. Всё остальное — 404, кроме автора, админа и ссылки предпросмотра.

Автор пишет в «Мои статьи», сохраняет сколько угодно, смотрит предпросмотр. Когда готов — отправляет на проверку. Админ читает, одобряет или отклоняет. При отклонении автор видит причину и может исправить.

Кнопка «Снять с публикации» возвращает статью в draft. Не в «скрытую», не в «unlisted» — в обычный черновик. Об этом отдельная статья, но принцип важен: меньше состояний — меньше путаницы.

Почему «Опубликованные» не смешиваются с «На проверке»

Была ошибка: в кабинете статьи со статусом pending попадали в список «Опубликованные». Автор видел статью в опубликованных и думал, что она уже вышла. А её ещё никто не проверил.

Пришлось разделить на три списка:

  • Черновики — draft + rejected;
  • На проверке — pending;
  • Опубликованные — published.

Теперь статус совпадает с тем, что человек видит. Мелочь, но именно такие мелочи создают доверие к интерфейсу.

Предпросмотр по токену

Черновик нельзя показывать всем. Но иногда нужно показать его человеку: редактору, автору, заказчику.

Для этого у статьи есть preview_token. Ссылка вида:

/article/?id=42&preview=8f3c...

работает для черновика и не индексируется. Токен можно перевыпустить. Если статья опубликована, предпросмотр не нужен — работает обычная ссылка.

Это лучше, чем «временная публикация»: не надо менять статус, не надо потом не забыть снять. Ссылка либо есть, либо нет.

Права и проверки

Права проверяются не только в меню, но и в обработчиках. Меню можно скрыть, но запрос всё равно можно отправить руками. Поэтому:

  • каждая запись проверяет is_admin() или авторство;
  • каждое действие проверяет CSRF;
  • вход — по паролю, сессии — в реестре;
  • действия пишутся в аудит;
  • удаление — отдельное действие, а не «сохранить с пустым полем».

Последнее особенно важно. Если форма редактирования приходит с пустым полем, это не значит «удалить». Это может быть сбой, недогрузка или злой умысел. Удаление должно быть явным.

Где мы наступили на грабли

Тест обработчика через CLI пишет в базу

Один раз я тестировал обработчик, подставив $_POST вручную. Он честно сохранил данные. В том числе затёр README проекта: вместо нормального текста осталось x. Восстанавливали из бэкапа.

Урок: обработчик не знает, что он «тест». Если он пишет в базу — он пишет по-настоящему. Для проверок — отдельная запись или снапшот полей до/после.

Модалки вне <main>

Когда искал элементы для PJAX, смотрел только до первого <script>. А модалки README лежали после скриптов, вне <main>. В итоге переходы без перезагрузки их ломали.

Урок: структура страницы важнее, чем кажется. Если что-то живёт вне основного контейнера, это надо знать.

Кэш компонентов

Шапка и футер вставляются JS. Если поправить components/footer.html, но не поднять версию компонента и версию page-init.js в HTML, браузер держит старый футер. Ссылка «есть только на главной» — классический симптом.

Урок: версия — часть компонента. Нет версии — нет гарантии, что пользователь увидит правку.

Что бы я улучшил

  • Сделал бы нормальную матрицу прав, когда появятся роли кроме админа и участника.
  • Добавил бы «историю версий» для статей и проектов, а не только аудит.
  • Сделал бы массовые действия не только для анимации, но и для статей.
  • Убрал бы оставшиеся места, где навигация дублируется.
  • Добавил бы тесты на обработчики с временной БД, чтобы не трогать боевую.

Но даже в текущем виде кабинет делает главное: не мешает. Открыл, нашёл, поправил, отправил. Без «сначала изучи фреймворк».

Вместо вывода

Хорошая админка — скучная. В ней понятные статусы, предсказуемые кнопки и нет сюрпризов.

Самое сложное — не написать формы. Самое сложное — не добавить лишних состояний и не спрятать важное за тремя кликами. Если команда пользуется кабинетом без инструкции, значит, он получился.

// реакции
SkilinPurучастник InIProject