Основы создания плагинов

Содержание...

Плагины — это способ расширить функциональность приложения без изменения его исходного кода. Функционал, реализованный плагином, продолжает работать при обновлении приложения (при перезаписи всех исходных файлов приложения), потому что реализуется в независимых файлах и лишь подключается приложением установленным образом. Например, плагин «Теги» для приложения «Блог», реализующий фильтрацию записей по тегам в бекенде и во фронтенде, тесно интегрирован с пользовательским интерфейсом приложения «Блог», однако, реализован вне основных скриптов и шаблонов приложения «Блог». Это дает возможность обновлять и развивать плагины и приложения независимо друг от друга.

Фреймворк Вебасист реализует платформу для разработки плагинов для приложений, которая подразумевает гибкость изменения функционала приложений, стандартизированный способ подключения плагинов, установку обновлений через «Инсталлер» и т.п. Эта платформа описана далее в этой статье и является рекомендованной для внедрения в приложения. Предложенная платформа не мешает разработчику приложения реализовать свою собственную методику работы с плагинами, однако, разрабатывая свою систему, следует понимать, что она не будет интегрирована ни со стандартным функционалом фреймворка, ни с приложением «Инсталлер».

Поддержка плагинов в приложении находится полностью на усмотрении разработчика. Чтобы функционал приложения можно было расширять плагинами, необходимо соответствующим образом проектировать приложение и заранее предусмотреть места, в которых другие разработчики смогут расширять функциональность приложения. Некоторые приложения предусматривают расширение своего функционала плагинами, например, приложение «Блог», а некоторые могут не предусматривать этого — например, простые приложения «Гостевая книга», «Списки дел».

Плагин разрабатывается для одного конкретного приложения (не для нескольких приложений и не для всего фреймворка в целом). Разработчик приложения предусматривает в коде приложения места, в которых может быть добавлена дополнительная функциональность. Такие места условно называются хуками (hooks) и подразумевают вызов некоторых событий. Так в приложении «Блог» есть, например, хуки в левом навигационном меню бекенда (что позволяет плагинам добавлять туда свои ссылки или целые HTML-блоки) и хуки на события сохранения записи (что позволяет дополнить функционал на событие публикации записей, например, экспортом записи в Фейсбук).

Терминологически хук и событие различаются примерно так же, как различаются синтаксис и семантика. Хук — это место в коде, а событие — это способ интеграции с функционалом.

В предусмотренном месте приложения разработчик «определяет хук», добавляя следующий код:

wa()->event('[НАЗВАНИЕ_СОБЫТИЯ]', $params);

$params - параметры, которые передаются по ссылке (!)

Далее «опрашиваются» все плагины, у которых в описании указан обработчик указанного события, и все найденные плагины последовательно вызываются. Метод wa()->event($event_name, $params); возвращает массив вида:

array(
      '[PLUGIN_ID]-plugin' => результат, который возвращает обработчик плагина
);

Плагины очень похожи на приложения. Структура плагина идентична структуре приложения, плагин хранится в plugins/ внутри директории приложения:

wa-apps/[APP_ID]/plugins/[PLUGIN]/

Замочком помечены подкаталоги, которые рекомендуется закрыть с помощью директивы Deny from all в файле .htaccess.

Простейший плагин может состоять всего из двух файлов:

lib/config/plugin.php — описание плагина:

<?php

return array(
    // обязательные параметры
    'name' => 'НАЗВАНИЕ ПЛАГИНА',
    'description' => 'ОПИСАНИЕ ПЛАГИНА',
    'version' => '1.0',
    // соответствие событие => обработчик (название метода в классе плагина),
    // если плагину нужно обрабатывать какие-то события
    'handlers' => array(
      'НАЗВАНИЕ_СОБЫТИЯ' => 'method1',
      //...
    ),
    // остальные параметры — необязательные
    'img' => 'img/plugin.png', // иконка (будет показываться в Инсталлере) размером 16x16    
);

lib/[app_id][PLUGIN].plugin.php — класс с описанием обработчиков (должен быть унаследован от базового класса плагина приложения, например shopPlugin, если же такого класса в приложении нет, то тогда от класса waPlugin):

<?php

class [app_id][PLUGIN]Plugin extends waPlugin // например, blogAkismetPlugin extends blogPlugin
{

	public function method1($params) 
	{
		// реализация обработчика
  
  		// если нужен шаблон
		$view = wa()->getView();
		$content = $view->fetch($this->path.'/templates/[Название шаблона].html')
	}
	// … остальные обработчики

}

