Общие сведения о модулях
Модули позволяют расширять функционал ReadyScript и вмешиваться в стандартный ход выполнения различных операций. Каждый модуль включает в себя классы контроллеров, моделей, шаблоны отображения, изображения, CSS стили, JS файлы, языковые файлы. Такая инкапсуляция позволяет легко переносить модуль между приложениями на платформе ReadyScript. Модуль в ReadyScript - это папка с определенной структурой данных внутри. Модуль обязательно должен располагаться в каталоге для модулей /modules и иметь следующую структуру.
- modulename
- config
- file.inc.php
- module.xml
- handlers.inc.php
- install.inc.php
- uninstall.inc.php
- patches.inc.php
- controller
- model
- orm
- commenttype
- csvschema
- notice
- ...
- view
- hooks
- lang
- en
- messages.lng.php
- messages.js.php
- ...
Конфигурационные классы модуля
Класс настроек ModuleName\Config\File
Для того, чтобы модуль был идентифицирован в системе и его можно было установить, необходимо объявить в нем класс ModuleName\Config\File. В данном классе должны быть описаны настройки модуля. Значения по-умолчанию этих настроек должны быть описаны в файле module.xml. В дальнейшем, пользователь сможет менять значения настроек модуля в административной панели в разделе Веб-сайт → Настройка модулей
Конфигурационный класс модуля ModuleName::Config::File - должен быть наследником класса RS::Orm::ConfigObject . RS::Orm::ConfigObject - это ORM объект с особым типом хранища, поэтому добавление свойств(настроек модуля) происходит по общим правилам создания свойств у ORM Объектов, через метод _init. Значения настроек по умолчанию должны возвращаться статическим методом getDefaultValues() в виде массива, в котором ключами являются имена свойств. RS::Orm::ConfigObject имеет следующий набор стандартных свойств, описывающих модуль:
Свойство | Назначение | Ожидаемое значение |
name | Название модуля | String |
description | Описание | String |
is_system | Флаг, является ли модуль системным. Системные модули невозможно удалить | 1 - модуль системный, 0 - модуль обычный |
dependfrom | Здесь можно указать другие модули, которые должны присутствовать в системе, чтобы установить данный модуль | String. названия модулей должны быть перечислены через запятую |
version | Версия модуля | String. Пример версии: 0.1.0.0 - первая цифра - изменения, влияющие на совместимость, вторая - изменение в стуктуре БД, третья - мелкие правки, четвертая - ревизия из системы контроля версий |
version_date | Дата последнего обновления модуля | String |
core_version | Требуемая для установки версия ядра | String. Например: '0.1.0.0' (одна версия), или '0.1.0.0 - 0.2.0.0' (Диапазон версий), или '>=0.1.0.156' или '<=0.1.0.200' (для всех версий младше или старше требуемой), можно указать смешанно, через запятую так: '<=0.1.0.200, 0.2.0.0 - 0.3.0.0, 1.0.0.0, 1.1.0.0' |
author | Автор модуля | String |
tools | Утилиты по обслуживанию модуля. Отображаются на странице настроек модуля в правой колонке. | Array. Например: array(
array(
'url' => '/любая ссылка/',
'title' => t('Название инструмента'),
'description' => t('Описание инструмента')
),
...
) |
В ReadyScript имеется подсистема настройки прав доступа к модулям. Права к модулю - это число от 0 до 255. Каждому из 8-ми битов этого числа может быть присвоено название. Администратор может устанавливать флажок для каждого бита в административной части в разделе настройки прав групп пользователей. Назначение каждого бита при этом будет отображено администратору. Чтобы задать описание битам достаточно перегрузить публичное свойство $access_bit в классе конфигурации модуля. В процессе работы модуля можно проверять состояние любого из 8-ми битов перед выполнением того или иного действия.
Пример конфигурационного файла модуля Каталог
<?php
namespace Catalog\Config;
use \RS\Orm\Type;
{
{
parent::_init()->append(array(
'default_cost' => new Type\Integer(array(
'description' => t('Цена по умолчанию'),
'list' => array(array('\Catalog\Model\Costapi', 'staticSelectList'))
)),
'hide_unobtainable_goods' => new Type\Varchar(array(
'description' => t('Скрывать товары с нулевым остатком'),
'listfromarray' => array(array('Y' => 'Да', 'N' => 'Нет')),
'attr' => array(array('size' => 1))
)),
'list_page_size' => new Type\Integer(array(
'description' => t('Количество товаров на одной странице списка')
)),
'items_on_page' => new Type\Varchar(array(
'description' => t('Количество товаров на странице категории. Укажите через запятую, если нужно предоставить выбор'),
'hint' => t('Например: 12,48,96')
)),
'default_weight' => new Type\Integer(array(
'description' => t('Вес одного товара по-умолчанию, грамм'),
'hint' => t('Данное значение можно переустановить в настройках категории или у самого товара')
)),
'default_unit' => new Type\Integer(array(
'description' => t('Единица измерения по-умолчанию'),
'List' => array(array('\Catalog\Model\UnitApi', 'selectList'))
)),
'concat_dir_meta' => new Type\Integer(array(
'description' => t('Дописывать мета теги категорий к товару'),
'hint' => t('Данная опция имеет значение, когда мета данные заданы у товара.'),
'checkboxView' => array(1,0)
)),
'csv_id_fields' => new Type\ArrayList(array(
'runtime' => false,
'description' => t('Поля для идентификации товара при импорте (удерживая CTRL можно выбрать несколько полей)'),
'hint' => t('Во время импорта данных из CSV файла, система сперва будет обновлять товары, у которых будет совпадение значений по указанным здесь колонкам. В противном случае будет создаваться новый товар'),
'list' => array(array('\Catalog\Model\CsvSchema\Product','getPossibleIdFields')),
'size' => 7,
'attr' => array(array('multiple' => true))
)),
));
}
{
parent::getDefaultValues() + array(
'tools' => array(
array(
'url' => \RS\Router\Manager::obj()->getAdminUrl('ajaxCleanProperty', array(), 'catalog-tools'),
'title' => t('Удалить несвязанные характеристики'),
'description' => t('Удаляет характеристики и группы, которые не задействованы в товарах или категориях'),
'confirm' => t('Вы действительно хотите удалить несвязанные характеристики?')
),
array(
'url' => \RS\Router\Manager::obj()->getAdminUrl('ajaxCheckAliases', array(), 'catalog-tools'),
'title' => t('Добавить ЧПУ имена товарам и категориям'),
'description' => t('Добавит символьный идентификатор (методом транслитерации) товарам и категориям, у которых он отсутствует.'),
'confirm' => t('Вы действительно хотите добавить ЧПУ имена товарам и категориям?')
)
)
);
}
}
Файл с параметрами модуля module.xml
Файл должен содержать основные сведения о модуле и значения параметров конфигурации по-умолчанию.
Обязательные секции:
- name - название модуля
- description - описание модуля
- version - версия модуля
- author - автор модуля
Если у секции присутствует атрибут multilanguage="true", то это означает, что значение секции подлежит переводу на текущий язык пользователя. Задать различные языковые версии можно с помощью языковых файлов модуля.
Присутствие у секции атрибута type="array", будет означать, что параметр содержит списковое значение. Элементы списка допускается задавать вложенной секцией value. У секции value может быть задан необязательный атрибут key, который будет определять ключ ассоциативного массива параметра.
Пример файла module.xml:
<?xml version="1.0" encoding="utf-8"?>
<config>
<defaultValues>
<name multilanguage="true">Каталог товаров</name>
<description multilanguage="true">Администрирование каталога товаров по рубрикам</description>
<version>2.0.0.123</version>
<author>ReadyScript lab.</author>
<is_system>1</is_system>
<dependfrom>photo</dependfrom>
<default_cost>0</default_cost>
<hide_unobtainable_goods>N</hide_unobtainable_goods>
<list_page_size>12</list_page_size>
<items_on_page>12,48,96</items_on_page>
<default_weight>0</default_weight>
<default_unit>0</default_unit>
<concat_dir_meta>1</concat_dir_meta>
<csv_id_fields type="array">
<value>title</value>
<value>barcode</value>
</csv_id_fields>
</defaultValues>
</config>
Класс установки ModuleName\Config\Install
Чтобы реализовать собственный или дополнить стандартный алгоритм установки модуля, необходимо добавить ему класс-установщик Install в пространстве имен ModuleName\Config. Класс должен имплементировать интерфейс RS::Module::InstallInterface .
Рассмотрим данный интерфейс
<?php
namespace RS\Module;
interface InstallInterface
{
public function installAdminMenu();
}
Для увеличения скорости разработки, в ReadyScript имеется абстрактный класс RS::Module::AbstractInstall, в котором реализовано стандартное поведение установщика модуля. Стандартное поведение заключается в следующем:
- Метод install выполняет метод update.
- В методе update происходит поиск всех ORM объектов, расположенных в папке model/orm (включая вложенные папки). У каждого ORM объекта вызывается метод RS::Orm::AbstractObject::dbUpdate(), который приводит в соответствие структуру БД для данного объекта.
- метод installAdminMenu - не добавляет пунктов меню в административную панель.
- метод canInsertDemoData - возвращает false, что означает, что у модуля нет демонстрационных данных.
- остальные методы содержат пустое тело.
Если данное поведение полностью соответствует требованиям модуля, достаточно определить наследника класса RS::Module::AbstractInstall, с пустым телом.
Пример стандартного установщика модуля:
<?php
namespace ModuleName\Config;
{}
Класс удаления ModuleName\Config\Uninstall
Чтобы реализовать собственный или дополнить стандартный алгоритм удаления модуля, необходимо реализовать класс деинсталяции. Класс должен имплементировать интерфейс RS::Module::UninstallInterface. Рассмотрим данный интерфейс:
<?php
namespace RS\Module;
interface UninstallInterface
{
}
Аналогично функционалу установки модуля, для разработчиков есть готовый абстрактный класс RS::Module::AbstractUninstall, реализующий стандартное поведение модуля при удалении. Под стандартным поведением понимается:
После выполнения метода uninstall класса деинсталяции модуля, ReadyScript удаляет папку с модулем. Для создания стандартного класса деинсталяции будет достаточно определить класс UnInstall, как наследника класса RS::Module::AbstractUninstall с пустым телом.
<?php
namespace ModuleName\Config;
{
}
Класс единоразовых корректировок ModuleName\Config\Patches
Для случев, когда при обновлении модуля единожды требуется выполнить какой-либо PHP-код разработчика, в ReadyScript предусмотрен класс Patches. Он обязательно должен являться наследником класса RS::Module::AbstractPatches. В классе должен быть реализован метод init(), возвращающий массив идентификаторов патчей. Для каждого идентификатора должен быть реализован метод с одним из следующих префиксов:
- beforeUpdate{PATCH_ID}
- afterUpdate{PATCH_ID}
Методы с префиксом beforeUpdate будут выполнены до начала процедуры обновления модуля, методы с префиксом afterUpdate соответственно после. Метод из класса Patches выполняется только если они не были запущены раннее, таким образом все методы выполняются единожды.
Пример класса Patches.
<?php
namespace Search\Config;
Класс обработки событий ModuleName\Config\Handlers
Во время выполнения различных операций, система вызывает события. Каждый установленный и включенный модуль в системе может обрабатывать эти события и выполнять какие-либо действия. Некоторые события чувствительны к результату выполнения обработчиков, что позволяет корректировать с помощью сторонних модулей стандартный ход выполнения программы.
Подписка на события системы и непосредственно сами обработчики событий должны располагаться в классе Handlers, в пространстве имен ModuleName. Класс Handlers должен быть наследником класса RS::Event::HandlerAbstract.
<?php
namespace RS\Event;
abstract class HandlerAbstract
{
abstract public function init();
function bind($event, $callback_class = null, $callback_method = null, $priority = 10)
{
if ($callback_class === null) {
$callback_class = $this;
}
return $this;
}
}
В момент инициализации системы событий, скрипт проверяет наличие класса Handlers в каждом модуле и вызывает метод init. Предполагается, что в данном методе произойдет установка обработчиков на события, которые модуль пожелает обрабатывать.
Рассмотрим класс Handlers модуля Shop
<?php
namespace Shop\Config;
use \RS\Router;
{
{
$this
->bind('getroute')
->bind('user.auth')
->bind('delivery.gettypes')
->bind('payment.gettypes')
->bind('printform.getlist')
->bind('orm.init.catalog-product')
->bind('orm.init.catalog-dir');
if (\Setup::$INSTALLED) {
$this->
bind(
'orm.afterwrite.site-site', $this,
'onSiteCreate');
}
}
public static function getRoute(array $routes)
{
$routes[] = new Router\Route('shop-front-cartpage', '/cart/', null, t('Корзина'));
$routes[] = new Router\Route('shop-front-checkout', array('/checkout/{Act}/', '/checkout/'), null, t('Оформление заказа'));
return $routes;
}
public static function userAuth($params)
{
}
{
$list[] = new \Shop\Model\DeliveryType\FixedPay();
return $list;
}
{
$list[] = new \Shop\Model\PaymentType\Cash();
return $list;
}
public static function onSiteCreate($params)
{
}
{
}
{
}
{
}
}
Из приведенного выше примера класса видно, что если у метода bind не указаны параметры $callback_class, $callback_method, то ожидается, что в классе будет присутствовать метод, имя которого соответствует имени события без символов точек и минусов. Метод должен быть публичным и статическим. Такой метод и будет обрабатывать соответствующее событие.
Идея расположить все обработчики событий в одном файле приследует цель, создать единое место, в котором будет видна вся "внешняя" деятельность модуля.
Список всех событий см. в разделе События
Класс прав модуля ModuleName\Config\ModuleRights
По умолчанию для определения доступных в модуле прав и их автоматических проверок используется класс RS::AccessControl::DefaultModuleRights . Чтобы реализовать собственную или дополнить стандартную схему прав, необходимо создать класс прав модуля ModuleRights в пространстве имен ModuleName\Config. Класс должен быть наследником класса RS::AccessControl::DefaultModuleRights .
В классе прав модуля можно перегрузить 2 метода:
- getSelfModuleRights - возвращает древовидный список собственных прав модуля
protected function getSelfModuleRights()
{
return array(
new \RS\AccessControl\Right('right_id_1', t('Наименование права 1')),
...
new \RS\AccessControl\RightGroup('group_id', t('Наименование группы'), array(
new \RS\AccessControl\Right('right_id_2', t('Наименование права 2')),
...
)),
...
);
}
- Заметки
- Рекомендовано указывать идентификаторы прав при помощи констант, для удобства последующих проверок
- getSelfAutoCheckers - возвращает список собственных инструкций для автоматических проверок прав
protected function getSelfAutoCheckers()
{
return array(
new \RS\AccessControl\AutoCheckers\ControllerChecker($controller_mask, $method, $action, $request_params, $right, $ignore_missing_rights),
...
);
}
Классы автоматических проверок прав
В некоторых случаях вместо ручной установки проверки прав в код модуля можно использовать инструкции для автоматических проверок прав
- \RS\AccessControl\AutoCheckers\ControllerChecker - срабатывает при запуске любого контроллера, проверяет права только в случае соответствия всех указаных параметров параметрам запроса
- Аргументы конструктора:
- string $controller_mask - регулярное выражение описывающее имя контроллера, значение '' применяет проверку ко всем контроллерам
- string|string[] $method - список методов HTTP, значение '*' применяет проверку для любого метода
- string|string[] $action - список действий контроллера, значение '*' применяет проверку для любого действия
- array $request_params - параметры запроса переданные в GET, POST, COOKIES
- string $right - идентификатор проверяемого права
- bool $ignore_missing_rights - не считать ошибкой отсутствие в модуле проверяемого права (прав из )
- Примеры использования:
new \RS\AccessControl\AutoCheckers\ControllerChecker('', '*', '*', array(), 'read'),
new \RS\AccessControl\AutoCheckers\ControllerChecker('module-admin-entityctrl', '*', '*', array(), 'entity_read'),
new \RS\AccessControl\AutoCheckers\ControllerChecker('module-admin-entityctrl', 'post', 'some_action', array(), 'entity_some_action'),
new \RS\AccessControl\AutoCheckers\ControllerChecker('module-admin-entityctrl', array('post', 'get), '*', array(
GET => array(
recalc_something = 1
),
), 'entity_recalc_something'),
Пример - "файл прав модуля по умолчанию":
<?php
namespace RS\AccessControl;
class DefaultModuleRights extends AbstractModuleRights
{
const
{
return array(
new Right(self::RIGHT_READ, t('Чтение')),
new Right(self::RIGHT_CREATE, t('Создание')),
new Right(self::RIGHT_UPDATE, t('Изменение')),
new Right(self::RIGHT_DELETE, t('Удаление')),
);
}
{
return array(
new ControllerChecker('', '*', '*', array(), self::RIGHT_READ, true),
);
}
}
Расширение схемы прав
Список прав модуля и список интрукций для автоматических проверок можно дополнить из стороннего модуля при помощи событий