Функционал OctoberCMS, как и любой другой CMS, можно расширять. Единицей такого расширения является плагин. В этой заметке я расскажу немного о том, что такое плагин для OctoberCMS, как его сделать, из чего он состоит и что куда класть внутри.
Большую часть информации, рассматриваемой далее, можно почерпнуть из официальной документации OctoberCMS. В этой заметке я постараюсь не столько пересказать содержимое доков, сколько обобщить то, что мне удалось выяснить в процессе разработки и использования плагинов для October.
Где взять плагины
Главным местом обитания плагинов для OctoberCMS является маркет, плагины там бывают как бесплатные, так и платные. Основное удобство маркета — быстрая инсталляция. Кроме того, на маркете можно объединять плагины в проекты (сборки), но об этом как-нибудь в другой раз.
Плагин с маркета устанавливается в один клик из админки (в которой есть удобный поиск):
Кстати, не каждый сразу найдет, где именно находится установка плагинов. Она находится в меню «Настройки», пункт Система → Обновления (/backend/system/updates
). Так уж вышло.
А еще можно установить плагин в одну строчку командой artisan
:
php artisan plugin:install RainLab.Blog
и удаляется так же быстро:
php artisan plugin:remove RainLab.Blog
Здесь RainLab — имя автора плагина, а Blog — название.
Плагины, которым повезло меньше и они (пока) отсутствуют на маркете, обитают обычно, как и сам October, на гитхабе. Там же обитают и dev-версии популярных бесплатных плагинов с маркета, ссылки на их репозитории обычно указаны на странице плагина.
Если плагин отсутствует на маркете, устанавливать его придется вручную. Это нетрудно: нужно просто скачать директорию с плагином и положить в plugins/<authorname>/
, где <authorname>
— это имя автора плагина (например plugins/graker/blogarchive
). И затем нужно запустить миграции командой:
php artisan october:up
Нужно заметить, что October по умолчанию считает плагины, лежащие в plugins, включенными, а october:up
нужен только для запуска миграций.
Миграции — это файлы, описывающие создание (и удаление) таблиц БД, необходимых для работы плагина.
Поэтому установленный вручную плагин будет работать и без october:up
. Просто необходимые ему таблицы БД не создадутся и при первой же попытке что-либо считать или записать — произойдет ошибка.
Как создать плагин
Самый простой способ — использовать скаффолдинг, то есть с помощью команды artisan
:
php artisan create:plugin Author.MyPlugin
Команда создаст в /plugins директорию author (если ее еще нет), а в ней — директорию myplugin с файлами плагина. И да, как вы уже догадались, плагины в OctoberCMS группируются по авторам. Кроме того, нужно иметь в виду, что каждый плагин должен быть в неймспейсе AuthorName\PluginName
(с соблюдением регистра).
Структура плагина
В директории с плагином можно найти вот что:
- assets/ — директория с ассетами (скриптами, картинками, стилями);
- classes/ — директория со вспомогательными классами, нужными для работы плагина;
- components/ — компоненты, определенные в плагине;
- config/ — настройки плагина (вариант «в коде»);
- controllers/ и models/ — очевидно, контроллеры и модели;
- console/ — команды artisan;
- updates/ — миграции и файл
version.yaml
; - widgets/ — описанные в плагине виджеты;
- Plugin.php — регистрационный файл плагина.
Теперь посмотрим на основные составляющие плагин классы более подробно.
Регистрационный файл
Начнем с конца — с файла Plugin.php
. Этот файл содержит основную информацию о том, что вообще в нашем плагине есть: как он называется, какие компоненты, настройки, пункты меню и обработчики событий в нем зарегистрированы и т.д. Поэтому файл Plugin.php
называется в документации регистрационным.
Как добавить в плагин компоненты, модели и настройки — я покажу чуть позже. А пока посмотрим на начало регистрационного файла (на примере моего плагина PhotoAlbums).
class Plugin extends PluginBase
{
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => 'PhotoAlbums',
'description' => 'Create, display and manage galleries of photos arranged in albums',
'author' => 'Graker',
'icon' => 'icon-camera-retro',
'homepage' => 'https://github.com/graker/photoalbums',
];
}
// ...
}
Это основная информация о плагине: название, описание, автор, иконка (для меню админки, из набора October) и место жительства.
Если наш плагин должен иметь зависимости (от других плагинов), они тоже объявляются в регистрационном файле, в свойстве $require
:
public $require = ['RainLab.Blog', 'RainLab.Pages'];
— так мы объявили зависимости от плагинов Blog и Static Pages.
Метод boot()
Метод boot()
класса нашего плагина будет вызываться всякий раз перед обработкой входящего запроса. Этот метод хорошо подходит, чтобы, например, прицепиться к событиям, на которые мы хотим реагировать в нашем плагине.
public function boot() {
Event::listen('backend.form.extendFields', function (Form $widget) {
// модифицируем $widget
});
}
В данном примере мы реагируем на событие backend.form.extendFields
, которое позволяет нам модифицировать форму, созданную в другом плагине (это не единственный способ модифицировать форму, просто пример).
События в OctoberCMS — это основное средство взаимодействия между плагинами. Можно подслушивать и реагировать на системные события, события, определенные в других плагинах, и конечно генерировать свои события. Подробнее об этом можно почитать в документации.
Настройки и страницы админки
Обычно в плагине объявляется одна или несколько страниц админки с настройками, формами, списками и все такое.
Страницы админки (то есть, бэкэнда) объявляются в регистрационном файле. Вот пример объявления страницы для настроек:
public function registerSettings()
{
return [
'settings' => [
'label' => 'MyPlugin',
'description' => 'Manage MyPlugin Settings.',
'icon' => 'icon-camera-retro',
'class' => 'Author\MyPlugin\Models\Settings',
'order' => 100,
'permissions' => ['author.myplugin.access_settings'],
]
];
}
В результате в админке, на странице Settings в левой колонке, в секции Other появится пункт MyPlugin, содержащий форму настроек нашего плагина. Состав формы зависит от того, что мы укажем в модели настроек. Для настроек используется модель Author\MyPlugin\Models\Settings
, наследующая класс Model
и реализующая поведение System.Behaviors.SettingsModel
. Хранятся такие настройки в базе данных.
Есть возможность хранить настройки и в коде, но в таком случае для них не генерируется страница админки. И то, и другое описано в документации.
Помимо форм настроек, в регистрационном файле можно объявить и необходимые страницы админки «верхнего уровня», то есть которые появляются в верхнем меню. Обычно эти страницы используются контроллерами для вывода списков моделей и форм создания/редактирования моделей. Объявляются страницы админки вот так:
public function registerNavigation()
{
return [
'myplugin' => [
'label' => 'My Plugin',
'url' => Backend::url('author/myplugin/mymodels'),
'icon' => 'icon-camera-retro',
'permissions' => ['author.myplugin.manage_mymodels'],
'order' => 500,
'sideMenu' => [
'new_model' => [
'label' => 'New MyModel',
'icon' => 'icon-plus',
'url' => Backend::url('author/myplugin/mymodels/create'),
'permissions' => ['author.myplugin.manage_mymodels'],
],
'mymodels' => [
'label' => 'MyModels',
'icon' => 'icon-copy',
'url' => Backend::url('author/myplugin/mymodels'),
'permissions' => ['author.myplugin.manage_mymodels'],
],
],
],
];
}
В данном примере регистрируется пункт меню админки My Plugin, и два подпункта — New MyModel (форма для создания модели) и MyModels (список существующих моделей). Подпункты будут показаны в боковом меню при выборе My Plugin в главном (верхнем) меню. Причем страница, соответствующая пункту MyModels будет показана по умолчанию (т.к. у этой страницы указан такой же url, как у страницы верхнего уровня).
Также для каждой страницы задается иконка и разрешения, которыми должен обладать пользователь, чтобы он мог зайти на страницу. Вот пример страницы админки для плагина PhotoAlbums (тут определено больше пунктов в сайдбаре):
Как привязать к этим страницам контроллеры, будет показано ниже.
Модели
Одна из основных причин создать новый плагин — это необходимость объявить новый тип данных на сайте и описать работу с ними. Поскольку Laravel, на базе которого сделан October — это MVC-фреймворк, новые типы данных — это модели. Модель — это класс Model из Laravel, реализующий паттерн Active Record. В October класс модели дополнен разными полезностями, такими как генерация формы модели из yaml-файла или возможность приаттачить файл в одну строчку кода.
В рамках создаваемого плагина модели живут в директории models
и регистрируются автоматически, так что объявлять их в Plugin.php
не нужно. Для каждой модели нужно создать файл models/MyModel.php
с классом модели, наследующим класс Model.
Кроме того, в директории models/mymodel/
можно создать два файла: colums.yaml
и fields.yaml
. Файл columns.yaml
описывает поля модели, которые можно вывести в списке моделей:
# ===================================
# List Column Definitions
# ===================================
columns:
title:
label: Title
searchable: true
created_at:
label: Created
type: date
updated_at:
label: Updated
type: date
То есть если мы хотим где-нибудь в админке иметь таблицу существующих моделей, то ее можно будет автоматически сгенерировать в классе контроллера (см. ниже) и поля будут взяты из этого файла.
Файл fields.yaml
описывает форму, используемую для создания и редактирования модели:
# ===================================
# Form Field Definitions
# ===================================
fields:
title:
label: Title
span: full
placeholder: My model title
required: true
description:
label: Description
type: richeditor
size: large
span: right
image:
label: Photo
type: fileupload
mode: image
imageWidth: 320
imageHeight: 240
span: left
Этот файл также используется контроллером для генерации формы создания/редактирования модели.
Чтобы не создавать файлы и директории вручную, можно просто написать:
php artisan create:model AuthorName.MyPlugin MyModel
и файлы будут сгенерированы.
Миграции
Разумеется, чтобы созданными моделями можно было пользоваться, для них нужно создать таблицы в БД. По умолчанию модель ожидает, что ее таблица будет называться вот так:
authorname_myplugin_mymodels
То есть в нижнем регистре имя автора плагина → подчеркивание → название плагина → подчеркивание → название модели во множественном числе (!). Название таблицы, если очень хочется, можно изменить, сообщив его в свойстве модели.
Чтобы сгенерировать таблицу для модели, нужно создать файл миграции. Если для создания файлов модели мы использовали команду artisan create:model
, то файл create_mymodels_table.php
будет сгенерирован в директории updates/
автоматически.
Файл миграции выглядит примерно так:
<?php namespace AuthorName\MyPlugin\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class CreateMyModelsTable extends Migration
{
public function up()
{
Schema::create('authorname_myplugin_mymodels', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('title')->nullable();
$table->text('description')->nullable();
$table->integer('user_id')->unsigned()->nullable()->index();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('authorname_myplugin_mymodels');
}
}
Метод up()
будет вызван по команде php artisan october:up
и создаст описанную таблицу автоматически. А метод down()
предназначен для отката миграции (удаления данных) и вызывается по команде php artisan october:down
.
Обновления и версии плагина
Чтобы миграции запустились автоматически при установке плагина, или при выполнении october:up
, их нужно описать в файле обновлений, updates/version.yaml
:
1.0.1:
- Initialize plugin
- create_mymodels_table.php
Соответственно, если мы доработали плагин и наши доработки требуют изменений в БД (например, надо добавить поле в таблицу), то мы создаем еще одну миграцию и добавляем запись в version.yaml
:
1.0.1:
- Initialize plugin
- create_mymodels_table.php
1.1.0:
- Add column to mymodels table
- update_mymodels_table.php
Тогда при запуске october:up
миграция из update_mymodels_table.php
будет выполнена автоматчески. Также записи в version.yaml
нужно добавлять для любых изменений, если плагин живет на маркете — чтобы пользователи могли автоматически обновиться.
Кстати, если нужно полностью (с потерей данных!) переустановить плагин, есть команда plugin:refresh
:
php artisan plugin:refresh AuthorName.PluginName
Контроллеры
Чтобы добавленными в плагин моделями можно было управлять (создавать, редактировать и удалять), нам понадобится контроллер. Его можно сгенерировать командой
php artisan create:controller AuthorName.MyPlugin MyModels
Появится файл с классом контроллера controllers/MyModels.php
и директория controllers/mymodels/
с файлами представлений (view) и настроек.
Контроллеры и их представления — это по сути кирпичики, из которых (а также из настроек) построен бэк-энд сайта. Как правило, под каждую модель в плагине нужно создать отдельный контроллер.
Контроллеры в October устроены так, чтобы по возможности избавить разработчика от рутинных операций создания списков моделей и форм создания/обновления/удаления. Нужно лишь немного сконфигурировать контроллер и описать свою уникальную бизнес-логику, и админка будет готова. Для примера, сделаем страницу со списком созданных моделей. Что для этого нужно.
Во-первых, нужно сообщить в классе контроллера, что он реализует поведение (behavior) списка и указать, в каком файле лежат настройки списка:
class MyModels extends Controller
{
public $implement = [
'Backend.Behaviors.ListController'
];
public $listConfig = 'config_list.yaml';
// ...
}
Затем создать файл конфига controllers/mymodels/config_list.yaml
:
# ===================================
# List Behavior Config
# ===================================
# Укажем адрес конфига колонок модели (мы его создали выше)
list: $/authorname/myplugin/models/mymodel/columns.yaml
# Укажем класс модели
modelClass: AuthorName\MyPlugin\Models\MyModel
# Заголовок списка
title: Мои модели
# URL для редактирования модели
recordUrl: authorname/myplugin/mymodels/update/:id
# Количество записей на странице
recordsPerPage: 20
# Делаем колонки сортируемыми
showSorting: true
# Тулбар над списком
toolbar:
# Указываем, где описаны кнопки тулбара
buttons: list_toolbar
# Настраиваем поиск
search:
prompt: backend::lang.list.search_prompt
Это пример конфига для списка моделей, он не содержит все возможные настройки (они есть в документации), но даже из этого примера получается весьма функциональная страница админки с возможностью выводить модели списком, сортировать по заголовку и дате создания/обновления (как мы написали ранее в columns.yaml
), удалять выбранные модели и даже искать модели по названию. Таблица будет сгенерирована вся такая симпатичная (см. картинку выше) и кнопочки/поиски/сортировки из коробки будут работать с AJAX, т.е. без перезагрузки страницы.
Единственное что осталось, чтобы список был виден — привязать его к странице настроек, которую мы ранее зарегистрировали в Plugin.php
. Для этого мы в конструкторе нашего контроллера MyModels
напишем:
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Author.MyPlugin', 'myplugin', 'mymodels');
}
Здесь мы связали контроллер с пунктом меню админки My Plugin, описанным ранее в Plugin.php
.
Компоненты
Если контроллеры определяют бэкэнд, то для фронтэнда сайта строительным материалом в October служат компоненты. Компонент — это динамически генерируемый кусочек содержимого сайта. Лента записей в блоге, отдельный пост, верхнее меню, список тегов — это примеры компонентов.
Можно сказать, что компонент — это связующее звено между активной темой сайта (в которой определены все страницы фронт-энда, фрагменты и шаблоны) и плагинами сайта. Именно с помощью компонентов плагин предоставляет активной теме доступ к генерируемому в рамках плагина содержимому.
Как это происходит? Допустим, мы делаем блог и включили плагин Blog. Теперь нам нужно сделать страницу с лентой постов. Мы создаем в теме страницу и прикрепляем к ней компонент Blog Posts, который и рендерит ленту постов. Выглядеть код нашей страницы будет примерно вот так:
title = "Blog"
url = "/blog/:page?"
[blogPosts]
postsPerPage = "10"
==
{% component 'blogPosts' %}
То есть мы просто указываем настройки компонента (в данном случае, количество постов на страницу) и рендерим его с помощью тега {% component 'blogPosts' %}
. Все остальное происходит внутри компонента, то есть в плагине Blog. Конечно, скорее всего мы захотим внутри нашей темы изменить дефолтную верстку компонента. Это нетрудно сделать: у каждого компонента есть один или несколько шаблонов, которые нужно просто скопировать в директорию <тема>/partials/<имя компонента>/
и переопределять разметку там. Скопированные файлы подхватятся автоматически.
Теперь посмотрим, как добавить компонент в плагин. Для этого тоже есть удобная команда artisan:
php artisan create:component Author.MyPlugin MyComponent
Команда создаст в директории components
файл MyComponents.php
с классом компонента, а также файл с шаблоном default.htm
в директории components/mycomponent
. Файл шаблона содержит результирующую разметку, которая будет вставлена на страницу вместо тега {% component 'mycomponent' %}
:
{% set model = __SELF__.model %}
<h1>{{ model.title }}</h1>
{% if model.description %}
<div class="description row">
<div class="col-xs-12">
{{ model.description|raw }}
</div>
</div>
{% endif %}
Разумеется, в файле разметки можно обращаться к переменным и функциям, заданным в классе MyComponent
. То есть класс компонента выступает контроллером для выводимых нами данных, а шаблон компонента — это представление.
Посмотрим на класс компонента:
<?php namespace Author\MyPlugin\Components;
use Cms\Classes\Page;
use Cms\Classes\ComponentBase;
use Author\MyPlugn\Models\MyModel;
class MyComponent extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'My Model',
'description' => 'Component to output MyModel'
];
}
public function defineProperties()
{
return [
'id' => [
'title' => 'Id',
'description' => 'URL id parameter',
'default' => '{{ :id }}',
'type' => 'string'
],
];
}
// ...
В componentDetails()
мы сообщаем имя и описание компонента, а в defineProperties()
— задаем свойства (настройки) компонента. Эти настройки появятся в интерфейсе при прикреплении компонента к странице или шаблона, их также будет видно в коде страницы или шаблона в виде текста id = {{ :id }}
, как было показано в примере выше. В данном случае у нашего компонента есть свойство id
, которое по умолчанию берет id модели для показа из URL страницы.
Благодаря свойствам мы можем использовать один и тот же компонент в разных частях сайта, лишь изменив его свойства.
Теперь посмотрим на продолжение класса компонента. Поскольку компонент выводит на страницу нашу модель MyModel, нам нужно ее загрузить.
// ...
public $model;
public function onRun() {
$id = $this->property('id');
$this->model = $this->page->album = MyModel::find($id);
}
}
Метод onRun()
вызывается всякий раз при загрузке страницы и отлично подходит для подготовки данных для показа. Мы получаем id модели из определенного нами свойства компонента, затем загружаем модель по id и сохраняем в свойство $model, чтобы модель была доступна шаблону как __SELF__.model
.
В отличие от моделей и контроллеров, чтобы компонент стал доступен, его нужно зарегистрировать в Plugin.php
:
public function registerComponents()
{
return [
'Author\MyPlugin\Components\MyModel' => 'myModel'
];
}
Указанное имя myModel
будет использоваться при вставке компонента на страницы.
Подробнее о компонентах, их свойствах и шаблонах можно почитать в документации.
Это все, что я хотел рассказать об устройстве плагинов. Разумеется, в документации можно найти еще больше подробностей. Изложенного здесь, на мой взгляд, достаточно для общего представления о том, как устроен плагин в OctoberCMS.
Спасибо за внимание.