Мдель: "model" в терминах MVC. Класс, унаследованный от DataManager или реализующий аналогичный API.
- *
Хэлпер: "view" в терминах MVC. Класс, реализующий отрисовку интерфейса списка или детальной страницы.
- *
Роутер: "controller" в терминах MVC. Файл, принимающий все запросы к админке данного модуля, создающий нужные
- * хэлперы с нужными настройками. С ним напрямую работать не придётся.
- *
Виджеты: "delegate" в терминах MVC. Классы, отвечающие за отрисовку элементов управления для отдельных полей
- * сущностей. В списке и на детальной.
- *
- *
- * Схема работы с модулем следующя:
- *
- *
Реализация класса AdminListHelper - для управления страницей списка элементов
- *
Реализация класса AdminEditHelper - для управления страницей просмотра/редактирования элемента
- *
Создание файла Interface.php с вызовом AdminBaseHelper::setInterfaceSettings(), в которую передается
- * конфигурация
- * полей админки и классы, используемые для её построения.
- *
Если не хватает возможностей виджетов, идущих с модулем, можно реализовать свой виджет, унаследованный от любого
- * другого готового виджета или от абстрактного класса HelperWidget
- *
- *
- * Рекомендуемая файловая структура для модулей, использующих данный функционал:
- *
- *
Каталог admin. Достаточно поместить в него файл menu.php, отдельные файлы для списка и детальной
- * создавать не надо благодаря единому роутингу.
- *
Каталог classes (или lib): содержит классы модли, представлений и делегатов.
- *
-- classes/helper: каталог, содержащий классы "view", унаследованные от AdminListHelper и
- * AdminEditHelper.
- *
-- classes/widget: каталог, содержащий виджеты ("delegate"), если для модуля пришлось создавать
- * свои.
- *
-- classes/model: каталог с моделями, если пришлось переопределять поведение стандартынх функций getList
- * и т.д.
- *
- *
- * Использовать данную структуру не обязательно, это лишь рекомендация, основанная на успешном опыте применения модуля
- * в ряде проектов.
- *
- * @see AdminBaseHelper::setInterfaceSettings()
- * @package AdminHelper
- * @FIXME: Упростить обработку сообщений об ошибках: слишком запутанно.
- */
-abstract class AdminBaseHelper
-{
- /**
- * @internal
- * @var string адрес обработчика запросов к админ. интерфейсу.
- */
- static protected $routerUrl = '/bitrix/admin/admin_helper_route.php';
-
- /**
- * @var string
- * Имя класса используемой модели. Используется для выполнения CRUD-операций.
- * При наследовании класса необходимо переопределить эту переменную, указав полное имя класса модели.
- *
- * @see DataManager
- * @api
- */
- static protected $model;
-
- /**
- * @var string
- * Назвние модуля данной модели.
- * При наследовании класса необходимо указать нзвание модуля, в котором он находится.
- * Используется для избежания конфликтов между именами представлений.
- *
- * @api
- */
- static public $module = '';
-
- /**
- * @var string
- * Название представления.
- * При наследовании класса необходимо указать название представления. Оно будет использовано при построении URL к
- * данному разделу админки. Не должно содержать пробелов и других символов, требующих преобразований для
- * адресной строки браузера.
- *
- * @api
- */
- static protected $viewName;
-
- /**
- * @var array
- * Настройки интерфейса
- * @see AdminBaseHelper::setInterfaceSettings()
- * @internal
- */
- static protected $interfaceSettings = array();
-
- /**
- * @var array
- * Хранит список отображаемых полей и настройки их отображения
- * @see AdminBaseHelper::setInterfaceSettings()
- */
- protected $fields = array();
-
- /**
- * @var \CMain
- * Замена global $APPLICATION;
- */
- protected $app;
- protected $validationErrors = array();
-
- /**
- * @var string
- * Позволяет непосредственно указать адрес страницы списка. Полезно, в случае, если такая станица реализована без
- * использования данного модуля. В случае, если поле определено для класса, роутинг не используется.
- *
- * @see AdminBaseHelper::getListPageUrl
- * @api
- */
- static protected $listPageUrl;
-
- /**
- * @var string
- * $viewName представления, отвечающего за страницу списка. Необходимо указывать только для классов, уналедованных
- * от AdminEditHelper.
- *
- * @see AdminBaseHelper::$viewName
- * @see AdminBaseHelper::getListPageUrl
- * @see AdminEditHelper
- * @api
- */
- static protected $listViewName;
-
- /**
- * @var string
- * Позволяет непосредственно указать адрес страницы просмотра/редактирования элемента. Полезно, в случае, если
- * такая станица реализована без использования данного модуля. В случае, если поле определено для класса,
- * роутинг не используется.
- *
- * @see AdminBaseHelper::getEditPageUrl
- * @api
- */
- static protected $editPageUrl;
-
-
- /**
- * @var string
- * $viewName представления, отвечающего за страницу редактирования/просмотра элемента. Необходимо указывать только
- * для классов, уналедованных от AdminListHelper.
- *
- * @see AdminBaseHelper::$viewName
- * @see AdminBaseHelper::getEditPageUrl
- * @see AdminListHelper
- * @api
- */
- static protected $editViewName;
-
- /**
- * @var array
- * Дополнительные параметры URL, которые будут добавлены к параметрам по-умолчанию, генерируемым автоматически
- * @api
- */
- protected $additionalUrlParams = array();
-
- /**
- * @var string контекст выполнения. Полезен для информирования виджетов о том, какая операция в настоящий момент
- * производится.
- */
- protected $context = '';
-
- /**
- * @param array $fields список используемых полей и виджетов для них
- * @param array $tabs список вкладок для детальной страницы
- * @param string $module название модуля
- */
- public function __construct(array $fields, array $tabs = array(), $module = "")
- {
- global $APPLICATION;
- $this->app = $APPLICATION;
-
- $this->loadMessages();
-
- $settings = array(
- 'FIELDS' => $fields,
- 'TABS' => $tabs
- );
- if (static::setInterfaceSettings($settings)) {
- $this->fields = $fields;
- } else {
- $settings = static::getInterfaceSettings();
- $this->fields = $settings['FIELDS'];
- }
- }
-
- /**
- * Подгрузка ленг-файла с сообщениями для локализации
- */
- protected function loadMessages()
- {
-
- }
-
- /**
- * @param string $viewName - имя вьюхи, для которой мы хотим получить натсройки
- *
- * @return array Возвращает настройки интерфейса для данного класса.
- *
- * @see AdminBaseHelper::setInterfaceSettings()
- * @api
- */
- static public function getInterfaceSettings($viewName = '')
- {
- if (empty($viewName)) {
- $viewName = static::$viewName;
- }
- return self::$interfaceSettings[static::getModule()][$viewName]['interface'];
- }
-
- /**
- * Основная функция для конфигурации всего административного интерфейса.
- *
- * @param array $settings настройки полей и вкладок
- * @param array $helpers список классов-хэлперов, используемых для отрисовки админки
- * @param string $module название модуля
- *
- * @return bool false, если для данного класса уже были утановлены настройки
- *
- * @api
- */
- static public function setInterfaceSettings(array $settings, array $helpers = array(), $module = '')
- {
- foreach ($helpers as $helper/**@var AdminBaseHelper $helper */) {
- $success = $helper::registerInterfaceSettings($module, $settings);
- if (!$success) return false;
- }
-
- return true;
- }
-
- /**
- * Регистрирует настройки интерфейса для текущего хелпера
- *
- * @param string $module имя текущего модуля
- * @param $interfaceSettings
- * @return bool
- * @internal
- */
- static public function registerInterfaceSettings($module, $interfaceSettings)
- {
- if (empty($module)) {
- return false;
- }
- self::$module = $module;
-
- if (empty($interfaceSettings)) {
- return false;
- }
-
- if (isset(self::$interfaceSettings[$module][static::$viewName])) {
- return false;
- }
-
- self::$interfaceSettings[$module][static::$viewName] = array(
- 'helper' => get_called_class(),
- 'interface' => $interfaceSettings
- );
-
- return true;
- }
-
- /**
- * Получает настройки интерфейса для данного модуля и представления
- * Используется при роутинге.
- * Возвращается массив со следующими ключами:
- *
- *
- *
helper - название класса-хэлпера, который будет рисовать страницу
- *
interface - настройки интерфейса для хелпера
- *
- *
- * @param string $module Модуль, для которого нужно получить настройки
- * @param string $view Название представления
- * @return array
- * @internal
- */
- static public function getGlobalInterfaceSettings($module, $view)
- {
- if (!isset(self::$interfaceSettings[$module][$view])) {
- return false;
- }
-
- return array(
- self::$interfaceSettings[$module][$view]['helper'],
- self::$interfaceSettings[$module][$view]['interface'],
- );
- }
-
- /**
- * @return string
- * Возвращает имя текущего представления
- * @api
- */
- public static function getViewName()
- {
- return static::$viewName;
- }
-
- /**
- * @return \Bitrix\Main\Entity\DataManager|string Возвращает имя класса используемой модели
- * Возвращает имя класса используемой модели
- *
- * @throws \Bitrix\Main\ArgumentException
- * @throws \Bitrix\Main\SystemException
- * @throws \Exception
- * @api
- */
- public static function getModel()
- {
- return static::getHLEntity(static::$model);
- }
-
- /**
- * Возвращает имя модуля
- * @return string
- * @api
- */
- static public function getModule()
- {
- return static::$module;
- }
-
- /**
- * Возвращает список полей, переданных через AdminBaseHelper::setInterfaceSettings()
- * @see AdminBaseHelper::setInterfaceSettings()
- * @return array
- * @api
- */
- public function getFields()
- {
- return $this->fields;
- }
-
- /**
- * Окончательно выводит админисстративную страницу
- * @internal
- */
- abstract public function show();
-
- /**
- * Получает название таблицы используемой модели
- * @return mixed
- */
- public function table()
- {
- /**@var DataManager $className */
- $className = static::getModel();
-
- return $className::getTableName();
- }
-
- /**
- * Возвращает первичный ключ таблицы используемой модели
- * Для HL-инфоблоков битрикс - всегда ID. Но может поменяться для какой-либо другой сущности.
- * @return string
- * @api
- */
- public function pk()
- {
- return 'ID';
- }
-
- /**
- * Устанавливает заголовок раздела в админке
- * @param $title
- * @api
- */
- public function setTitle($title)
- {
- $this->app->SetTitle($title);
- }
-
- /**
- * Функция для обработки дополнительных операций над элементами в админке.
- * Как правило должно оканчиваться LocalRedirect после внесения изменений.
- *
- * @param string $action Название действия
- * @param null|int $id ID элемента
- * @api
- */
- protected function customActions($action, $id = null)
- {
- return;
- }
-
- /**
- * Выполняется проверка прав на выполнение опреаций редактирования элементов
- * @return bool
- * @api
- */
- protected function hasRights()
- {
- return true;
- }
-
- /**
- * Выводит сообщения об ошибках
- * @internal
- */
- protected function showMessages()
- {
- $allErrors = $this->getErrors();
- $notes = $this->getNotes();
-
- if (!empty($allErrors)) {
- $errorList[] = implode("\n", $allErrors);
- }
- if ($e = $this->getLastException()) {
- $errorList[] = trim($e->GetString());
- }
-
-
- if (!empty($errorList)) {
- $errorText = implode("\n\n", $errorList);
- \CAdminMessage::ShowOldStyleError($errorText);
-
- } else {
- if (!empty($notes)) {
- $noteText = implode("\n\n", $notes);
- \CAdminMessage::ShowNote($noteText);
- }
- }
- }
-
- /**
- * @return bool|\CApplicationException
- * @internal
- */
- protected function getLastException()
- {
- if (isset($_SESSION['APPLICATION_EXCEPTION']) AND !empty($_SESSION['APPLICATION_EXCEPTION'])) {
- /** @var CApplicationException $e */
- $e = $_SESSION['APPLICATION_EXCEPTION'];
- unset($_SESSION['APPLICATION_EXCEPTION']);
-
- return $e;
- } else {
- return false;
- }
-
- }
-
- /**
- * @param $e
- */
- protected function setAppException($e)
- {
- $_SESSION['APPLICATION_EXCEPTION'] = $e;
- }
-
- /**
- * Добавляет ошибку или массив ошибок для показа пользователю
- * @param array|string $errors
- * @api
- */
- public function addErrors($errors)
- {
- if (!is_array($errors)) {
- $errors = array($errors);
- }
-
- if (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {
- $_SESSION['ELEMENT_SAVE_ERRORS'] = array_merge($_SESSION['ELEMENT_SAVE_ERRORS'], $errors);
- } else {
- $_SESSION['ELEMENT_SAVE_ERRORS'] = $errors;
- }
- }
-
- /**
- * Добавляет уведомление или список уведомлений для показа пользователю
- * @param array|string $notes
- * @api
- */
- public function addNotes($notes)
- {
- if (!is_array($notes)) {
- $notes = array($notes);
- }
-
- if (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {
- $_SESSION['ELEMENT_SAVE_NOTES'] = array_merge($_SESSION['ELEMENT_SAVE_NOTES'],
- $notes);
- } else {
- $_SESSION['ELEMENT_SAVE_NOTES'] = $notes;
- }
- }
-
- /**
- * @return bool|array
- * @api
- */
- protected function getErrors()
- {
- if (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {
- $errors = $_SESSION['ELEMENT_SAVE_ERRORS'];
- unset($_SESSION['ELEMENT_SAVE_ERRORS']);
-
- return $errors;
- } else {
- return false;
- }
- }
-
- /**
- * @return bool
- * @api
- */
- protected function getNotes()
- {
- if (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {
- $notes = $_SESSION['ELEMENT_SAVE_NOTES'];
- unset($_SESSION['ELEMENT_SAVE_NOTES']);
-
- return $notes;
- } else {
- return false;
- }
- }
-
- /**
- * Возвращает URL страницы редактирования класса данного представления
- * @param array $params
- * @return string
- * @api
- */
- static public function getEditPageURL($params = array())
- {
- $viewName = isset(static::$editViewName) ? static::$editViewName : static::$viewName;
- if (!isset($viewName)) {
- $query = "?lang=" . LANGUAGE_ID . '&' . http_build_query($params);
- if (is_subclass_of(get_called_class(), 'AdminEditHelper')) {
- return $query;
- } else {
- return static::$editPageUrl . $query;
- }
- }
-
- return static::getViewURL($viewName, static::$editPageUrl, $params);
- }
-
-
- /**
- * Возвращает URL страницы списка класса данного представления
- * @param array $params
- * @return string
- * @api
- */
- static public function getListPageURL($params = array())
- {
- $viewName = isset(static::$listViewName) ? static::$listViewName : static::$viewName;
-
- return static::getViewURL($viewName, static::$listPageUrl, $params);
- }
-
- /**
- * Получает URL для указанного представления
- *
- * @param string $viewName название представления
- * @param string $defaultURL позволяет указать URL напрямую. Если указано, то будет использовано это значение
- * @param array $params - дополнительные query-параметры в URL
- * @return string
- * @internal
- */
- static public function getViewURL($viewName, $defaultURL, $params = array())
- {
- if (isset($defaultURL)) {
- $url = $defaultURL . "?lang=" . LANGUAGE_ID;
- } else {
- $url = static::getRouterURL() . '?lang=' . LANGUAGE_ID . '&module=' . static::getModule() . '&view=' . $viewName;
- }
-
- if (!empty($params)) {
- unset($params['lang']);
- unset($params['module']);
- unset($params['view']);
-
- $query = http_build_query($params);
- $url .= '&' . $query;
- }
-
- return $url;
- }
-
- /**
- * Возвращает адрес обработчика запросов к админ. интерфейсу.
- * @return string
- * @api
- */
- static public function getRouterURL()
- {
- return static::$routerUrl;
- }
-
- /**
- * Получает виджет для текущего поля, выполняет базовую инициализацию.
- *
- * @param string $code ключ поля для данного виджета (должен быть в массиве $data)
- * @param array $data - данные объекта в виде массива
- * @return bool|\DigitalWand\AdminHelper\Widget\HelperWidget
- * @throws \DigitalWand\AdminHelper\Helper\Exception
- * @internal
- */
- public function createWidgetForField($code, &$data = array())
- {
- if (!isset($this->fields[$code]['WIDGET'])) {
- $error = str_replace('#CODE#', $code, 'Can\'t create widget for the code "#CODE#"');
- throw new Exception($error, Exception::CODE_NO_WIDGET);
- }
-
- /** @var HelperWidget $widget */
- $widget = $this->fields[$code]['WIDGET'];
-
- $widget->setHelper($this);
- $widget->setCode($code);
- $widget->setEntityName($this->getModel());
- $widget->setData($data);
-
- return $widget;
- }
-
- /**
- * Если класс не объявлен, то битрикс генерирует новый класс в рантайме.
- * Если класс уже есть, то возвращаем имя как есть.
- *
- * @param $className
- * @return \Bitrix\Highloadblock\DataManager
- *
- * @throws \Bitrix\Main\ArgumentException
- * @throws \Bitrix\Main\SystemException
- * @throws Exception
- *
- */
- public static function getHLEntity($className)
- {
- if (!class_exists($className)) {
- $info = static::getHLEntityInfo($className);
- if ($info) {
- $entity = HL\HighloadBlockTable::compileEntity($info);
- return $entity->getDataClass();
- } else {
- $error = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_GETMODEL_EXCEPTION', array('#CLASS#' => $className));
- $exception = new Exception($error, Exception::CODE_NO_HL_ENTITY_INFORMATION);
-
- throw $exception;
- }
- }
-
- return $className;
- }
-
- /**
- * Получает запись из БД с информацией об HL.
- *
- * @param $className - название класса, обязательно без Table в конце и без указания неймспейса
- * @return array|false
- * @throws \Bitrix\Main\ArgumentException
- */
- public static function getHLEntityInfo($className)
- {
- $className = str_replace('\\', '', $className);
- $pos = strripos($className, 'Table', -5);
- if ($pos !== false) {
- $className = substr($className, 0, $pos);
- }
- $parameters = array(
- 'filter' => array(
- 'NAME' => $className,
- ),
- 'limit' => 1
- );
-
- return HL\HighloadBlockTable::getList($parameters)->fetch();
- }
-
- /**
- * Выставляет текущий контекст исполнения.
- * @param $context
- * @see $context
- */
- protected function setContext($context)
- {
- $this->context = $context;
- }
-
- public function getContext()
- {
- return $this->context;
- }
+
+ *
Мдель: "model" в терминах MVC. Класс, унаследованный от DataManager или реализующий аналогичный API.
+ *
Хэлпер: "view" в терминах MVC. Класс, реализующий отрисовку интерфейса списка или детальной страницы.
+ *
Роутер: "controller" в терминах MVC. Файл, принимающий все запросы к админке данного модуля, создающий нужные
+ * хэлперы с нужными настройками. С ним напрямую работать не придётся.
+ *
Виджеты: "delegate" в терминах MVC. Классы, отвечающие за отрисовку элементов управления для отдельных полей
+ * сущностей. В списке и на детальной.
+ *
+ *
+ * Схема работы с модулем следующая:
+ *
+ *
Реализация класса AdminListHelper - для управления страницей списка элементов
+ *
Реализация класса AdminEditHelper - для управления страницей просмотра/редактирования элемента
+ *
Реализация класса AdminInterface - для описания конфигурации полей админки и классы интерфейсов
+ *
Реализация класса AdminSectionListHelper - для описания странице списка разделов(если они используются)
+ *
Реализация класса AdminSectionEditHelper - для управления страницей просмотра/редактирования раздела(если они используются)
+ *
Если не хватает возможностей виджетов, идущих с модулем, можно реализовать свой виджет, унаследованный от любого
+ * другого готового виджета или от абстрактного класса HelperWidget
+ *
+ *
+ * Устаревший функционал:
+ *
+ *
Файл Interface.php с вызовом AdminBaseHelper::setInterfaceSettings(), в который передается
+ * конфигурация полей админки и классы.
+ *
+ * Рекомендуемая файловая структура для модулей, использующих данный функционал:
+ *
+ *
Каталог admin. Достаточно поместить в него файл menu.php, отдельные файлы для списка и детальной
+ * создавать не надо благодаря единому роутингу.
+ *
Каталог classes (или lib): содержит классы модли, представлений и делегатов.
+ *
-- classes/admininterface: каталог, содержащий классы "view", унаследованные от AdminListHelper,
+ * AdminEditHelper, AdminInterface, AdminSectionListHelper и AdminSectionEditHelper.
+ *
-- classes/widget: каталог, содержащий виджеты ("delegate"), если для модуля пришлось создавать
+ * свои.
+ *
-- classes/model: каталог с моделями, если пришлось переопределять поведение стандартынх функций getList
+ * и т.д.
+ *
+ *
+ * Использовать данную структуру не обязательно, это лишь рекомендация, основанная на успешном опыте применения модуля
+ * в ряде проектов.
+ *
+ * Единственное обязательное условие - расположение всех реализуемых классов админ хелперов и админ интерфейсов
+ * в одном неймспейсе
+ *
+ * @see AdminInterface::fields()
+ * @package AdminHelper
+ *
+ * @author Nik Samokhvalov
+ * @author Artem Yarygin
+ */
+abstract class AdminBaseHelper
+{
+ /**
+ * @internal
+ * @var string адрес обработчика запросов к админ. интерфейсу.
+ */
+ static protected $routerUrl = '/bitrix/admin/admin_helper_route.php';
+
+ /**
+ * @var string
+ * Имя класса используемой модели. Используется для выполнения CRUD-операций.
+ * При наследовании класса необходимо переопределить эту переменную, указав полное имя класса модели.
+ *
+ * @see DataManager
+ * @api
+ */
+ static protected $model;
+
+ /**
+ * @var string
+ * Назвние модуля данной модели.
+ * При наследовании класса необходимо указать нзвание модуля, в котором он находится.
+ * А можно и не указывать, в этому случае он определится автоматически по namespace класса
+ * Используется для избежания конфликтов между именами представлений.
+ *
+ * @api
+ */
+ static public $module = array();
+
+ /**
+ * @var string[]
+ * Название представления.
+ * При наследовании класса необходимо указать название представления.
+ * А можно и не указывать, в этому случае оно определится автоматически по namespace класса.
+ * Оно будет использовано при построении URL к данному разделу админки.
+ * Не должно содержать пробелов и других символов, требующих преобразований для
+ * адресной строки браузера.
+ *
+ * @api
+ */
+ static protected $viewName = array();
+
+ /**
+ * @var array
+ * Настройки интерфейса
+ * @see AdminBaseHelper::setInterfaceSettings()
+ * @internal
+ */
+ static protected $interfaceSettings = array();
+
+ /**
+ * @var array
+ * Привязка класса интерфеса к классу хелпера
+ */
+ static protected $interfaceClass = array();
+
+ /**
+ * @var array
+ * Хранит список отображаемых полей и настройки их отображения
+ * @see AdminBaseHelper::setInterfaceSettings()
+ */
+ protected $fields = array();
+
+ /**
+ * @var \CMain
+ * Замена global $APPLICATION;
+ */
+ protected $app;
+ protected $validationErrors = array();
+
+ /**
+ * @var string
+ * Позволяет непосредственно указать адрес страницы списка. Полезно, в случае, если такая станица реализована без
+ * использования данного модуля. В случае, если поле определено для класса, роутинг не используется.
+ *
+ * @see AdminBaseHelper::getListPageUrl
+ * @api
+ */
+ static protected $listPageUrl;
+
+ /**
+ * @var string
+ * $viewName представления, отвечающего за страницу списка. Необходимо указывать только для классов, уналедованных
+ * от AdminEditHelper.
+ * Необязательное, сгенерируется автоматически если не определено
+ *
+ * @see AdminBaseHelper::getViewName()
+ * @see AdminBaseHelper::getListPageUrl
+ * @see AdminEditHelper
+ * @api
+ */
+ static protected $listViewName;
+
+ /**
+ * @var string
+ * Позволяет непосредственно указать адрес страницы просмотра/редактирования элемента. Полезно, в случае, если
+ * такая станица реализована без использования данного модуля. В случае, если поле определено для класса,
+ * роутинг не используется.
+ *
+ * @see AdminBaseHelper::getEditPageUrl
+ * @api
+ */
+ static protected $editPageUrl;
+
+ /**
+ * @var string
+ * $viewName представления, отвечающего за страницу редактирования/просмотра элемента. Необходимо указывать только
+ * для классов, уналедованных от AdminListHelper.
+ *
+ * @see AdminBaseHelper::getViewName()
+ * @see AdminBaseHelper::getEditPageUrl
+ * @see AdminListHelper
+ * @api
+ */
+ static protected $editViewName;
+
+ /**
+ * @var string
+ * Позволяет непосредственно указать адрес страницы просмотра/редактирования раздела. Полезно, в случае, если
+ * такая станица реализована без использования данного модуля. В случае, если поле определено для класса,
+ * роутинг не используется.
+ *
+ * @see AdminBaseHelper::getEditPageUrl
+ * @api
+ */
+ static protected $sectionsEditPageUrl;
+
+ /**
+ * @var string
+ * $viewName представления, отвечающего за страницу редактирования/просмотра раздела. Необходимо указывать только
+ * для классов, уналедованных от AdminListHelper.
+ * Необязательное, сгенерируется автоматически если не определено
+ *
+ * @see AdminBaseHelper::getViewName()
+ * @see AdminBaseHelper::getEditPageUrl
+ * @see AdminListHelper
+ * @api
+ */
+ static protected $sectionsEditViewName;
+
+ /**
+ * @var array
+ * Дополнительные параметры URL, которые будут добавлены к параметрам по-умолчанию, генерируемым автоматически
+ * @api
+ */
+ protected $additionalUrlParams = array();
+
+ /**
+ * @var string контекст выполнения. Полезен для информирования виджетов о том, какая операция в настоящий момент
+ * производится.
+ */
+ protected $context = '';
+
+ /**
+ * Флаг использования разделов, необходимо переопределять в дочернем классе
+ * @var bool
+ */
+ static protected $useSections = false;
+
+ /**
+ * Правило именования хелперов для разделов по умолчанию
+ * @var string
+ */
+ static protected $sectionSuffix = 'Sections';
+
+ /**
+ * @param array $fields список используемых полей и виджетов для них
+ * @param array $tabs список вкладок для детальной страницы
+ * @param string $module название модуля
+ */
+ public function __construct(array $fields, array $tabs = array(), $module = "")
+ {
+ global $APPLICATION;
+
+ $this->app = $APPLICATION;
+
+ $settings = array(
+ 'FIELDS' => $fields,
+ 'TABS' => $tabs
+ );
+ if (static::setInterfaceSettings($settings)) {
+ $this->fields = $fields;
+ }
+ else {
+ $settings = static::getInterfaceSettings();
+ $this->fields = $settings['FIELDS'];
+ }
+ }
+
+ /**
+ * @param string $viewName Имя вьюхи, для которой мы хотим получить натсройки
+ *
+ * @return array Возвращает настройки интерфейса для данного класса.
+ *
+ * @see AdminBaseHelper::setInterfaceSettings()
+ * @api
+ */
+ public static function getInterfaceSettings($viewName = '')
+ {
+ if (empty($viewName)) {
+ $viewName = static::getViewName();
+ }
+
+ return self::$interfaceSettings[static::getModule()][$viewName]['interface'];
+ }
+
+ /**
+ * Основная функция для конфигурации всего административного интерфейса.
+ *
+ * @param array $settings настройки полей и вкладок
+ * @param array $helpers список классов-хэлперов, используемых для отрисовки админки
+ * @param string $module название модуля
+ *
+ * @return bool false, если для данного класса уже были утановлены настройки
+ *
+ * @api
+ */
+ public static function setInterfaceSettings(array $settings, array $helpers = array(), $module = '')
+ {
+ foreach ($helpers as $helperClass => $helperSettings) {
+ if (!is_array($helperSettings)) { // поддержка старого формата описания хелперов
+ $helperClass = $helperSettings; // в значении передается класс хелпера а не настройки
+ $helperSettings = array(); // настроек в старом формате нет
+ }
+ $success = $helperClass::registerInterfaceSettings($module, array_merge($settings, $helperSettings));
+ if (!$success) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Привязывает класса хелпера из которого вызывается к интерфесу, используется при получении
+ * данных об элементах управления из интерфейса.
+ *
+ * @param $class
+ */
+ public static function setInterfaceClass($class)
+ {
+ static::$interfaceClass[get_called_class()] = $class;
+ }
+
+ /**
+ * Возвращает класс интерфейса к которому привязан хелпер из которого вызван метод.
+ *
+ * @return array
+ */
+ public static function getInterfaceClass()
+ {
+ return isset(static::$interfaceClass[get_called_class()]) ? static::$interfaceClass[get_called_class()] : false;
+ }
+
+ /**
+ * Регистрирует настройки интерфейса для текущего хелпера
+ *
+ * @param string $module имя текущего модуля
+ * @param $interfaceSettings
+ *
+ * @return bool
+ * @internal
+ */
+ public static function registerInterfaceSettings($module, $interfaceSettings)
+ {
+ if (isset(self::$interfaceSettings[$module][static::getViewName()]) || empty($module)
+ || empty($interfaceSettings)
+ ) {
+ return false;
+ }
+
+ self::$interfaceSettings[$module][static::getViewName()] = array(
+ 'helper' => get_called_class(),
+ 'interface' => $interfaceSettings
+ );
+
+ return true;
+ }
+
+ /**
+ * Получает настройки интерфейса для данного модуля и представления. Используется при роутинге.
+ * Возвращается массив со следующими ключами:
+ *
+ *
+ *
helper - название класса-хэлпера, который будет рисовать страницу
+ *
interface - настройки интерфейса для хелпера
+ *
+ *
+ * @param string $module Модуль, для которого нужно получить настройки.
+ * @param string $view Название представления.
+ *
+ * @return array
+ * @internal
+ */
+ public static function getGlobalInterfaceSettings($module, $view)
+ {
+ if (!isset(self::$interfaceSettings[$module][$view])) {
+ return false;
+ }
+
+ return array(
+ self::$interfaceSettings[$module][$view]['helper'],
+ self::$interfaceSettings[$module][$view]['interface'],
+ );
+ }
+
+ /**
+ * Возвращает имя текущего представления.
+ *
+ * @return string
+ * @api
+ */
+ public static function getViewName()
+ {
+ if (!is_array(static::$viewName)) {
+ return static::$viewName;
+ }
+
+ $className = get_called_class();
+
+ if (!isset(static::$viewName[$className])) {
+ $classNameParts = explode('\\', trim($className, '\\'));
+
+ if (count($classNameParts) > 2) {
+ $classCaption = array_pop($classNameParts); // название класса без namespace
+ preg_match_all('/((?:^|[A-Z])[a-z]+)/', $classCaption, $matches);
+ $classCaptionParts = $matches[0];
+
+ if (end($classCaptionParts) == 'Helper') {
+ array_pop($classCaptionParts);
+ }
+
+ static::$viewName[$className] = strtolower(implode('_', $classCaptionParts));
+ }
+ }
+
+ return static::$viewName[$className];
+ }
+
+ /**
+ * Возвращает имя класса используемой модели.
+ *
+ * @return \Bitrix\Main\Entity\DataManager|string
+ *
+ * @throws \Bitrix\Main\ArgumentException
+ * @throws \Bitrix\Main\SystemException
+ * @throws \Exception
+ * @api
+ */
+ public static function getModel()
+ {
+ if (static::$model) {
+ return static::getHLEntity(static::$model);
+ }
+
+ return null;
+ }
+
+ /**
+ * Возвращает имя модуля. Если оно не задано, то определяет автоматически из namespace класса.
+ *
+ * @return string
+ *
+ * @throws LoaderException
+ * @api
+ */
+ public static function getModule()
+ {
+ if (!is_array(static::$module)) {
+ return static::$module;
+ }
+
+ $className = get_called_class();
+
+ if (!isset(static::$module[$className])) {
+ $classNameParts = explode('\\', trim($className, '\\'));
+
+ $moduleNameParts = array();
+ $moduleName = false;
+
+ while (count($classNameParts)) {
+ $moduleNameParts[] = strtolower(array_shift($classNameParts));
+ $moduleName = implode('.', $moduleNameParts);
+
+ if (ModuleManager::isModuleInstalled($moduleName)) {
+ static::$module[$className] = $moduleName;
+ break;
+ }
+ }
+
+ if (empty($moduleName)) {
+ throw new LoaderException('Module name not found');
+ }
+ }
+
+ return static::$module[$className];
+ }
+
+ /**
+ * Возвращает модифцированный массив с описанием элемента управления по его коду. Берет название и настройки
+ * из админ-интерфейса, если они не заданы — используются значения по умолчанию.
+ *
+ * Если элемент управления описан в админ-интерфейсе, то дефолтные настройки и описанные в классе интерфейса
+ * будут совмещены (смержены).
+ *
+ * @param $code
+ * @param $params
+ * @param array $keys
+ *
+ * @return array|bool
+ */
+ protected function getButton($code, $params, $keys = array('name', 'TEXT'))
+ {
+ $interfaceClass = static::getInterfaceClass();
+ $interfaceSettings = static::getInterfaceSettings();
+
+ if ($interfaceClass && !empty($interfaceSettings['BUTTONS'])) {
+ $buttons = $interfaceSettings['BUTTONS'];
+
+ if (is_array($buttons) && isset($buttons[$code])) {
+ if ($buttons[$code]['VISIBLE'] == 'N') {
+ return false;
+ }
+ $params = array_merge($params, $buttons[$code]);
+
+ return $params;
+ }
+ }
+
+ $text = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_' . $code);
+
+ foreach ($keys as $key) {
+ $params[$key] = $text;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Возвращает список полей интерфейса.
+ *
+ * @see AdminBaseHelper::setInterfaceSettings()
+ *
+ * @return array
+ *
+ * @api
+ */
+ public function getFields()
+ {
+ return $this->fields;
+ }
+
+ /**
+ * Окончательно выводит административную страницу.
+ */
+ abstract public function show();
+
+ /**
+ * Получает название таблицы используемой модели.
+ *
+ * @return mixed
+ */
+ public function table()
+ {
+ /**
+ * @var DataManager $className
+ */
+ $className = static::getModel();
+
+ return $className::getTableName();
+ }
+
+ /**
+ * Возвращает первичный ключ таблицы используемой модели
+ * Для HL-инфоблоков битрикс - всегда ID. Но может поменяться для какой-либо другой сущности.
+ * @return string
+ * @api
+ */
+ public function pk()
+ {
+ return 'ID';
+ }
+
+ /**
+ * Возвращает первичный ключ таблицы используемой модели разделов. Для HL-инфоблоков битрикс - всегда ID.
+ * Но может поменяться для какой-либо другой сущности.
+ *
+ * @return string
+ *
+ * @api
+ */
+ public function sectionPk()
+ {
+ return 'ID';
+ }
+
+ /**
+ * Устанавливает заголовок раздела в админке.
+ *
+ * @param string $title
+ *
+ * @api
+ */
+ public function setTitle($title)
+ {
+ $this->app->SetTitle($title);
+ }
+
+ /**
+ * Функция для обработки дополнительных операций над элементами в админке. Как правило, должно оканчиваться
+ * LocalRedirect после внесения изменений.
+ *
+ * @param string $action Название действия.
+ * @param null|int $id ID элемента.
+ *
+ * @api
+ */
+ protected function customActions($action, $id = null)
+ {
+ return;
+ }
+
+ /**
+ * Выполняется проверка прав на доступ к сущности.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ protected function hasRights()
+ {
+ return true;
+ }
+
+ /**
+ * Выполняется проверка прав на выполнение операций чтения элементов.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ protected function hasReadRights()
+ {
+ return true;
+ }
+
+ /**
+ * Выполняется проверка прав на выполнение операций редактирования элементов.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ protected function hasWriteRights()
+ {
+ return true;
+ }
+
+ /**
+ * Проверка прав на изменение определенного элемента.
+ *
+ * @param array $element Массив данных элемента.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ protected function hasWriteRightsElement($element = array())
+ {
+ if (!$this->hasWriteRights()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Выполняется проверка прав на выполнение опреаций удаления элементов.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ protected function hasDeleteRights()
+ {
+ return true;
+ }
+
+ /**
+ * Выводит сообщения об ошибках.
+ *
+ * @internal
+ */
+ protected function showMessages()
+ {
+ $allErrors = $this->getErrors();
+ $notes = $this->getNotes();
+
+ if (!empty($allErrors)) {
+ $errorList[] = implode("\n", $allErrors);
+ }
+ if ($e = $this->getLastException()) {
+ $errorList[] = trim($e->GetString());
+ }
+
+ if (!empty($errorList)) {
+ $errorText = implode("\n\n", $errorList);
+ \CAdminMessage::ShowOldStyleError($errorText);
+ }
+ else {
+ if (!empty($notes)) {
+ $noteText = implode("\n\n", $notes);
+ \CAdminMessage::ShowNote($noteText);
+ }
+ }
+ }
+
+ /**
+ * @return bool|\CApplicationException
+ *
+ * @internal
+ */
+ protected function getLastException()
+ {
+ if (isset($_SESSION['APPLICATION_EXCEPTION']) AND !empty($_SESSION['APPLICATION_EXCEPTION'])) {
+ /** @var CApplicationException $e */
+ $e = $_SESSION['APPLICATION_EXCEPTION'];
+ unset($_SESSION['APPLICATION_EXCEPTION']);
+
+ return $e;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * @param $e
+ */
+ protected function setAppException($e)
+ {
+ $_SESSION['APPLICATION_EXCEPTION'] = $e;
+ }
+
+ /**
+ * Добавляет ошибку или массив ошибок для показа пользователю.
+ *
+ * @param array|string $errors
+ *
+ * @api
+ */
+ public function addErrors($errors)
+ {
+ if (!is_array($errors)) {
+ $errors = array($errors);
+ }
+
+ if (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {
+ $_SESSION['ELEMENT_SAVE_ERRORS'] = array_merge($_SESSION['ELEMENT_SAVE_ERRORS'], $errors);
+ }
+ else {
+ $_SESSION['ELEMENT_SAVE_ERRORS'] = $errors;
+ }
+ }
+
+ /**
+ * Добавляет уведомление или список уведомлений для показа пользователю.
+ *
+ * @param array|string $notes
+ *
+ * @api
+ */
+ public function addNotes($notes)
+ {
+ if (!is_array($notes)) {
+ $notes = array($notes);
+ }
+
+ if (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {
+ $_SESSION['ELEMENT_SAVE_NOTES'] = array_merge($_SESSION['ELEMENT_SAVE_NOTES'],
+ $notes);
+ }
+ else {
+ $_SESSION['ELEMENT_SAVE_NOTES'] = $notes;
+ }
+ }
+
+ /**
+ * @return bool|array
+ *
+ * @api
+ */
+ protected function getErrors()
+ {
+ if (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {
+ $errors = $_SESSION['ELEMENT_SAVE_ERRORS'];
+ unset($_SESSION['ELEMENT_SAVE_ERRORS']);
+
+ return $errors;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * @return bool
+ *
+ * @api
+ */
+ protected function getNotes()
+ {
+ if (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {
+ $notes = $_SESSION['ELEMENT_SAVE_NOTES'];
+ unset($_SESSION['ELEMENT_SAVE_NOTES']);
+
+ return $notes;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Возвращает класс хелпера нужного типа из всех зарегистрированных хелперов в модуле и находящихся
+ * в том же неймспейсе что класс хелпера из которого вызван этот метод
+ *
+ * Под типом понимается ближайший родитель из модуля AdminHelper.
+ *
+ * Например если нам нужно получить ListHelper для формирования ссылки на список из EditHelper,
+ * то это будет вглядеть так $listHelperClass = static::getHelperClass(AdminListHelper::getClass())
+ *
+ * @param $class
+ *
+ * @return string|bool
+ */
+ public function getHelperClass($class)
+ {
+ $interfaceSettings = self::$interfaceSettings[static::getModule()];
+
+ foreach ($interfaceSettings as $viewName => $settings) {
+ $parentClasses = class_parents($settings['helper']);
+ array_pop($parentClasses); // AdminBaseHelper
+
+ $parentClass = array_pop($parentClasses);
+ $thirdClass = array_pop($parentClasses);
+
+ if (in_array($thirdClass, array(AdminSectionListHelper::className(), AdminSectionEditHelper::className()))) {
+ $parentClass = $thirdClass;
+ }
+
+ if ($parentClass == $class && class_exists($settings['helper'])) {
+ $helperClassParts = explode('\\', $settings['helper']);
+ array_pop($helperClassParts);
+ $helperNamespace = implode('\\', $helperClassParts);
+
+ $сlassParts = explode('\\', get_called_class());
+ array_pop($сlassParts);
+ $classNamespace = implode('\\', $сlassParts);
+
+ if ($helperNamespace == $classNamespace) {
+ return $settings['helper'];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Возвращает относительный namespace до хелперов в виде URL параметра.
+ *
+ * @return string
+ */
+ public static function getEntityCode()
+ {
+ $namespaceParts = explode('\\', get_called_class());
+ array_pop($namespaceParts);
+ array_shift($namespaceParts);
+ array_shift($namespaceParts);
+
+ if (end($namespaceParts) == 'AdminInterface') {
+ array_pop($namespaceParts);
+ }
+
+ return str_replace(
+ '\\',
+ '_',
+ implode(
+ '\\',
+ array_map('lcfirst', $namespaceParts)
+ )
+ );
+ }
+
+ /**
+ * Возвращает URL страницы редактирования класса данного представления.
+ *
+ * @param array $params
+ *
+ * @return string
+ *
+ * @api
+ */
+ public static function getEditPageURL($params = array())
+ {
+ $editHelperClass = str_replace('List', 'Edit', get_called_class());
+ if (empty(static::$editViewName) && class_exists($editHelperClass)) {
+ return $editHelperClass::getViewURL($editHelperClass::getViewName(), static::$editPageUrl, $params);
+ }
+ else {
+ return static::getViewURL(static::$editViewName, static::$editPageUrl, $params);
+ }
+ }
+
+ /**
+ * Возвращает URL страницы редактирования класса данного представления.
+ *
+ * @param array $params
+ *
+ * @return string
+ *
+ * @api
+ */
+ public static function getSectionsEditPageURL($params = array())
+ {
+ $sectionEditHelperClass = str_replace('List', 'SectionsEdit', get_called_class());
+
+ if (empty(static::$sectionsEditViewName) && class_exists($sectionEditHelperClass)) {
+ return $sectionEditHelperClass::getViewURL($sectionEditHelperClass::getViewName(), static::$sectionsEditPageUrl, $params);
+ }
+ else {
+ return static::getViewURL(static::$sectionsEditViewName, static::$sectionsEditPageUrl, $params);
+ }
+ }
+
+ /**
+ * Возвращает URL страницы списка класса данного представления.
+ *
+ * @param array $params
+ *
+ * @return string
+ *
+ * @api
+ */
+ public static function getListPageURL($params = array())
+ {
+ $listHelperClass = str_replace('Edit', 'List', get_called_class());
+
+ if (empty(static::$listViewName) && class_exists($listHelperClass)) {
+ return $listHelperClass::getViewURL($listHelperClass::getViewName(), static::$listPageUrl, $params);
+ }
+ else {
+ return static::getViewURL(static::$listViewName, static::$listPageUrl, $params);
+ }
+ }
+
+ /**
+ * Получает URL для указанного представления
+ *
+ * @param string $viewName Название представления.
+ * @param string $defaultURL Позволяет указать URL напрямую. Если указано, то будет использовано это значение.
+ * @param array $params Дополнительные query-параметры в URL.
+ *
+ * @return string
+ *
+ * @internal
+ */
+ public static function getViewURL($viewName, $defaultURL, $params = array())
+ {
+ $params['entity'] = static::getEntityCode();
+
+ if (isset($defaultURL)) {
+ $url = $defaultURL . "?lang=" . LANGUAGE_ID;
+ }
+ else {
+ $url = static::getRouterURL() . '?lang=' . LANGUAGE_ID . '&module=' . static::getModule() . '&view=' . $viewName;
+ }
+
+ if (!empty($params)) {
+ unset($params['lang']);
+ unset($params['module']);
+ unset($params['view']);
+
+ $query = http_build_query($params);
+ $url .= '&' . $query;
+ }
+
+ return $url;
+ }
+
+ /**
+ * Возвращает адрес обработчика запросов к админ. интерфейсу.
+ *
+ * @return string
+ *
+ * @api
+ */
+ public static function getRouterURL()
+ {
+ return static::$routerUrl;
+ }
+
+ /**
+ * Возвращает URL страницы с хелпером. Как правило, метод вызывается при генерации административного
+ * меню (`menu.php`).
+ *
+ * @param array $params Дополнительные GET-параметры для подстановки в URL.
+ *
+ * @return string
+ */
+ public static function getUrl(array $params = array())
+ {
+ return static::getViewURL(static::getViewName(), null, $params);
+ }
+
+ /**
+ * Получает виджет для текущего поля, выполняет базовую инициализацию.
+ *
+ * @param string $code Ключ поля для данного виджета (должен быть в массиве $data).
+ * @param array $data Данные объекта в виде массива.
+ *
+ * @return bool|\DigitalWand\AdminHelper\Widget\HelperWidget
+ *
+ * @throws \DigitalWand\AdminHelper\Helper\Exception
+ *
+ * @internal
+ */
+ public function createWidgetForField($code, &$data = array())
+ {
+ if (!isset($this->fields[$code]['WIDGET'])) {
+ $error = str_replace('#CODE#', $code, 'Can\'t create widget for the code "#CODE#"');
+ throw new Exception($error, Exception::CODE_NO_WIDGET);
+ }
+
+ /** @var HelperWidget $widget */
+ $widget = $this->fields[$code]['WIDGET'];
+
+ $widget->setHelper($this);
+ $widget->setCode($code);
+ $widget->setData($data);
+ $widget->setEntityName($this->getModel());
+
+ $this->onCreateWidgetForField($widget, $data);
+
+ if (!$this->hasWriteRightsElement($data)) {
+ $widget->setSetting('READONLY', true);
+ }
+
+ return $widget;
+ }
+
+ /**
+ * Метод вызывается при создании виджета для текущего поля. Может быть использован для изменения настроек виджета
+ * на основе передаваемых данных.
+ *
+ * @param \DigitalWand\AdminHelper\Widget\HelperWidget $widget
+ * @param array $data
+ */
+ protected function onCreateWidgetForField(&$widget, $data = array())
+ {
+ }
+
+ /**
+ * Если класс не объявлен, то битрикс генерирует новый класс в рантайме. Если класс уже есть, то возвращаем имя
+ * как есть.
+ *
+ * @param $className
+ * @return \Bitrix\Highloadblock\DataManager
+ *
+ * @throws \Bitrix\Main\ArgumentException
+ * @throws \Bitrix\Main\SystemException
+ * @throws Exception
+ */
+ public static function getHLEntity($className)
+ {
+ if (!class_exists($className)) {
+ $info = static::getHLEntityInfo($className);
+
+ if ($info) {
+ $entity = HL\HighloadBlockTable::compileEntity($info);
+
+ return $entity->getDataClass();
+ }
+ else {
+ $error = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_GETMODEL_EXCEPTION', array('#CLASS#' => $className));
+ $exception = new Exception($error, Exception::CODE_NO_HL_ENTITY_INFORMATION);
+
+ throw $exception;
+ }
+ }
+
+ return $className;
+ }
+
+ /**
+ * Получает запись из БД с информацией об HL.
+ *
+ * @param string $className Название класса, обязательно без Table в конце и без указания неймспейса.
+ *
+ * @return array|false
+ *
+ * @throws \Bitrix\Main\ArgumentException
+ */
+ public static function getHLEntityInfo($className)
+ {
+ $className = str_replace('\\', '', $className);
+ $pos = strripos($className, 'Table', -5);
+
+ if ($pos !== false) {
+ $className = substr($className, 0, $pos);
+ }
+
+ $parameters = array(
+ 'filter' => array(
+ 'NAME' => $className,
+ ),
+ 'limit' => 1
+ );
+
+ return HL\HighloadBlockTable::getList($parameters)->fetch();
+ }
+
+ /**
+ * Выставляет текущий контекст исполнения.
+ *
+ * @param $context
+ *
+ * @see $context
+ */
+ protected function setContext($context)
+ {
+ $this->context = $context;
+ }
+
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ public static function className()
+ {
+ return get_called_class();
+ }
}
\ No newline at end of file
diff --git a/lib/helper/AdminEditHelper.php b/lib/helper/AdminEditHelper.php
index f975070..282d1f3 100644
--- a/lib/helper/AdminEditHelper.php
+++ b/lib/helper/AdminEditHelper.php
@@ -1,521 +1,608 @@
-
- *
+ *
+ * @param array $sort Настройки сортировки.
+ *
+ * @see AdminListHelper::getList();
+ * @see AdminListHelper::getMixedData();
+ * @see AdminListHelper::modifyRowData();
+ * @see AdminListHelper::addRowCell();
+ * @see AdminListHelper::addRow();
+ * @see HelperWidget::changeGetListOptions();
+ */
+ public function buildList($sort)
+ {
+ $this->setContext(AdminListHelper::OP_GET_DATA_BEFORE);
+
+ $headers = $this->arHeader;
+
+ $sectionEditHelper = static::getHelperClass(AdminSectionEditHelper::className());
+
+ if ($sectionEditHelper) { // если есть реализация класса AdminSectionEditHelper, значит используются разделы
+ $sectionHeaders = $this->getSectionsHeader();
+ foreach ($sectionHeaders as $sectionHeader) {
+ foreach ($headers as $i => $elementHeader) {
+ if ($sectionHeader['id'] == $elementHeader['id']) {
+ unset($headers[$i]);
+ }
+ }
+ }
+ $headers = array_merge($headers, $sectionHeaders);
+ }
+
+ // сортировка столбцов с сохранением исходной позиции в
+ // массиве для развнозначных элементов
+ // массив $headers модифицируется
+ $this->mergeSortHeader($headers);
+
+ $this->list->AddHeaders($headers);
+ $visibleColumns = $this->list->GetVisibleHeaderColumns();
+
+ if ($sectionEditHelper) {
+ $modelClass = $this->getModel();
+ $elementFields = array_keys($modelClass::getEntity()->getFields());
+ $sectionsVisibleColumns = array();
+ foreach ($visibleColumns as $k => $v) {
+ if (isset($this->sectionFields[$v])) {
+ if(!in_array($k, $elementFields)){
+ unset($visibleColumns[$k]);
+ }
+ $sectionsVisibleColumns[] = $v;
+ }
+ }
+ $visibleColumns = array_values($visibleColumns);
+ $visibleColumns = array_merge($visibleColumns, array_keys($this->tableColumnsMap));
+ }
+
+ $className = static::getModel();
+ $visibleColumns[] = static::pk();
+ $sectionsVisibleColumns[] = static::sectionPk();
+
+ $raw = array(
+ 'SELECT' => $visibleColumns,
+ 'FILTER' => $this->arFilter,
+ 'SORT' => $sort
+ );
+
+ foreach ($this->fields as $name => $settings) {
+ if ((isset($settings['VIRTUAL']) AND $settings['VIRTUAL'] == true)) {
+ $key = array_search($name, $visibleColumns);
+ unset($visibleColumns[$key]);
+ unset($this->arFilter[$name]);
+ unset($sort[$name]);
+ }
+ if (isset($settings['FORCE_SELECT']) AND $settings['FORCE_SELECT'] == true) {
+ $visibleColumns[] = $name;
+ }
+ }
+
+ $visibleColumns = array_unique($visibleColumns);
+ $sectionsVisibleColumns = array_unique($sectionsVisibleColumns);
+
+ // Поля для селекта (перевернутый массив)
+ $listSelect = array_flip($visibleColumns);
+ foreach ($this->fields as $code => $settings) {
+ $widget = $this->createWidgetForField($code);
+ $widget->changeGetListOptions($this->arFilter, $visibleColumns, $sort, $raw);
+ // Множественные поля не должны быть в селекте
+ if (!empty($settings['MULTIPLE'])) {
+ unset($listSelect[$code]);
+ }
+ }
+ // Поля для селекта (множественные поля отфильтрованы)
+ $listSelect = array_flip($listSelect);
+
+ if ($sectionEditHelper) // Вывод разделов и элементов в одном списке
+ {
+ $mixedData = $this->getMixedData($sectionsVisibleColumns, $visibleColumns, $sort, $raw);
+ $res = new \CDbResult;
+ $res->InitFromArray($mixedData);
+ $res = new \CAdminResult($res, $this->getListTableID());
+ $res->nSelectedCount = $this->totalRowsCount;
+ // используем кастомный NavStart что бы определить правильное количество страниц и элементов в списке
+ $this->customNavStart($res);
+ $this->list->NavText($res->GetNavPrint(Loc::getMessage("PAGES")));
+ while ($data = $res->NavNext(false)) {
+ $this->modifyRowData($data);
+ if ($data['IS_SECTION']) // для разделов своя обработка
+ {
+ list($link, $name) = $this->getRow($data, $this->getHelperClass(AdminSectionEditHelper::className()));
+ $row = $this->list->AddRow('s' . $data[$this->pk()], $data, $link, $name);
+ foreach ($this->sectionFields as $code => $settings) {
+ if (in_array($code, $sectionsVisibleColumns)) {
+ $this->addRowSectionCell($row, $code, $data);
+ }
+ }
+ $row->AddActions($this->getRowActions($data, true));
+ }
+ else // для элементов своя
+ {
+ $this->modifyRowData($data);
+ list($link, $name) = $this->getRow($data);
+ // объединение полей элемента с полями раздела
+ foreach ($this->tableColumnsMap as $elementCode => $sectionCode) {
+ if (isset($data[$elementCode])) {
+ $data[$sectionCode] = $data[$elementCode];
+ }
+ }
+ $row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);
+ foreach ($this->fields as $code => $settings) {
+ $this->addRowCell($row, $code, $data,
+ isset($this->tableColumnsMap[$code]) ? $this->tableColumnsMap[$code] : false);
+ }
+ $row->AddActions($this->getRowActions($data));
+ }
+ }
+ }
+ else // Обычный вывод элементов без использования разделов
+ {
+ $res = $this->getData($className, $this->arFilter, $listSelect, $sort, $raw);
+ $res = new \CAdminResult($res, $this->getListTableID());
+ $res->NavStart();
+ $this->list->NavText($res->GetNavPrint(Loc::getMessage("PAGES")));
+ while ($data = $res->NavNext(false)) {
+ $this->modifyRowData($data);
+ list($link, $name) = $this->getRow($data);
+ $row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);
+ foreach ($this->fields as $code => $settings) {
+ $this->addRowCell($row, $code, $data);
+ }
+ $row->AddActions($this->getRowActions($data));
+ }
+ }
+
+ $this->list->AddFooter($this->getFooter($res));
+ $this->list->AddGroupActionTable($this->getGroupActions(), $this->groupActionsParams);
+ $this->list->AddAdminContextMenu($this->getContextMenu());
+
+ $this->list->BeginPrologContent();
+ echo $this->prologHtml;
+ $this->list->EndPrologContent();
+
+ $this->list->BeginEpilogContent();
+ echo $this->epilogHtml;
+ $this->list->EndEpilogContent();
+
+ // добавляем ошибки в CAdminList для режимов list и frame
+ if(in_array($_GET['mode'], array('list','frame')) && is_array($this->getErrors())) {
+ foreach($this->getErrors() as $error) {
+ $this->list->addGroupError($error);
+ }
+ }
+
+ $this->list->CheckListMode();
+ }
+
+ /**
+ * Функция сортировки столбцов c сохранением порядка равнозначных элементов
+ * @param $array
+ */
+ protected function mergeSortHeader(&$array)
+ {
+ // для сортировки нужно хотя бы 2 элемента
+ if (count($array) < 2) return;
+
+ // делим массив пополам
+ $halfway = count($array) / 2;
+ $array1 = array_slice($array, 0, $halfway);
+ $array2 = array_slice($array, $halfway);
+
+ // реукрсивно сортируем каждую половину
+ $this->mergeSortHeader($array1);
+ $this->mergeSortHeader($array2);
+
+ // если последний элемент первой половины меньше или равен первому элементу
+ // второй половины, то просто соединяем массивы
+ if ($this->mergeSortHeaderCompare(end($array1), $array2[0]) < 1) {
+ $array = array_merge($array1, $array2);
+ return;
+ }
+
+ // соединяем 2 отсортированных половины в один отсортированный массив
+ $array = array();
+ $ptr1 = $ptr2 = 0;
+ while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
+ // собираем в 1 массив последовательную цепочку
+ // элементов из 2-х отсортированных половинок
+ if ($this->mergeSortHeaderCompare($array1[$ptr1], $array2[$ptr2]) < 1) {
+ $array[] = $array1[$ptr1++];
+ }
+ else {
+ $array[] = $array2[$ptr2++];
+ }
+ }
+
+ // если в исходных массивах что-то осталось забираем в основной массив
+ while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
+ while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
+
+ return;
+ }
+
+ /**
+ * Функция сравнения столбцов по их весу в сортировке
+ * @param $a
+ * @param $b
+ * @return int
+ */
+ public function mergeSortHeaderCompare($a, $b)
+ {
+ $a = $a['admin_list_helper_sort'];
+ $b = $b['admin_list_helper_sort'];
+ if ($a == $b) {
+ return 0;
+ }
+
+ return ($a < $b) ? -1 : 1;
+ }
+
+ /**
+ * Получение смешанного списка из разделов и элементов.
+ *
+ * @param $sectionsVisibleColumns
+ * @param $elementVisibleColumns
+ * @param $sort
+ * @param $raw
+ * @return array
+ */
+ protected function getMixedData($sectionsVisibleColumns, $elementVisibleColumns, $sort, $raw)
+ {
+ $sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());
+ $returnData = array();
+ /**
+ * @var DataManager $sectionModel
+ */
+ $sectionModel = $sectionEditHelperClass::getModel();
+ $sectionFilter = array($sectionModel::getSectionField() => $_GET['ID']);
+
+ $raw['SELECT'] = array_unique($raw['SELECT']);
+
+ // при использовании в качестве popup окна исключаем раздел из выборке
+ // что бы не было возможности сделать раздел родителем самого себя
+ if (!empty($_REQUEST['self_id'])) {
+ $sectionFilter['!' . $this->sectionPk()] = $_REQUEST['self_id'];
+ }
+
+ $sectionSort = array();
+ $limitData = $this->getLimits();
+ // добавляем к общему количеству элементов количество разделов
+ $this->totalRowsCount = $sectionModel::getCount($sectionFilter);
+ foreach ($sort as $field => $direction) {
+ if (in_array($field, $sectionsVisibleColumns)) {
+ $sectionSort[$field] = $direction;
+ }
+ }
+ // добавляем к выборке разделы
+ $rsSections = $sectionModel::getList(array(
+ 'filter' => $sectionFilter,
+ 'select' => $sectionsVisibleColumns,
+ 'order' => $sectionSort,
+ 'limit' => $limitData[1],
+ 'offset' => $limitData[0],
+ ));
+
+ while ($section = $rsSections->fetch()) {
+ $section['IS_SECTION'] = true;
+ $returnData[] = $section;
+ }
+
+ // расчитываем offset и limit для элементов
+ if (count($returnData) > 0) {
+ $elementOffset = 0;
+ }
+ else {
+ $elementOffset = $limitData[0] - $this->totalRowsCount;
+ }
+
+ // для списка разделов элементы не нужны
+ if (static::getHelperClass(AdminSectionListHelper::className()) == static::className()) {
+ return $returnData;
+ }
+
+ $elementLimit = $limitData[1] - count($returnData);
+ $elementModel = static::$model;
+ $elementFilter = $this->arFilter;
+ $elementFilter[$elementModel::getSectionField()] = $_GET['ID'];
+ // добавляем к общему количеству элементов количество элементов
+ $this->totalRowsCount += $elementModel::getCount($elementFilter);
+
+ // возвращае данные без элементов если разделы занимают всю страницу выборки
+ if (!empty($returnData) && $limitData[0] == 0 && $limitData[1] == $this->totalRowsCount) {
+ return $returnData;
+ }
+
+ $elementSort = array();
+ foreach ($sort as $field => $direction) {
+ if (in_array($field, $elementVisibleColumns)) {
+ $elementSort[$field] = $direction;
+ }
+ }
+
+ $elementParams = array(
+ 'filter' => $elementFilter,
+ 'select' => $elementVisibleColumns,
+ 'order' => $elementSort,
+ );
+ if ($elementLimit > 0 && $elementOffset >= 0) {
+ $elementParams['limit'] = $elementLimit;
+ $elementParams['offset'] = $elementOffset;
+ // добавляем к выборке элементы
+ $rsSections = $elementModel::getList($elementParams);
+
+ while ($element = $rsSections->fetch()) {
+ $element['IS_SECTION'] = false;
+ $returnData[] = $element;
+ }
+ }
+
+ return $returnData;
+ }
+
+ /**
+ * Огранчения выборки из CAdminResult
+ * @return array
+ */
+ protected function getLimits()
+ {
+ if ($this->navParams['navParams']['SHOW_ALL']) {
+ return array();
+ }
+ else {
+ if (!intval($this->navParams['navParams']['PAGEN']) OR !isset($this->navParams['navParams']['PAGEN'])) {
+ $this->navParams['navParams']['PAGEN'] = 1;
+ }
+ $from = $this->navParams['nPageSize'] * ((int)$this->navParams['navParams']['PAGEN'] - 1);
+
+ return array($from, $this->navParams['nPageSize']);
+ }
+ }
+
+ /**
+ * Выполняет CDBResult::NavNext с той разницей, что общее количество элементов берется не из count($arResult),
+ * а из нашего параметра, полученного из SQL-запроса.
+ * array_slice также не делается.
+ *
+ * @param \CAdminResult $res
+ */
+ protected function customNavStart(&$res)
+ {
+ $res->NavStart($this->navParams['nPageSize'],
+ $this->navParams['navParams']['SHOW_ALL'],
+ (int)$this->navParams['navParams']['PAGEN']
+ );
+
+ $res->NavRecordCount = $this->totalRowsCount;
+ if ($res->NavRecordCount < 1)
+ return;
+
+ if ($res->NavShowAll)
+ $res->NavPageSize = $res->NavRecordCount;
+
+ $res->NavPageCount = floor($res->NavRecordCount / $res->NavPageSize);
+ if ($res->NavRecordCount % $res->NavPageSize > 0)
+ $res->NavPageCount++;
+
+ $res->NavPageNomer =
+ ($res->PAGEN < 1 || $res->PAGEN > $res->NavPageCount
+ ?
+ (\CPageOption::GetOptionString("main", "nav_page_in_session", "Y") != "Y"
+ || $_SESSION[$res->SESS_PAGEN] < 1
+ || $_SESSION[$res->SESS_PAGEN] > $res->NavPageCount
+ ?
+ 1
+ :
+ $_SESSION[$res->SESS_PAGEN]
+ )
+ :
+ $res->PAGEN
+ );
+ }
+
+ /**
+ * Преобразует данные строки, перед тем как добавлять их в список.
+ *
+ * @param $data
+ *
+ * @see AdminListHelper::getList()
+ *
+ * @api
+ */
+ protected function modifyRowData(&$data)
+ {
+ }
+
+ /**
+ * Настройки строки таблицы.
+ *
+ * @param array $data Данные текущей строки БД.
+ * @param bool|string $class Класс хелпера через метод getUrl которого идет получение ссылки.
+ *
+ * @return array Возвращает ссылку на детальную страницу и её название.
+ *
+ * @api
+ */
+ protected function getRow($data, $class = false)
+ {
+ if (empty($class)) {
+ $class = static::getHelperClass(AdminEditHelper::className());
+ }
+ if ($this->isPopup()) {
+ return array();
+ }
+ else {
+ $query = array_merge($this->additionalUrlParams, array(
+ 'lang' => LANGUAGE_ID,
+ static::pk() => $data[static::pk()]
+ ));
+
+ return array($class::getUrl($query));
+ }
+ }
+
+ /**
+ * Для каждой ячейки(раздела) таблицы создаёт виджет соответствующего типа.
+ * Виджет подготавливает необходимый HTML для списка.
+ *
+ * @param \CAdminListRow $row
+ * @param $code Сивольный код поля.
+ * @param $data Данные текущей строки.
+ *
+ * @throws Exception
+ *
+ * @see HelperWidget::generateRow()
+ */
+ protected function addRowSectionCell($row, $code, $data)
+ {
+ $sectionEditHelper = $this->getHelperClass(AdminSectionEditHelper::className());
+ if (!isset($this->sectionFields[$code]['WIDGET'])) {
+ $error = str_replace('#CODE#', $code, 'Can\'t create widget for the code "#CODE#"');
+ throw new Exception($error, Exception::CODE_NO_WIDGET);
+ }
+
+ /**
+ * @var \DigitalWand\AdminHelper\Widget\HelperWidget $widget
+ */
+ $widget = $this->sectionFields[$code]['WIDGET'];
+
+ $widget->setHelper($this);
+ $widget->setCode($code);
+ $widget->setData($data);
+ $widget->setEntityName($sectionEditHelper::getModel());
+
+ $this->setContext(AdminListHelper::OP_ADD_ROW_CELL);
+ $widget->generateRow($row, $data);
+ }
+
+ /**
+ * Возвращает массив со списком действий при клике правой клавишей мыши на строке таблицы
+ * По-умолчанию:
+ *
+ *
Редактировать элемент
+ *
Удалить элемент
+ *
Если это всплывающее окно - запустить кастомную JS-функцию.
+ *
+ *
+ * @param $data Данные текущей строки.
+ * @param $section Признак списка для раздела.
+ *
+ * @return array
+ *
+ * @see CAdminListRow::AddActions
+ *
+ * @api
+ */
+ protected function getRowActions($data, $section = false)
+ {
+ $actions = array();
+
+ if ($this->isPopup()) {
+ $jsData = \CUtil::PhpToJSObject($data);
+ $actions['select'] = array(
+ 'ICON' => 'select',
+ 'DEFAULT' => true,
+ 'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SELECT'),
+ "ACTION" => 'javascript:' . $this->popupClickFunctionName . '(' . $jsData . ')'
+ );
+ }
+ else {
+ $viewQueryString = 'module=' . static::getModule() . '&view=' . static::getViewName() . '&entity=' . static::getEntityCode();
+ $query = array_merge($this->additionalUrlParams,
+ array($this->pk() => $data[$this->pk()]));
+ if ($this->hasWriteRights()) {
+ $sectionHelperClass = static::getHelperClass(AdminSectionEditHelper::className());
+ $editHelperClass = static::getHelperClass(AdminEditHelper::className());
+
+ $actions['edit'] = array(
+ 'ICON' => 'edit',
+ 'DEFAULT' => true,
+ 'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_EDIT'),
+ 'ACTION' => $this->list->ActionRedirect($section ? $sectionHelperClass::getUrl($query) : $editHelperClass::getUrl($query))
+ );
+ }
+ if ($this->hasDeleteRights()) {
+ $actions['delete'] = array(
+ 'ICON' => 'delete',
+ 'TEXT' => Loc::getMessage("DIGITALWAND_ADMIN_HELPER_LIST_DELETE"),
+ 'ACTION' => "if(confirm('" . Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_CONFIRM') . "')) " . $this->list->ActionDoGroup($data[$this->pk()],
+ $section ? "delete-section" : "delete", $viewQueryString)
+ );
+ }
+ }
+
+ return $actions;
+ }
+
+ /**
+ * Для каждой ячейки таблицы создаёт виджет соответствующего типа. Виджет подготавливает необходимый HTML-код
+ * для списка.
+ *
+ * @param \CAdminListRow $row Объект строки списка записей.
+ * @param string $code Сивольный код поля.
+ * @param array $data Данные текущей строки.
+ * @param bool $virtualCode
+ *
+ * @throws Exception
+ *
+ * @see HelperWidget::generateRow()
+ */
+ protected function addRowCell($row, $code, $data, $virtualCode = false)
+ {
+ $widget = $this->createWidgetForField($code, $data);
+ $this->setContext(AdminListHelper::OP_ADD_ROW_CELL);
+
+ // устанавливаем виртуальный код ячейки, используется при слиянии столбцов
+ if ($virtualCode) {
+ $widget->setCode($virtualCode);
+ }
+
+ $widget->generateRow($row, $data);
+
+ if ($virtualCode) {
+ $widget->setCode($code);
+ }
+ }
+
+ /**
+ * Производит выборку данных. Функцию стоит переопределить в случае, если необходима своя логика, и её нельзя
+ * вынести в класс модели.
+ *
+ * @param DataManager $className
+ * @param array $filter
+ * @param array $select
+ * @param array $sort
+ * @param array $raw
+ *
+ * @return Result
+ *
+ * @api
+ */
+ protected function getData($className, $filter, $select, $sort, $raw)
+ {
+ $parameters = array(
+ 'filter' => $filter,
+ 'select' => $select,
+ 'order' => $sort
+ );
+
+ /** @var Result $res */
+ $res = $className::getList($parameters);
+
+ return $res;
+ }
+
+ /**
+ * Подготавливает массив с настройками футера таблицы Bitrix
+ * @param \CAdminResult $res - результат выборки данных
+ * @see \CAdminList::AddFooter()
+ * @return array[]
+ */
+ protected function getFooter($res)
+ {
+ return array(
+ $this->getButton('MAIN_ADMIN_LIST_SELECTED', array("value" => $res->SelectedRowsCount())),
+ $this->getButton('MAIN_ADMIN_LIST_CHECKED', array("value" => $res->SelectedRowsCount()), array(
+ "counter" => true,
+ "value" => "0",
+ )),
+ );
+ }
+
+ /**
+ * Выводит форму фильтрации списка
+ */
+ public function createFilterForm()
+ {
+ $this->setContext(AdminListHelper::OP_CREATE_FILTER_FORM);
+ print ' ';
+ }
+
+ /**
+ * Возвращает ID таблицы, который не должен конфликтовать с ID в других разделах админки, а также нормально
+ * парситься в JS
+ *
+ * @return string
+ */
+ protected function getListTableID()
+ {
+ return str_replace('.', '', static::$tablePrefix . $this->table());
+ }
+
+ /**
+ * Выводит сформированный список.
+ * Сохраняет обработанный GET-запрос в сессию
+ */
+ public function show()
+ {
+ if (!$this->hasReadRights()) {
+ $this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_ACCESS_FORBIDDEN'));
+ $this->showMessages();
+
+ return false;
+ }
+ $this->showMessages();
+ $this->list->DisplayList();
+
+ if ($this->isPopup()) {
+ print $this->popupClickFunctionCode;
+ }
+
+ $this->saveGetQuery();
+ }
+
+ /**
+ * Сохраняет параметры запроса для поторного использования после возврата с других страниц (к примеру, после
+ * перехода с детальной обратно в список - чтобы вернуться в точности в тот раздел, с которого ранее ушли)
+ */
+ private function saveGetQuery()
+ {
+ $_SESSION['LAST_GET_QUERY'][get_called_class()] = $_GET;
+ }
+
+ /**
+ * Восстанавливает последний GET-запрос, если в текущем задан параметр restore_query=Y
+ */
+ private function restoreLastGetQuery()
+ {
+ if (!isset($_SESSION['LAST_GET_QUERY'][get_called_class()])
+ OR !isset($_REQUEST['restore_query'])
+ OR $_REQUEST['restore_query'] != 'Y'
+ ) {
+ return;
+ }
+
+ $_GET = array_merge($_GET, $_SESSION['LAST_GET_QUERY'][get_called_class()]);
+ $_REQUEST = array_merge($_REQUEST, $_SESSION['LAST_GET_QUERY'][get_called_class()]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getUrl(array $params = array())
+ {
+ return static::getViewURL(static::getViewName(), static::$listPageUrl, $params);
+ }
}
\ No newline at end of file
diff --git a/lib/helper/AdminSectionEditHelper.php b/lib/helper/AdminSectionEditHelper.php
new file mode 100644
index 0000000..4119c12
--- /dev/null
+++ b/lib/helper/AdminSectionEditHelper.php
@@ -0,0 +1,22 @@
+
+ * @author Artem Yarygin
+ */
+class AdminSectionEditHelper extends AdminEditHelper
+{
+}
\ No newline at end of file
diff --git a/lib/helper/AdminSectionListHelper.php b/lib/helper/AdminSectionListHelper.php
new file mode 100644
index 0000000..783113b
--- /dev/null
+++ b/lib/helper/AdminSectionListHelper.php
@@ -0,0 +1,17 @@
+
+ * @author Artem Yarygin
+ */
+class AdminSectionListHelper extends AdminListHelper
+{
+}
\ No newline at end of file
diff --git a/lib/helper/Exception.php b/lib/helper/Exception.php
index 41b0801..6feb913 100644
--- a/lib/helper/Exception.php
+++ b/lib/helper/Exception.php
@@ -4,7 +4,7 @@
class Exception extends \Exception
{
- const CODE_NO_WIDGET = 1;
- const CODE_NO_HL_ENTITY_INFORMATION = 2;
+ const CODE_NO_WIDGET = 1;
+ const CODE_NO_HL_ENTITY_INFORMATION = 2;
}
\ No newline at end of file
diff --git a/lib/widget/CheckboxWidget.php b/lib/widget/CheckboxWidget.php
index d262b78..f8ca73b 100644
--- a/lib/widget/CheckboxWidget.php
+++ b/lib/widget/CheckboxWidget.php
@@ -1,4 +1,5 @@
true
+ );
+
+ /**
+ * @inheritdoc
+ */
+ protected function getEditHtml()
{
- $checked = $this->getValue() == 'Y' ? 'checked' : '';
+ $html = '';
+
+ $modeType = $this->getCheckboxType();
+
+ switch ($modeType) {
+ case static::TYPE_STRING: {
+ $checked = $this->getValue() == self::TYPE_STRING_YES ? 'checked' : '';
+
+ $html = '';
+ $html .= '';
+ break;
+ }
+ case static::TYPE_INT:
+ case static::TYPE_BOOLEAN: {
+ $checked = $this->getValue() == self::TYPE_INT_YES ? 'checked' : '';
+
+ $html = '';
+ $html .= '';
+ break;
+ }
+ }
- return '';
+ return $html;
}
/**
- * Генерирует HTML для поля в списке
- *
- * @see AdminListHelper::addRowCell();
- *
- * @param \CAdminListRow $row
- * @param array $data - данные текущей строки
- *
- * @return mixed
+ * @inheritdoc
*/
- public function genListHTML(&$row, $data)
+ public function generateRow(&$row, $data)
{
+ $modeType = $this->getCheckboxType();
+
+ $globalYes = '';
+ $globalNo = '';
+
+ switch ($modeType) {
+ case self::TYPE_STRING: {
+ $globalYes = self::TYPE_STRING_YES;
+ $globalNo = self::TYPE_STRING_NO;
+ break;
+ }
+ case self::TYPE_INT:
+ case self::TYPE_BOOLEAN: {
+ $globalYes = self::TYPE_INT_YES;
+ $globalNo = self::TYPE_INT_NO;
+ break;
+ }
+ }
+
if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {
- $checked = intval($this->getValue() == 'Y') ? 'checked' : '';
- $js = 'var input = document.getElementsByName(\''.$this->getEditableListInputName().'\')[0];
- input.value = this.checked ? \'Y\' : \'N\';';
- $editHtml
- = '
+ $checked = intval($this->getValue() == $globalYes) ? 'checked' : '';
+ $js = 'var input = document.getElementsByName(\'' . $this->getEditableListInputName() . '\')[0];
+ input.value = this.checked ? \'' . $globalYes . '\' : \'' . $globalNo . '\';';
+ $editHtml = '
';
+ value="' . static::prepareToTagAttr($this->getValue()) . '"
+ name="' . $this->getEditableListInputName() . '" />';
$row->AddEditField($this->getCode(), $editHtml);
}
- $value = $this->getValueReadonly();
- $row->AddViewField($this->getCode(), $value);
-
- }
+ if (intval($this->getValue() == $globalYes)) {
+ $value = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES');
+ } else {
+ $value = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');
+ }
- protected function getValueReadonly()
- {
- return intval($this->getValue() == 'Y') ? Loc::getMessage('CHECKBOX_YES') : Loc::getMessage('CHECKBOX_NO');
+ $row->AddViewField($this->getCode(), $value);
}
/**
- * Генерирует HTML для поля фильтрации
- *
- * @see AdminListHelper::createFilterForm();
- * @return mixed
+ * @inheritdoc
*/
- public function genFilterHTML()
+ public function showFilterHtml()
{
- print '
';
- print '
'.$this->getSettings('TITLE').'
';
- print '
';
- print '
';
+ print $filterHtml;
}
- public function getValue()
+ /**
+ * @inheritdoc
+ */
+ public function getValueReadonly()
{
- $rawValue = parent::getValue();
- if (!is_string($rawValue)) {
- return $this->toString($rawValue);
+ $code = $this->getCode();
+ $value = isset($this->data[$code]) ? $this->data[$code] : null;
+ $modeType = $this->getCheckboxType();
+
+ switch ($modeType) {
+ case static::TYPE_STRING: {
+ $value = $value == 'Y' ? Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES') : Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');
+ break;
+ }
+ case static::TYPE_INT:
+ case static::TYPE_BOOLEAN: {
+ $value = $value ? Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES') : Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');
+ break;
+ }
}
- return $rawValue;
+ return static::prepareToOutput($value);
}
- public static function toInt($stringValue)
+ /**
+ * @inheritdoc
+ */
+ public function processEditAction()
{
- return $stringValue == 'Y' ? 1 : 0;
- }
+ parent::processEditAction();
- public static function toString($boolValue)
- {
- return $boolValue ? 'Y' : 'N';
+ if ($this->getCheckboxType() === static::TYPE_BOOLEAN) {
+ $this->data[$this->getCode()] = (bool) $this->data[$this->getCode()];
+ }
}
- public function processEditAction()
+ /**
+ * Получить тип чекбокса по типу поля.
+ *
+ * @return mixed
+ */
+ public function getCheckboxType()
{
- parent::processEditAction();
- if(!isset($this->data[$this->getCode()])){
- $this->data[$this->getCode()] = 'N';
+ $fieldType = '';
+ $entity = $this->getEntityName();
+ $entityMap = $entity::getMap();
+ $columnName = $this->getCode();
+
+ if (!isset($entityMap[$columnName])) {
+ foreach ($entityMap as $field) {
+ if ($field->getColumnName() === $columnName) {
+ $fieldType = $field->getDataType();
+ break;
+ }
+ }
+ } else {
+ $fieldType = $entityMap[$columnName]['data_type'];
}
+
+ return $fieldType;
}
}
\ No newline at end of file
diff --git a/lib/widget/ComboBoxWidget.php b/lib/widget/ComboBoxWidget.php
index 9676882..8ca829b 100644
--- a/lib/widget/ComboBoxWidget.php
+++ b/lib/widget/ComboBoxWidget.php
@@ -2,12 +2,23 @@
namespace DigitalWand\AdminHelper\Widget;
+use Bitrix\Main\Localization\Loc;
+
+Loc::loadMessages(__FILE__);
+
/**
- * Class ComboBoxWidget Выпадающий список
+ * Выпадающий список.
+ *
* Доступные опции:
*
*
STYLE - inline-стили
- *
VARIANTS - массив с вариантами занчений или функция для их получения
+ *
VARIANTS - массив с вариантами значений или функция для их получения в формате ключ=>заголовок
+ * Например:
+ * [
+ * 1=>'Первый пункт',
+ * 2=>'Второй пункт'
+ * ]
+ *
*
DEFAULT_VARIANT - ID варианта по-умолчанию
*
*/
@@ -18,41 +29,114 @@ class ComboBoxWidget extends HelperWidget
);
/**
- * Генерирует HTML для редактирования поля
+ * @inheritdoc
+ *
* @see AdminEditHelper::showField();
+ *
* @param bool $forFilter
+ *
* @return mixed
*/
- protected function genEditHTML($forFilter = false)
+ protected function getEditHtml($forFilter = false)
{
$style = $this->getSettings('STYLE');
+ $multiple = $this->getSettings('MULTIPLE');
+ $multipleSelected = array();
+
+ if ($multiple) {
+ $multipleSelected = $this->getMultipleValue();
+ }
- $name = $forFilter ? $this->getFilterInputName() : $this->getEditInputName();
- $result = "";
$variants = $this->getVariants();
- $default = $this->getValue();
- if (is_null($default)) {
- $default = $this->getSettings('DEFAULT_VARIANT');
+
+ if (empty($variants)) {
+ $result = 'Не удалось получить данные для выбора';
+ } else {
+ $name = $forFilter ? $this->getFilterInputName() : $this->getEditInputName();
+ $result = "";
+
+ if (!$multiple) {
+ $variantEmpty = array(
+ '' => array(
+ 'ID' => '',
+ 'TITLE' => Loc::getMessage('COMBO_BOX_LIST_EMPTY')
+ )
+ );
+ $variants = $variantEmpty + $variants;
+ }
+
+ $default = $this->getValue();
+
+ if (is_null($default)) {
+ $default = $this->getSettings('DEFAULT_VARIANT');
+ }
+
+ foreach ($variants as $id => $data) {
+ $name = strlen($data["TITLE"]) > 0 ? $data["TITLE"] : "";
+ $selected = false;
+
+ if ($multiple) {
+ if (in_array($id, $multipleSelected)) {
+ $selected = true;
+ }
+ } else {
+ if ($id == $default) {
+ $selected = true;
+ }
+ }
+
+ $result .= "";
+ }
+
+ $result .= "";
}
- foreach ($variants as $id => $name) {
- $result .= "";
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function processEditAction()
+ {
+ if ($this->getSettings('MULTIPLE')) {
+ $sphere = $this->data[$this->getCode()];
+ unset($this->data[$this->getCode()]);
+
+ foreach ($sphere as $sphereKey) {
+ $this->data[$this->getCode()][] = array('VALUE' => $sphereKey);
+ }
}
- $result .= "";
+ parent::processEditAction();
+ }
- return $result;
+ /**
+ * @inheritdoc
+ */
+ protected function getMultipleEditHtml()
+ {
+ return $this->getEditHtml();
}
+ /**
+ * @inheritdoc
+ */
protected function getValueReadonly()
{
$variants = $this->getVariants();
- $value = $variants[$this->getValue()];
- return $value;
+ $value = $variants[$this->getValue()]['TITLE'];
+
+ return static::prepareToOutput($value);
}
/**
- * Возвращает массив в формате
+ * Возвращает массив в следующем формате:
*
* array(
* '123' => array('ID' => 123, 'TITLE' => 'ololo'),
@@ -60,52 +144,92 @@ protected function getValueReadonly()
* '789' => array('ID' => 789, 'TITLE' => 'pish-pish'),
* )
*
- * Результат будет выводиться в комбобоксе
+ *
+ * Результат будет выводиться в комбобоксе.
* @return array
*/
protected function getVariants()
{
$variants = $this->getSettings('VARIANTS');
- if (is_callable($variants)) {
- $var = call_user_func($variants);
+ if (is_array($variants) AND !empty($variants)) {
+ return $this->formatVariants($variants);
+ } else if (is_callable($variants)) {
+ $var = $variants();
if (is_array($var)) {
- return $var;
+ return $this->formatVariants($var);
}
- } else if (is_array($variants) AND !empty($variants)) {
- return $variants;
}
return array();
}
/**
- * Генерирует HTML для поля в списке
- * @see AdminListHelper::addRowCell();
- * @param \CAdminListRow $row
- * @param array $data - данные текущей строки
- * @return mixed
+ * Приводит варианты к нужному формату, если они заданы в виде одномерного массива.
+ *
+ * @param $variants
+ *
+ * @return array
*/
- public function genListHTML(&$row, $data)
+ protected function formatVariants($variants)
{
- if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {
- $variants = $this->getVariants();
- $row->AddSelectField($this->getCode(), $variants, array('style' => 'width:90%'));
+ $formatted = array();
+
+ foreach ($variants as $id => $data) {
+ if (!is_array($data)) {
+ $formatted[$id] = array(
+ 'ID' => $id,
+ 'TITLE' => $data
+ );
+ }
+ }
+
+ return $formatted;
+ }
+ /**
+ * @inheritdoc
+ */
+ public function generateRow(&$row, $data)
+ {
+ if ($this->settings['EDIT_IN_LIST'] AND !$this->settings['READONLY']) {
+ $row->AddInputField($this->getCode(), array('style' => 'width:90%'));
} else {
$row->AddViewField($this->getCode(), $this->getValueReadonly());
}
}
/**
- * Генерирует HTML для поля фильтрации
- * @see AdminListHelper::createFilterForm();
- * @return mixed
+ * @inheritdoc
*/
- public function genFilterHTML()
+ public function showFilterHtml()
{
print '
';
print '
' . $this->getSettings('TITLE') . '
';
- print '
' . $this->genEditHTML(true) . '
';
+ print '
' . $this->getEditHtml(true) . '
';
print '
';
}
+
+ /**
+ * @inheritdoc
+ */
+ protected function getMultipleValueReadonly()
+ {
+ $variants = $this->getVariants();
+ $values = $this->getMultipleValue();
+ $result = '';
+
+ if (empty($variants)) {
+ $result = 'Не удалось получить данные для выбора';
+ } else {
+ foreach ($variants as $id => $data) {
+ $name = strlen($data["TITLE"]) > 0 ? $data["TITLE"] : "";
+
+ if (in_array($id, $values)) {
+ $result .= static::prepareToOutput($name) . ' ';
+ }
+ }
+ }
+
+ return $result;
+ }
}
\ No newline at end of file
diff --git a/lib/widget/DateTimeWidget.php b/lib/widget/DateTimeWidget.php
index b3d824c..437474a 100644
--- a/lib/widget/DateTimeWidget.php
+++ b/lib/widget/DateTimeWidget.php
@@ -2,74 +2,76 @@
namespace DigitalWand\AdminHelper\Widget;
-use Bitrix\Main\Type\Date;
-use Bitrix\Main\Type\DateTime;
-
class DateTimeWidget extends HelperWidget
{
- /**
- * Генерирует HTML для редактирования поля
- * @see AdminEditHelper::showField();
- * @return mixed
- */
- protected function genEditHTML()
- {
- ob_start();
- global $APPLICATION;
- $APPLICATION->IncludeComponent("bitrix:main.calendar", "",
- array(
- "SHOW_INPUT" => "Y",
- "FORM_NAME" => "",
- "INPUT_NAME" => $this->getEditInputName(),
- "INPUT_VALUE" => $this->getValue(),
- "INPUT_VALUE_FINISH" => "",
- "SHOW_TIME" => "Y",
- "HIDE_TIMEBAR" => "N"
- )
- );
- $html = ob_get_contents();
- ob_end_clean();
-
- return $html;
- }
-
- /**
- * Генерирует HTML для поля в списке
- * @see AdminListHelper::addRowCell();
- * @param \CAdminListRow $row
- * @param array $data - данные текущей строки
- * @return mixed
- */
- public function genListHTML(&$row, $data)
- {
- if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {
- $row->AddCalendarField($this->getCode());
- } else {
- $row->AddViewField($this->getCode(), $this->getValue());
- }
- }
+ /**
+ * Генерирует HTML для редактирования поля
+ * @see AdminEditHelper::showField();
+ * @return mixed
+ */
+ protected function getEditHtml()
+ {
+ return \CAdminCalendar::CalendarDate($this->getEditInputName(), ConvertTimeStamp(strtotime($this->getValue()), "FULL"), 10, true);
+ }
- /**
- * Генерирует HTML для поля фильтрации
- * @see AdminListHelper::createFilterForm();
- * @return mixed
- */
- public function genFilterHTML()
- {
- list($inputNameFrom, $inputNameTo) = $this->getFilterInputName();
+ /**
+ * Генерирует HTML для поля в списке
+ * @see AdminListHelper::addRowCell();
+ * @param CAdminListRow $row
+ * @param array $data - данные текущей строки
+ * @return mixed
+ */
+ public function generateRow(&$row, $data)
+ {
+ if (isset($this->settings['EDIT_IN_LIST']) AND $this->settings['EDIT_IN_LIST'])
+ {
+ $row->AddCalendarField($this->getCode());
+ }
+ else
+ {
+ $arDate = ParseDateTime($this->getValue());
- print '
HIDE_WHEN_CREATE - скрывает поле в форме редактирования, если создаётся новый элемент, а не открыт
* существующий на редактирование.
- *
TITLE - название поля. Будет использовано в фильтре, заголовке таблицы и в качестве подписи поля на
- * странице редактирования
+ *
TITLE - название поля. Если не задано то возьмется значение title из DataManager::getMap()
+ * через getField($code)->getTitle(). Будет использовано в фильтре, заголовке таблицы и в качестве подписи поля
+ * на
+ * странице редактирования.
*
REQUIRED - является ли поле обязательным.
*
READONLY - поле нельзя редактировать, предназначено только для чтения
*
FILTER - позволяет указать способ фильтрации по полю. В базовом классе возможен только вариант "BETWEEN"
@@ -59,12 +56,91 @@
* выводиться данные из нескольких полей сразу.
*
EDIT_IN_LIST - параметр не обрабатывается непосредственно виджетом, однако используется хэлпером.
* Указывает, можно ли редактировать данное поле в спискке
+ *
MULTIPLE - bool является ли поле множественным
+ *
MULTIPLE_FIELDS - bool поля используемые в хранилище множественных значений и их алиасы
+ *
+ *
+ * Как сделать виджет множественным?
+ *
+ *
Реализуйте метод genMultipleEditHTML(). Метод должен выводить множественную форму ввода. Для реализации формы
+ * ввода есть JS хелпер HelperWidget::jsHelper()
+ *
Опишите поля, которые будут переданы связи в EntityManager. Поля описываются в настройке "MULTIPLE_FIELDS"
+ * виджета. По умолчанию множественный виджет использует поля ID, ENTITY_ID, VALUE
+ *
Полученные от виджета данные будут переданы в EntityManager и сохранены как связанные данные
*
+ * Пример реализации можно увидеть в виджете StringWidget
+ *
+ * Как использовать множественный виджет?
+ *
+ *
+ * Создайте таблицу и модель, которая будет хранить данные поля
+ * - Таблица обязательно должна иметь поля, которые требует виджет.
+ * Обязательные поля виджета по умолчанию описаны в: HelperWidget::$settings['MULTIPLE_FIELDS']
+ * Если у виджета нестандартный набор полей, то они хранятся в: SomeWidget::$settings['MULTIPLE_FIELDS']
+ * - Если поля, которые требует виджет есть в вашей таблице, но они имеют другие названия,
+ * можно настроить виджет для работы с вашими полями.
+ * Для этого переопределите настройку MULTIPLE_FIELDS при объявлении поля в интерфейсе следующим способом:
+ * ```
+ * 'RELATED_LINKS' => array(
+ * 'WIDGET' => new StringWidget(),
+ * 'TITLE' => 'Ссылки',
+ * 'MULTIPLE' => true,
+ * // Обратите внимание, именно тут переопределяются поля виджета
+ * 'MULTIPLE_FIELDS' => array(
+ * 'ID', // Должны быть прописаны все поля, даже те, которые не нужно переопределять
+ * 'ENTITY_ID' => 'NEWS_ID', // ENTITY_ID - поле, которое требует виджет, NEWS_ID - пример поля, которое
+ * будет использоваться вместо ENTITY_ID
+ * 'VALUE' => 'LINK', // VALUE - поле, которое требует виджет, LINK - пример поля, которое будет
+ * использоваться вместо VALUE
+ * )
+ * ),
+ * ```
+ *
+ *
+ *
+ * Далее в основной модели (та, которая указана в AdminBaseHelper::$model) нужно прописать связь с моделью,
+ * в которой вы хотите хранить данные поля
+ * Пример объявления связи:
+ * ```
+ * new Entity\ReferenceField(
+ * 'RELATED_LINKS',
+ * 'namespace\NewsLinksTable',
+ * array('=this.ID' => 'ref.NEWS_ID'),
+ * // Условия FIELD и ENTITY не обязательны, подробности смотрите в комментариях к классу @see EntityManager
+ * 'ref.FIELD' => new DB\SqlExpression('?s', 'NEWS_LINKS'),
+ * 'ref.ENTITY' => new DB\SqlExpression('?s', 'news'),
+ * ),
+ * ```
+ *