Контроллеры/экшены

Функциональная часть плагинов реализуется в экшенах — в точности, как в приложениях. Если, например, плагин добавляет какой-то HTML в интерфейс приложения или необходимо сохранить что-либо по AJAX, то следует создать контроллеры и экшены аналогично тому, как это делается в приложении. Маршрутизация ничем не отличается от маршрутизации в приложении. Для бекенда нужно только в URL добавлять параметр plugin=PLUGIN. Для фронтенда необходимо создать файл routing.php, аналогично тому, как это делается для приложения.

Вызов URL (в бекенде) ?plugin=[PLUGIN]&action=edit передаст управление экшену lib/actions/[app_id][PLUGIN]PluginBackendEdit.action.php:

<?php

class [app_id][PLUGIN]PluginBackendEditAction extends waViewAction 
{

	public function execute()
	{
		// реализация экшена
	}

}

И по умолчанию будет использоваться шаблон: templates/actions/backend/BackendEdit.html

При реализации экшенов и контроллеров, обрабатывающих загрузку данных на сервер методом POST, учитывайте необходимость проверки данных для защиты от CSRF-атак, если такая защита включена для приложения, для которого разрабатывается плагин. Несоблюдение этого требования приведёт к неработоспособности функции загрузки данных. Подробнее о защите от CSRF-атак »

Настройки плагина

По умолчанию у плагина нет никаких настроек. Чтобы добавить настройки нужно описать их в файле lib/config/settings.php. В этом случае на странице плагина в списке плагинов в бекенде приложения будет показана форма настроек.

<?php 
return array(
    'api_key'  => array(
        'title'        => /*_wp*/('Akismet API Key'),
        'description'  => array(
            /*_wp*/('Get an API key for your domain at Akismet website'),
            'https://akismet.com/signup/'
        ),
        'value'        => '', // значение по умолчанию
        'control_type'=> waHtmlControl::INPUT,
    ),
);

Иногда простых настроек недостаточно и необходимо, например, добавить дополнительный JavaScript или собственные нестандартные элементы настроек.
В этом случае нужно в файле lib/config/plugin.php указать:

'custom_settings' => true,

Далее нужно создать экшен для вашей кастомной страницы настроек в файле lib/actions/[app_id][PLUGIN]PluginSettings.action.php:

<?php

class [app_id][PLUGIN]PluginSettingsAction extends waViewAction
{
    public function execute()
    {
        $plugin = wa('[app_id]')->getPlugin('[PLUGIN]');
        // получаем все настройки плагина, чтобы передать их в шаблон
        $settings = $plugin->getSettings(); 
        $this->view->assign('settings', $settings);
    }
}
Далее нужно создать шаблон настроек в файле templates/actions/settings/Settings.html:
<h1>MY PLUGIN NAME</h1>
<div class="fields form">
    <form action="?module=plugins&id=myplugin&action=save" method="post" id="plugins-settings-form">
    {$wa->csrf()}
    <div class="field">
        <div class="name">
            [`My setting`]
        </div>
        <div class="value">
            <input type="text" name="myapp_myplugin[my_setting]" value="{if isset($settings.my_setting)}{$settings.my_setting}{/if}">
        </div>
    </div>
    ...
    <div class="field">
        <div class="value submit">
            <input type="submit" class="button green" value="[s`Save`]">
            <span id="plugins-settings-form-status" style="display:none"></span>
        </div>
    </div>
    </form>
</div>   

Обратите внимание, что name для всех полей должен быть вида [app_id]_[PLUGIN] и дальше ключ с кодом настройки [SETTING], например shop_brands[feature_id].
Никакого javascript для отправки формы, а так же собственого контроллера для сохранения настроек делать нет необходимости.

Если вам нужна какая-то своя логика при сохранении настроек, вы можете переопределить в основном классе плагина метод saveSettings:

public function saveSettings($settings = array()) {
    // тут ваша логика
    ...
    parent::saveSettings($settings);
}

Действия по расписанию

Фреймворк Вебасист содержит инфраструктуру для создания cli-скриптов (от английского command-line interface, «интерфейс командной строки»), которые могут исполняться из консоли сервера и, следовательно, могут быть включены в задания планировщика (cron). Плагины могут иметь собственные cli-скрипты отдельно от cli-скриптов приложения. Такие скрипты в плагинах оформляются в виде классов, унаследованных от базового класса waCliController. Логика выполнения скрипта должна содержаться в методе класса execute(). PHP-файл класса следует находиться по адресу wa-apps/[app_id]/plugins/[plugin_id]/lib/cli/[app_id][Plugin_id]Classname.cli.php, а имя класса должно быть сформировано по правилу [app_id][Plugin_id]ClassnameCli.

