Как устроена CMS ARGELIT

Что нужно для разработки

  • PHP 8.3 или новее (расширения: pdo_mysql, mbstring, openssl, fileinfo, gd, zip, sodium, intl);
  • MySQL 5.7+ / MariaDB 10.3+;
  • любой локальный сервер (OSPanel, Laragon, Docker) или встроенный: php artisan serve.

Скачайте архив CMS, распакуйте в папку сайта и пройдите мастер установки. Всё, что вы пишете, лежит в двух папках: plugins/ и themes/ — ядро трогать не нужно (при обновлении CMS его файлы заменяются).

Папки

Папка Что в ней Можно менять?
core/ ядро CMS: админка, сайт, классы Cms\…, функции core/src/helpers.php нет — заменяется при обновлении
vendor/ библиотеки (Laravel и другие) нет
plugins/ плагины, каждый в своей папке да
themes/ темы; стандартная — themes/starter да (стандартную лучше не править — сделайте дочернюю)
public/uploads/ файлы медиатеки загружаются через админку
storage/ журналы (storage/logs), кэш, резервные копии служебная
.env настройки сайта: база, адрес, отладка да, вручную

Что происходит на каждом запросе

  1. Laravel загружается, ядро регистрирует свои типы записей, права, настройки по умолчанию.
  2. Плагины: подключаются классы (autoload из plugin.json), шаблоны и миграции включённых плагинов, затем их адреса (routes/web.php), затем их plugin.php — и срабатывает событие plugins_loaded.
  3. Тема: подключаются шаблоны и functions.php активной темы (у дочерней — сначала родительской) — событие after_setup_theme.
  4. Событие init: всё загружено. Здесь регистрируют типы записей, таксономии, блоки, роли.
  5. Дальше — сама страница:
    • в админке: admin_init, построение меню (admin_menu), страница раздела;
    • на сайте: CMS понимает, что открыто (запись, рубрика, поиск…), событие template_redirect, выбор шаблона темы (фильтры template_hierarchy, template_include), вывод — с событиями site_head, site_body_open, site_footer.

Поэтому правило простое: в plugin.php и functions.php только подписывайтесь на события, а регистрацию (типы записей, блоки) делайте в обработчике init.

Хуки: события и фильтры

Событие (action) — «сейчас происходит вот это, кто хочет — сделайте своё»:

// Вывести метку в <head> каждой страницы сайта
add_action('site_head', function ($context) {
    echo '<meta name="author" content="Студия ARGELIT">';
});

// Когда пришла заявка с формы — сделать что-то своё
add_action('form_submitted', function ($form, $entry) {
    logger()->info('Новая заявка', ['form' => $form->title]);
}, 10, 2);

Фильтр (filter) — «вот значение, можете его поменять»; обработчик обязательно возвращает значение:

// Дописать к заголовку вкладки браузера
add_filter('document_title', fn (string $title) => $title.' — доставка за час');

// Убрать из sitemap.xml записи рубрики «Архив»
add_filter('sitemap_entries', fn (array $entries) => array_filter($entries, fn ($e) => ! str_contains($e['loc'], '/arhiv/')));
  • Третий параметр — приоритет (по умолчанию 10): меньше — раньше.
  • Четвёртый — сколько аргументов передать обработчику; по умолчанию передаются все.
  • remove_action() / remove_filter() убирают обработчик, has_action(), did_action() — проверки.
  • Свои события и фильтры можно вызывать так же: do_action('my_plugin_saved', $item), apply_filters('my_plugin_price', $price) — так ваш плагин или тему смогут расширять другие.

Все хуки ядра — в справочнике.

Настройки сайта (options)

Настройки — пары «имя → значение» в таблице options; значение может быть строкой, числом или массивом (хранится в JSON).

update_option('otzyvy_per_page', 6);
$perPage = get_option('otzyvy_per_page', 3);   // 3 — если настройки ещё нет
delete_option('otzyvy_per_page');

Называйте свои настройки с префиксом плагина (otzyvy_…), чтобы не столкнуться с чужими. Часто нужные настройки ядра: site_title, site_description, admin_email, timezone, date_format, active_theme, blog_public.

Пользователи, роли и права

Роли: администратор, редактор, автор, участник, подписчик — как в WordPress. Роль — набор прав (edit_posts, manage_options…). Проверяйте права, а не роли:

if (current_user_can('manage_options')) { … }      // в PHP
@can('edit_others_posts') … @endcan                // в шаблоне Blade

Свои роли и права — в init:

add_action('init', function () {
    add_role('manager', 'Менеджер', ['read', 'edit_posts', 'upload_files', 'manage_reviews']);
    app(\Cms\Auth\Roles::class)->addCap('administrator', 'manage_reviews');
});

Laravel внутри

CMS — это приложение на Laravel 13, поэтому в плагинах доступно всё: DB, Eloquent-модели, Http, Cache, Mail (письма уходят так, как настроено в «Настройки → Почта»), очереди, валидация, Blade. Модели ядра: Cms\Models\Post, Term, Media, Menu, Form, FormEntry; пользователи — App\Models\User.

Команды

php artisan cms:plugin list                       # плагины: list | activate папка | deactivate папка
php artisan cms:make-plugin otzyvy --name="Отзывы"
php artisan cms:make-theme pekarnya --name="Пекарня"   # дочерняя тема «Старта»; --copy — полная копия
php artisan cms:admin                             # создать администратора или сменить пароль
php artisan cms:backup                            # резервная копия сайта
php artisan migrate                               # таблицы ядра и включённых плагинов

Отладка

  • На своём компьютере включите в .env APP_DEBUG=true — ошибки будут видны прямо на странице. На рабочем сайте — только false.
  • Журнал ошибок — storage/logs/.
  • Если плагин падает с ошибкой, CMS сама его отключает и показывает текст ошибки с файлом и строкой в разделе «Плагины» — сайт продолжает работать. Ошибка в functions.php темы видна в «Внешний вид → Темы».
  • Плагин, из-за которого не открывается админка, отключается из командной строки: php artisan cms:plugin deactivate папка.
  • «Инструменты → Здоровье сайта» показывает версии PHP, базы, настройки сервера.

Безопасность — коротко

  • Выводите данные через {{ }} (экранирует HTML); {!! !!} — только для HTML, который вы сами проверили.
  • В каждой форме — @csrf; входные данные проверяйте ($request->validate([...])).
  • Проверяйте права (current_user_can) на каждой странице и в каждом обработчике, который что-то меняет.
  • Не храните пароли открытым текстом: для секретов — Crypt::encryptString().