Модулем в UMI.CMS называется структурированная и оформленная определенными образом совокупность типов данных и механизмов и правил работы с данными.
Файловая структура
Файлы реализации модуля
Исходные файлы всех модулей хранятся в папке ~/classes/сomponents/имя_модуля/.
Типовой список файлов реализации модуля:
- admin.php - класс административного функционала;
- autoload.php - файл, в котором перечислены классы для добавления в автозагрузку;
- class.php - базовый класс модуля с правилами импементации классов модуля и непереопределяемыми методами;
- customAdmin.php - файл для кастомизации административного функционала;
- customAutoload.php - файл, в котором перечислены пользовательские классы для добавления в автозагрузку;
- customMacros.php - файл для кастомизации клиентского функционала;
- events.php - привязка событий к обработчикам;
- handlers.php - обработчики событий;
- i18n.php - файл с языковыми константами (как правило для административного режима);
- includes.php - файл для подключения дополнительных классов, которые не имплементируются в модуль;
- install.php - установщик модуля;
- lang.php - файл с языковыми константами (как правило для клиентского режима);
- macros.php - класс клиентского функционал;
- permissions.php - файл с определением групп прав доступа к функционалу;
*Дополнительные классы, если они имплементируются в модуль, как правило лежат с директорией модуля, а внешние классы - в директории /classes/.
Файлы шаблонов модуля
Шаблоны всех модулей хранятся в папке ~/styles/skins/имя_скина/data/modules/имя_модуля/.
Типовой список файлов шаблонов модуля:
- form.modify.xsl - шаблон формы редактирования или создания сущности модуля;
- list.modify.xsl - шаблон формы редактирования списка сущностей модуля;
- list.view.xsl - шаблон формы вывода списка сущностей модуля;
- settings.view.xsl - шаблон вывода настроек модуля;
- settings.modify.xsl - шаблон вывода формы редактирования настроек модуля;
*Имя файла шаблона соответствует настройкам методов административной панели, см. описание файла admin.php.
Описание файлов реализации модуля
class.php
Файл содержит базовый класс модуля. Класс должен наследоваться от класса def_module, а его имя начинаться с маленькой буквы, пример:
class filemanager extends def_module {
/**
* @var int количество выводимых элементов на страницу в рамках пагинации
*/
public $per_page = 25;
};
Главное назначение класса - содержать схему имлементации других классов, которые составляют функциональность модуля.
В системных модулях эта логика содержится в конструкторе:
/**
* Конструктор
*/
public function __construct() {
//Вызываем родительские конструктор, в нем подключается файл includes.php
parent::__construct();
$cmsController = cmsController::getInstance();
//Административный режим
if ($cmsController->getCurrentMode() == "admin") {
//Подключаем административный функционал
$this->__loadLib("admin.php");
$this->__implement("FilemanagerAdmin");
//Подключаем расширения административной функциональности с переопределением
$this->loadAdminExtension();
//Подключаем файл для кастомизации административного функционала с переопределением
$this->__loadLib("customAdmin.php");
$this->__implement("FileManagerCustomAdmin", true);
}
//Подключаем клиентский функционал
$this->__loadLib("macros.php");
$this->__implement("FileManagerMacros");
//Подключаем расширения клиентской функциональности с переопределением
$this->loadSiteExtension();
//Подключаем файл для кастомизации клиентского функционала с переопределением
$this->__loadLib("customMacros.php");
$this->__implement("FileManagerCustomMacros", true);
//Подключаем расширения общей функциональности с переопределением
$this->loadCommonExtension();
//Подключаем кастомные файлы из ресурсов шаблона с переопределением
$this->loadTemplateCustoms();
}
В этот класс так же можно добавлять методы, которые необходимо использовать в подключаемых классах и нельзя переопределять.
Как работает подключение классов в базовый класс модуля
Каждый базовый класс хранит в себе пути подключенных файлов, классов и методов, к которым он имеет доступ:
/**
* @var array $libs загруженные файлы классов
*/
private $libs = [];
/**
* @var array $classes подключенные классы
*/
private $classes = [];
/**
* @var array $methods подключенные методы
*/
private $methods = [];
Подключение классов к модулю и работа с их методами от имени класс модуля реализована с помощью 3 методов:
- def_module::__loadLib() - подключает файл с классом;
- def_module::__implement() - подключает класс;
- def_module::__call() - переадресует обращение к подключенному классу;
Если с первым методом все понятно - он просто подключает php файл, то на других мы остановимся подробнее:
/**
* Подключает класс к модулю
* @param string $className имя подключаемого класса
* @param bool $allowOverriding поддерживается ли перегрузка методов
* @return bool
*/
protected function __implement($className, $allowOverriding = false) {
try {
//Получаем отражение класса
$classReflection = new ReflectionClass($className);
//Если нельзя получить экземпляр класса - то класс нам не подходит
if (!$classReflection->isInstantiable()) {
return false;
}
//Получаем публичные методы
$methods = $classReflection->getMethods(ReflectionMethod::IS_PUBLIC);
if (count($methods) == 0) {
return false;
}
//Создаем экземпляр подключаемого класса, передаем ему в конструктор экземпляр базового класса
$classInstance = new $className($this);
//Устанавливаем экземпляр базового класса в свойство module
$classInstance->module = $this;
//Записывает экземпляр класса по имени класса
$this->classes[$className] = $classInstance;
//Обходим методы класса
foreach ($methods as $method) {
//Если метод с заданными именем уже есть в базовом классе и отключено переопределние - пропускаем его
if (isset($this->methods[$method->getName()]) && !$allowOverriding) {
continue;
}
//Иначе записываем имя метода и экземпляр или имя класса
if ($method->isStatic()) {
$this->methods[$method->getName()] = $className;
} else {
$this->methods[$method->getName()] = $this->classes[$className];
}
}
} catch (ReflectionException $e) {
return false;
}
return true;
}
Исходя из этого мы знаем, что подключать можно только те классы, экземпляр которых можно получить через new, и подключены будут только публичные методы.
После всех подключений базовый класс будет содержать схему машрутов, то есть за каким методом к какому подключенному классу ему нужно обратиться.
Само обращение к подключенным классам реализовано с помощью магического метода __call():
/**
* Магический метод, пытается найти переданный метод
* среди методов подключенных классов,
* если метод найден - вызывает его
* @param string $method имя метода
* @param array $args аргументы вызова
* @return mixed|string
*/
public function __call($method, $args) {
$args = (!is_array($args)) ? [] : $args;
//Если заданный метод был подключен
if (isset($this->methods[$method])) {
//$class - экземпляр класса или его имя, если метод статический
$class = $this->methods[$method];
//Делаем его вызов
return call_user_func_array([$class, $method], $args);
}
//Если заданный метод не известен, то пытаемся вернуть ошибку
}
*То есть, чтобы вызывался метод из подключенного класса, в базовом классе не должно быть одноименных методов.
Для работы с подключенными классами есть три вспомогательных метода:
/**
* Возвращает экземпляр подключенного класса
* @param string $class имя класса
* @return object
* @throws coreException
*/
public function getImplementedInstance($class) {
if (!is_string($class) || !$this->isClassImplemented($class)) {
throw new coreException('Class ' . $class . ' not implemented');
}
return $this->classes[$class];
}
/**
* Возвращает экземпляр основного класса административной панели
* @return object
* @throws coreException
*/
public function getAdminInstance() {
return $this->getImplementedInstance($this->getAdminClassName());
}
/**
* Подключен ли класс
* @param string $class имя класса
* @return bool
*/
public function isClassImplemented($class) {
return isset($this->classes[$class]);
}
admin.php
Файл содержит административный функционал модуля. Класс должен использовать трейт baseModuleAdmin, а его имя должно соответствовать схеме Имя_базового_классаAdmin, то есть должно начинаться с большой буквы. Если имя будет начинаться с маленькой буквы, система не сможет динамически подключить класс.
Файл подключается только в административном режиме работы модуля. Переопределить его методы можно через файл customAdmin.php.
В классе доступен объект базового класса модуля, через него можно получить доступ к методам других подключенных классов.
Например:
/**
* Класс функционала административной панели
*/
class NewsAdmin {
use baseModuleAdmin;
/**
* @var news $module
*/
public $module;
};
Класс содержит бек для вкладок модулей административной панели и действий над объектами.
Пример интерфейса функционала админ панели модуля "Баннеры":
/**
* Возвращает список баннеров
* @return bool
* @throws coreException
* @throws selectorException
*/
public function lists();
/**
* Возвращает список мест для показа баннеров.
* Если передан ключевой параметр $_REQUEST['param0'] = do,
* то сохраняет изменения списка мест.
* @throws coreException
* @throws selectorException
*/
public function places();
/**
* Возвращает данные для создания формы добавления баннера,
* если передан $_REQUEST['param0'] = do, то создает баннера
* и перенаправляет страницу, где его можно отредактировать.
* @throws coreException
* @throws publicAdminException
* @throws wrongElementTypeAdminException
*/
public function add();
/**
* Возвращает данные для создания формы редактирования баннера.
* Если передан ключевой параметр $_REQUEST['param1'] = do,
* то сохраняет изменения баннера и производит перенаправление.
* Адрес перенаправления зависит от режима кнопки "Сохранить".
* @throws coreException
* @throws expectObjectException
*/
public function edit();
/**
* Удаляет баннеры
* @throws coreException
* @throws expectObjectException
* @throws wrongElementTypeAdminException
*/
public function del();
/**
* Изменяет активность баннеров
* @throws coreException
* @throws expectObjectException
*/
public function activity();
/**
* Возвращает настройки модуля.
* Если передан ключевой параметр $_REQUEST['param0'] = do,
* то сохраняет настройки.
* @throws coreException
*/
public function config();
/**
* Возвращает настройки для формирования табличного контрола
* @param string $param контрольный параметр
* @return array
*/
public function getDatasetConfiguration($param = '');
Большинство методов административного функционала отличаются тем, что, фактически, они ничего не возвращают, а только устанавливают настройки и результат вывода.
Эти действия производятся с помощью следующих методов трейта baseModuleAdmin:
- baseModuleAdmin::setDataType($type) - устанавливает тип данных результата;
- baseModuleAdmin::setActionType($type) - устанавливает тип действия;
- baseModuleAdmin::setDataRange($limit, $offset) - устанавливает ограничения на количество элементов;
- baseModuleAdmin::prepareData($data, $type) - подготавливает данные к сериализации к xml;
- baseModuleAdmin::setData($data, $total) - устанавливает результат метода;
- baseModuleAdmin::doData() - устанавливает окончательный результат метода;
Обратите внимание, что эти методы можно вызывать только из класса Имя_базового_классаAdmin, иначе они не будут обработаны корректно. В зависимости от установленных типов данных и результат действия в дальнейшем подключается тот или иной xsl шаблон административной панели.
Например, для метода:
/**
* Возвращает список баннеров
* @return bool
* @throws coreException
* @throws selectorException
*/
public function lists() {
$this->setDataType("list");
$this->setActionType("view");
if ($this->module->ifNotXmlMode()) {
$this->setDirectCallError();
$this->doData();
return true;
}
$limit = getRequest('per_page_limit');
$curr_page = getRequest('p');
$offset = $limit * $curr_page;
$sel = new selector('objects');
$sel->types('hierarchy-type')->name('banners', 'banner');
$sel->limit($offset, $limit);
selectorHelper::detectFilters($sel);
$this->setDataRange($limit, $offset);
$data = $this->prepareData($sel->result, "objects");
$this->setData($data, $sel->length);
$this->doData();
}
Будет подключен шаблон: ~/styles/skins/имя_скина/data/modules/имя_модуля/list.view.xsl.
autoload.php
Файл, в котором перечислены классы для добавления в автозагрузку. В файле объявляется массив $classes, и в него добавляется пары типа 'ИмяКласса' => [массив файлов для автозагрузки при использовании этого класса]. Эти классы становятся доступными для использования только после загрузки модуля, которому они принадлежат. Дополнительные классы можно добавить в файле customAutoload.php. Начиная с 20 версии системы добавлена возможность задать правила автозагрузки классов через директорию с шаблоном (templates/ваш_шаблон/autoload.php)
Пример:
$classes = [
'simpleStat' => [
dirname(__FILE__) . '/classes/simpleStat.php'
],
'statistic' => [
dirname(__FILE__) . '/classes/statistic.php'
],
'statisticFactory' => [
dirname(__FILE__) . '/classes/statisticFactory.php'
],
'xmlDecorator' => [
dirname(__FILE__) . '/classes/xmlDecorator.php'
],
'holidayRoutineCounter' => [
dirname(__FILE__) . '/classes/holidayRoutineCounter.php'
],
'openstat' => [
dirname(__FILE__) . '/classes/openstat.php'
]
];
customAdmin.php
Класс для кастомизации административного функционала.
Данный файл не обновляется системой и в него можно добавить дополнительные методы, доступ к которым необходим в административной панели. Если добавить в класс метод с таким же именем, какой есть в классе административного функционала - первый переопределит последний.
В классе доступен объект базового класса модуля, через него можно получить доступ к методам других подключенных классов.
Пример:
/**
* Класс пользовательских методов административной панели
*/
class BannersCustomAdmin {
/**
* @var banners $module
*/
public $module;
/**
* @var BannersAdmin|void $admin базовый класс административной панели модуля
*/
public $admin;
/**
* Конструктор
* @param banners $module
*/
public function __construct(banners $module) {
$this->module = is_null($this->module) ? $module : $this->module;
if (!$this->module->isClassImplemented('BannersAdmin')) {
throw new coreException('Class BannersAdmin must be implemented');
}
// сохраняем базовый административный класс для использования методов
// трейта baseModuleAdmin
$this->admin = $this->module->getImplementedInstance('BannersAdmin');
}
/**
* Переопределяем метод BannersAdmin::lists()
*/
public function lists() {
$this->admin->setDataType("list");
$this->admin->setActionType("view");
// кастомная логика
...
$this->admin->setDataRange($limit, $offset);
$data = $this->admin->prepareData($sel->result, "objects");
$this->admin->setData($data, $sel->length);
$this->admin->doData();
}
}
customAutoload.php
Файл, в котором перечислены пользовательские классы для добавления в автозагрузку. Принцип работы тот же, что и у файла autoload.php.
Данный файл не обновляется системой.
customMacros.php
Класс для кастомизации макросов, то есть методов, которые доступны в клиентском режиме, в шаблоне сайта.
Данный файл не обновляется системой и в него можно добавить дополнительные методы, доступ
к которым необходим в шаблоне сайта. Если добавить в класс метод с таким же именем,
какой есть в подключенных классах, то он переопределит метод из подключенного класса.
В классе доступен объект базового класса модуля, через него можно получить доступ к методам других подключенных классов.
Пример:
/**
* Класс пользовательских макросов
*/
class EmarketCustomMacros {
/**
* @var emarket $module
*/
public $module;
}
events.php
Файл для привязки событий к их обработчикам, содержит объявления "слушателей" событий.
Пример такого файла:
/**
* Импортирует все фиды по срабатыванию системного крона
*/
$onCronNewsRead = new umiEventListener("cron", "news", "feedsImportListener");
/**
* Активирует новости с подходящей датой публикации по срабатыванию системного крона
*/
$onCronActivateNews = new umiEventListener("cron", "news", "cronActivateNews");
Для назначение своих обработчиков, нужно создать в директории с модулем файл с именем custom_events.php и поместить свои привязки в него.
handlers.php
Файл содержит обработчики событий.
Файл подключается в любом режиме работы модуля (клиентском и административном).
В классе доступен объект базового класса модуля, через него можно получить доступ к методам других подключенных классов.
Например:
/**
* Класс обработчиков событий
*/
class NewsHandlers {
/**
* @var news $module
*/
public $module;
/**
* Обработчик события срабатывания системного крона.
* Активирует новости с подходящей датой начала активности.
* @param iUmiEventPoint $event событие срабатывания системного крона
* @throws selectorException
*/
public function cronActivateNews(iUmiEventPoint $event) {
//realisation
}
/**
* Обработчик события срабатывания системного крона.
* Импортирует все RSS-фиды.
* @param iUmiEventPoint $event событие срабатывания системного крона
* @return boolean
*/
public function feedsImportListener(iUmiEventPoint $event) {
//realisation
}
}
i18n.php
Файл с языковыми константами для версии по умолчанию (русской).
В основном, его константы задействованы в административной панели.
Пример файла:
/**
* Языковые константы для русской версии
*/
$i18n = [
'module-tickets' => 'Заметки',
'header-tickets-tickets' => 'Управление заметками',
'perms-tickets-tickets' => 'Управление заметками',
'wrong-permissions-json' => 'Редактировать или удалять заметки могут только их владельцы или супервайзеры',
'ticket-not-found-json' => 'Заметка не найдена',
'js-del-object-title-short' => 'Удаление',
];
?>
*Префикс "js-" в имени ключа константы делает ее доступной в js коде админ панели.
В php и js значение константы можно получить с помощью глобальной функции getLabel(),
подробнее вопрос интернационализации описан в следующей статье.
includes.php
Файл для подключения сторонних (внешних) классов и/или произвольного кода.
Под сторонними (внешними) классами имеются ввиду классы, которые используются модулем, но не имплементируются в него.
install.php
Установщик модуля.
Содержит значения реестра для модуля и перечень файлов, которые входят в состав модуля.
Файл предназначен для установки модуля через административную панель модуля "Конфигурация" - вкладка "Модули".
Пример файла:
/**
* @var array $INFO реестр модуля
*/
$INFO = [];
$INFO['name'] = "tickets"; // имя модуля
$INFO['config'] = "0"; // содержит ли модуль вкладку с настройками
$INFO['default_method'] = "empty"; // метод по умолчанию для клиентской части модуля
$INFO['default_method_admin'] = "tickets"; // метод по умолчанию для административной части модуля
$INFO['func_perms'] = "Группы прав на функционал модуля"; // родительский ключ реестра для группы пра
$INFO['func_perms/tickets'] = "Права на управление заметками"; // группа прав на функционал модуля
/**
* @var array $COMPONENTS файлы модуля
*/
$COMPONENTS = [];
$COMPONENTS[] = "./classes/components/tickets/admin.php";
// ...
$COMPONENTS[] = "./classes/components/tickets/permissions.php";
lang.php
Файл с языковыми константами для версии по умолчанию (русской).
В основном, его константы задействованы в клиентской части.
Данные файлы редко используются и оставлены для обратной совместимости. Помимо директории модуля их можно помещать в папку с шаблонами templates/ваш_шаблон.
Пример файла:
/**
* Языковые константы для русской версии
*/
$C_LANG = [];
$C_LANG['module_name'] = "Интеграция с социальными сетями";
$C_LANG['module_title'] = 'Интеграция с социальными сетями';
$C_LANG['module_description'] = 'Модуль интеграции с социальными сетями позволяет ';
$C_LANG['config'] = 'Настройки модуля';
$LANG_EXPORT = [];
macros.php
Класс содержит макросы, то есть методы, которые доступны в шаблоне сайта
Файл подключается только в клиентском режиме работы модуля. Переопределить его методы можно через файл customMacros.php.
В классе доступен объект базового класса модуля, через него можно получить доступ к методам других подключенных классов.
Например:
/**
* Класс функционала административной панели
*/
class CatalogMacros {
/**
* @var catalog $module
*/
public $module;
};
permissions.php
Файл содержит перечень групп функционала, для которых можно настроить права.
Формат - [имя_группы_прав => [имя_метода, имя_метода]]
Например:
/**
* Группы прав на функционал модуля
*/
$permissions = [
/**
* Права на администрирование модуля
*/
'banners_list' => [
'add',
'edit',
'activity',
'del',
'lists',
'places',
'config'
],
/**
* Права на просмотр баннеров
*/
'insert' => [
'insert',
'go_to',
'fastinsert',
'multiplefastinsert',
'getstaticbannercall',
'renderBanner'
]
];
Если есть необходимость добавить свой кастомный метод в существующую группу прав,
то в директории с модулем нужно создать файл permissions.custom.php аналогичного формата.
Подробнее вопрос прав описан в следующей статье.