Например, PHP-файл некоторого cli-скрипта с именем update для плагина brands приложения shop может располагаться по адресу wa-apps/shop/plugins/brands/lib/cli/shopBrandsUpdate.cli.php и иметь следующий вид:

<?php 

class shopBrandsUpdateCli extends waCliController
{

    public function execute()
    {
        // Код, который должен запуститься при вызове скрипта
        // Можно использовать модели и любые другие классы приложения
    }

}

Для вызова такого скрипта нужно использовать команду следующего вида:

php cli.php shop brandsUpdate

Более подробно о создании cli-скриптов читайте в статье «Действия по расписанию».

Локализация

Локализация плагинов реализуется полностью аналогично локализации приложений (документация). В папке locale следует разместить файлы переводов *.po и *.mo и подключать ключи в коде следующим образом:

Название и описание плагина (name и description в конфигурационном файле) переводятся с использованием локализации плагина по умолчанию, таким образом указывать 'name' => _wp('НАЗВАНИЕ ПЛАГИНА') не надо — достаточно просто указать 'name'=>'НАЗВАНИЕ ПЛАГИНА'.

Использование локализации в статических методах

В случае вызова публичных статических методов классов плагина во внешнем окружении, например, в коде темы дизайна, локализация плагина автоматически не подключается, и функция _wp() не возвращает перевод строки, как ожидалось. Для того чтобы использовать локализацию плагина в таких методах, необходимо помещать все вызовы функции _wp() внутри специальной конструкции, выделенной жирным шрифтом в показанном ниже примере:

class appMyPlugin extends waPlugin
{
    public static function displayData()
    {
        //в обеих строках укажите ID приложения и своего плагина
        waLocale::loadByDomain(array('app_id', 'plugin_id'));
        waSystem::pushActivePlugin('plugin_id', 'app_id');
        
        $result = _wp('...');
        
        waSystem::popActivePlugin();
        
        return $result;
    }
}

Подробнее о локализации плагинов читайте в основной статье о локализации.

База данных

Если плагин использует собственные таблицы в базе данных, то имена таблиц должны начинаться с фрагмента вида [app_id]_[plugin]_, например: shop_ebay_tablename.

Подключение плагина

Для того чтобы написанный плагин заработал, необходимо подключить его в системном конфигурационном файле приложения wa-config/apps/APP_ID/plugins.php, добавив в него строку:

'plugin_id' => true

Пример этого файла для приложения «Блог» (wa-config/apps/blog/plugins.php):

<?php

return array(
    'akismet' => true,
    'tag' => true,
    'category' => true,
    'gravatar' => true,
    'favorite' => true,
    'troll' => true,
);

В этом же файле можно отключить ненужные плагины.

install.php и uninstall.php

Если при установке плагина требуется выполнить какие-то нестандартные действия (например, добавить новые поля в существующие таблицы приложения), то логику таких действий необходимо описать в конфигурационном файле lib/config/install.php. Пример добавления дополнительного поля в таблицу при установке плагина:

$model = new waModel();
try {
    $model->query('SELECT `custom_field` FROM `shop_product` WHERE 0');
} catch (waDbException $e) {
    $model->exec('ALTER TABLE `shop_product` ADD `custom_field` INT(11) UNSIGNED NULL DEFAULT NULL');
}

Действия, которые нужно выполнить при удалении плагина, аналогичным образом описывайте в файле lib/config/uninstall.php.

Создание и удаление собственных таблиц плагина в файлах install.php и uninstall.php описывать не нужно — таблицы автоматически создаются и удаляются на основании содержимого другого конфигурационного файла: db.php. См. «Консольная команда для формирования файла db.php».

Пример

Плагинная платформа была внедрена во фреймворк вместе с приложением «Блог», поэтому для дальнейшего изучения рекомендуем установить это приложение и рассмотреть плагины, написанные для него (плагины устанавливаются через «Инсталлер»). Для «Блога» написаны плагины различной направленности и практического применения: для фронтенда, бекенда, расширяющие возможности пользовательского интерфейса, выгрузки данных и пр.

См. списки событий, доступных в различных приложениях →