Модульная система

Грубо говоря, модулем в 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 = array();
/**
 * @var array $classes подключенные классы
 */
private $classes = array();
/**
 * @var array $methods подключенные методы
 */
private $methods = array();

 

Подключение классов к модулю и работа с их методами от имени класс модуля реализована с помощью 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)) ? array() : $args;

    //Если заданный метод был подключен
    if (isset($this->methods[$method])) {
        //$class - экземпляр класса или его имя, если метод статический
        $class = $this->methods[$method];
        //Делаем его вызов
        return call_user_func_array(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.

Пример:


    $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 = Array(
    '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 = array();
$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 = array();
$COMPONENTS[] = "./classes/components/tickets/admin.php";
// ...
$COMPONENTS[] = "./classes/components/tickets/permissions.php";

 

lang.php

Файл с языковыми константами для версии по умолчанию (русской).

В основном, его константы задействованы в клиентской части.

Данные файлы редко используются и оставлены для обратной совместимости.

Пример файла:


/**
 * Языковые константы для русской версии
 */
$C_LANG = Array();
$C_LANG['module_name'] = "Интеграция с социальными сетями";
$C_LANG['module_title'] = 'Интеграция с социальными сетями';
$C_LANG['module_description'] = 'Модуль интеграции с социальными сетями позволяет ';
$C_LANG['config'] = 'Настройки модуля';

$LANG_EXPORT = Array();

 

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 аналогичного формата.

Подробнее вопрос прав описан в следующей статье.