[
  {
    "path": ".gitignore",
    "content": "/.idea\n/composer.lock"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright © 2015 DigitalWand (http://digitalwand.ru/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# digitalwand.admin_helper\nAPI для сборки кастомных админок в Битриксе\n\nДокументация по модулю доступна по адресу [http://api.digitalwand.ru/admin_helper/](http://api.digitalwand.ru/admin_helper/). Её же можно прочитать в комментариях в коде модуля. \n\nЕсть хорошая вводная статья в блоге: [Генератор админок «Битрикса»](http://samokhvalov.info/blog/all/bitrix-admin-helper/).\n\nПростой рабочий пример реализован отдельным модулем \n[demo.adminhelper](https://github.com/DigitalWand/demo.adminhelper)\n\n\n## Концепция\nДанный модуль реализует подход MVC для создания административного интерфейса.\n\nВозможность построения административного интерфейса появляется благодаря наличию единого API для CRUD-операциями над\nсущностями. Поэтому построение админ. интерфейса средствами данного модуля возможно только для классов, реализующих\nAPI ORM Битрикс. При желании использовать данный модуль для сущностей, не использующих ORM Битрикс, можно\nподготовить для таких сущностей класс-обёртку, реализующий необходимые функции.\n\nОсновные понятия модуля:\n<ul>\n<li>Модель: \"model\" в терминах MVC. Класс, унаследованный от DataManager или реализующий аналогичный API.</li>\n<li>Хэлпер: \"view\" в терминах MVC. Класс, реализующий отрисовку интерфейса списка или детальной страницы.</li>\n<li>Роутер: \"controller\" в терминах MVC. Файл, принимающий все запросы к админке данного модуля, создающий нужные\nхэлперы с нужными настройками. С ним напрямую работать не придётся.</li>\n<li>Виджеты: \"delegate\" в терминах MVC. Классы, отвечающие за отрисовку элементов управления для отдельных полей\nсущностей. В списке и на детальной.</li>\n</ul>\n\nСхема работы с модулем следующая:\n<ul>\n<li>Реализация класса AdminListHelper - для управления страницей списка элементов</li>\n<li>Реализация класса AdminEditHelper - для управления страницей просмотра/редактирования элемента</li>\n<li>Создание файла Interface.php с вызовом AdminBaseHelper::setInterfaceSettings(), в которую передается\nконфигурация\nполей админки и классы, используемые для её построения.</li>\n<li>Если не хватает возможностей виджетов, идущих с модулем, можно реализовать свой виджет, унаследованный от любого\nдругого готового виджета или от абстрактного класса HelperWidget</li>\n</ul>\n\nРекомендуемая файловая структура для модулей, использующих данный функционал:\n<ul>\n<li>Каталог <b>admin</b>. Достаточно поместить в него файл menu.php, отдельные файлы для списка и детальной\nсоздавать не надо благодаря единому роутингу.</li>\n<li>Каталог <b>classes</b> (или lib): содержит классы модели, представлений и делегатов.</li>\n<li> -- <b>classes/helper</b>: каталог, содержащий классы \"view\", унаследованные от AdminListHelper и\nAdminEditHelper.</li>\n<li> -- <b>classes/widget</b>: каталог, содержащий виджеты (\"delegate\"), если для модуля пришлось создавать\nсвои.</li>\n<li> -- <b>classes/model</b>: каталог с моделями, если пришлось переопределять поведение стандартных функций getList\nи т.д.</li>\n</ul>\n\nИспользовать данную структуру не обязательно, это лишь рекомендация, основанная на успешном опыте применения модуля\nв ряде проектов.\n\n## Разработчики\n\n<ul>\n<li><a href=\"http://digitalwand.ru/\">DigitalWand</a></li>\n<li><a href=\"http://nota.media/\">Notamedia</a></li>\n</ul>\n"
  },
  {
    "path": "admin/route.php",
    "content": "<?php\n\nuse Bitrix\\Main\\Loader;\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminListHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminEditHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminInterface;\n\nrequire_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_before.php');\n\nLoader::includeModule('digitalwand.admin_helper');\n\nfunction getRequestParams($param)\n{\n\tif (!isset($_REQUEST[$param])) {\n\t\treturn false;\n\t}\n\telse {\n\t\treturn htmlspecialcharsbx($_REQUEST[$param]);\n\t}\n}\n\n$module = getRequestParams('module');\n$view = getRequestParams('view');\n$entity = getRequestParams('entity');\n\nif (!$module OR !$view OR !Loader::IncludeModule($module)) {\n\tinclude $_SERVER['DOCUMENT_ROOT'] . BX_ROOT . '/admin/404.php';\n}\n\n// Собираем имя класса админского интерфейса\n$moduleNameParts = explode('.', $module);\n$entityNameParts = explode('_', $entity);\n$interfaceNameParts = array_merge($moduleNameParts, $entityNameParts);\n$interfaceNameClass = null;\n$viewParts = explode('_', $view);\n\n$count = count($viewParts);\nfor ($i = 0; $i < $count; $i++) {\n\t$interfaceName = implode('', array_map('ucfirst', $viewParts));\n\t$parts = $interfaceNameParts;\n\t$parts[] = $interfaceName . 'AdminInterface';\n\t$class = array_map('ucfirst', $parts);\n\t$interfaceNameClass = implode('\\\\', $class);\n\n\tif (class_exists($interfaceNameClass)) {\n\t\tbreak;\n\t}\n\telse {\n\t\t$className = array_pop($parts);\n\t\t$parts[] = 'AdminInterface';\n\t\t$parts[] = $className;\n\t\t$class = array_map('ucfirst', $parts);\n\t\t$interfaceNameClass = implode('\\\\', $class);\n\t\tif (class_exists($interfaceNameClass)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\tarray_pop($viewParts);\n}\n\n/**\n * @var AdminInterface $interfaceNameClass\n */\n\nif ($interfaceNameClass && class_exists($interfaceNameClass)) {\n\t$interfaceNameClass::register();\n}\n\nlist($helper, $interface) = AdminBaseHelper::getGlobalInterfaceSettings($module, $view);\n\nif (!$helper OR !$interface) {\n\tinclude $_SERVER['DOCUMENT_ROOT'] . BX_ROOT . '/admin/404.php';\n}\n\n$isPopup = isset($_REQUEST['popup']) AND $_REQUEST['popup'] == 'Y';\n$fields = isset($interface['FIELDS']) ? $interface['FIELDS'] : array();\n$tabs = isset($interface['TABS']) ? $interface['TABS'] : array();\n$helperType = false;\n\nif (is_subclass_of($helper, 'DigitalWand\\AdminHelper\\Helper\\AdminEditHelper')) {\n\t$helperType = 'edit';\n\t/**\n\t * @var AdminEditHelper $adminHelper\n\t */\n\t$adminHelper = new $helper($fields, $tabs);\n}\nelseif (is_subclass_of($helper, 'DigitalWand\\AdminHelper\\Helper\\AdminListHelper')) {\n\t$helperType = 'list';\n\t/**\n\t * @var AdminListHelper $adminHelper\n\t */\n\t$adminHelper = new $helper($fields, $isPopup);\n\t$adminHelper->buildList(array($by => $order));\n}\nelseif (is_subclass_of($helper, 'DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper')) {\n\t$adminHelper = new $helper($fields, $tabs);\n}\nelse {\n\tinclude $_SERVER['DOCUMENT_ROOT'] . BX_ROOT . '/admin/404.php';\n\texit();\n}\n\nif ($isPopup) {\n\trequire($_SERVER[\"DOCUMENT_ROOT\"] . \"/bitrix/modules/main/include/prolog_popup_admin.php\");\n}\nelse {\n\trequire($_SERVER[\"DOCUMENT_ROOT\"] . \"/bitrix/modules/main/include/prolog_admin_after.php\");\n}\n\nif ($helperType == 'list') {\n\t$adminHelper->createFilterForm();\n}\n\n$adminHelper->show();\n\nif ($isPopup) {\n\trequire($_SERVER[\"DOCUMENT_ROOT\"] . \"/bitrix/modules/main/include/epilog_popup_admin.php\");\n}\nelse {\n\trequire($_SERVER[\"DOCUMENT_ROOT\"] . \"/bitrix/modules/main/include/epilog_admin.php\");\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"digitalwand/digitalwand.admin_helper\",\n  \"description\": \"API for custom admin interface in Bitrix by DigitalWand and Notamedia agency\",\n  \"type\": \"bitrix-module\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"bitrix\",\n    \"admin\",\n    \"api\"\n  ],\n  \"support\": {\n    \"issues\": \"https://github.com/DigitalWand/digitalwand.admin_helper/issues\",\n    \"source\": \"https://github.com/DigitalWand/digitalwand.admin_helper\"\n  },\n  \"authors\": [\n    {\n      \"name\": \"Alexey Volkov\",\n      \"email\": \"asgalex@gmail.com\"\n    },\n    {\n      \"name\": \"Nik Samokhvalov\",\n      \"homepage\": \"http://samokhvalov.info\",\n      \"email\": \"nik@samokhvalov.info\"\n    }\n  ],\n  \"extra\": {\n    \"installer-name\": \"digitalwand.admin_helper\"\n  },\n  \"require\": {\n    \"php\": \">=5.3.0\",\n    \"composer/installers\": \"~1\"\n  },\n  \"minimum-stability\": \"dev\"\n}\n"
  },
  {
    "path": "include.php",
    "content": "<?php\r\n\r\nuse Bitrix\\Main\\Loader;\r\n\r\nLoader::registerAutoLoadClasses('digitalwand.admin_helper',\r\n    array(\r\n        'DigitalWand\\AdminHelper\\EventHandlers' => 'lib/EventHandlers.php',\r\n\r\n        'DigitalWand\\AdminHelper\\Helper\\Exception' => 'lib/helper/Exception.php',\r\n\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminInterface' => 'lib/helper/AdminInterface.php',\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper' => 'lib/helper/AdminBaseHelper.php',\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminListHelper' => 'lib/helper/AdminListHelper.php',\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminSectionListHelper' => 'lib/helper/AdminSectionListHelper.php',\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminEditHelper' => 'lib/helper/AdminEditHelper.php',\r\n        'DigitalWand\\AdminHelper\\Helper\\AdminSectionEditHelper' => 'lib/helper/AdminSectionEditHelper.php',\r\n\r\n        'DigitalWand\\AdminHelper\\EntityManager' => 'lib/EntityManager.php',\r\n        'DigitalWand\\AdminHelper\\Sorting' => 'lib/Sorting.php',\r\n\r\n        'DigitalWand\\AdminHelper\\Widget\\HelperWidget' => 'lib/widget/HelperWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\CheckboxWidget' => 'lib/widget/CheckboxWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\ComboBoxWidget' => 'lib/widget/ComboBoxWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\StringWidget' => 'lib/widget/StringWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\NumberWidget' => 'lib/widget/NumberWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\FileWidget' => 'lib/widget/FileWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\TextAreaWidget' => 'lib/widget/TextAreaWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\HLIBlockFieldWidget' => 'lib/widget/HLIBlockFieldWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\DateTimeWidget' => 'lib/widget/DateTimeWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\IblockElementWidget' => 'lib/widget/IblockElementWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\UrlWidget' => 'lib/widget/UrlWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\VisualEditorWidget' => 'lib/widget/VisualEditorWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\UserWidget' => 'lib/widget/UserWidget.php',\r\n        'DigitalWand\\AdminHelper\\Widget\\OrmElementWidget' => 'lib/widget/OrmElementWidget.php',\r\n    )\r\n);\r\n"
  },
  {
    "path": "install/admin/admin_helper_route.php",
    "content": "<?\r\nif (!@include_once $_SERVER[\"DOCUMENT_ROOT\"] . \"/bitrix/modules/digitalwand.admin_helper/admin/route.php\") {\r\n    if (!@include_once $_SERVER[\"DOCUMENT_ROOT\"] . \"/local/modules/digitalwand.admin_helper/admin/route.php\") {\r\n        include $_SERVER['DOCUMENT_ROOT'] . '/bitrix/admin/404.php';\r\n    }\r\n}\r\n"
  },
  {
    "path": "install/index.php",
    "content": "<?php\r\n\r\nuse Bitrix\\Main\\Localization\\Loc;\r\n\r\nLoc::loadMessages(__FILE__);\r\n\r\nif (class_exists('digitalwand_admin_helper')) return;\r\n\r\nclass digitalwand_admin_helper extends CModule\r\n{\r\n    var $MODULE_ID = 'digitalwand.admin_helper';\r\n    var $MODULE_VERSION;\r\n    var $MODULE_VERSION_DATE;\r\n    var $MODULE_NAME;\r\n    var $MODULE_DESCRIPTION;\r\n    var $MODULE_GROUP_RIGHTS = 'Y';\r\n    var $MODULE_CSS;\r\n    var $PARTNER_NAME = 'DigitalWand & Notamedia';\r\n    var $PARTNER_URI = '';\r\n\r\n    function digitalwand_admin_helper()\r\n    {\r\n        include __DIR__ . '/version.php';\r\n\r\n        $this->MODULE_VERSION = ADMIN_HELPER_VERSION;\r\n        $this->MODULE_VERSION_DATE = ADMIN_HELPER_VERSION_DATE;\r\n        $this->MODULE_NAME = Loc::getMessage('ADMIN_HELPER_INSTALL_NAME');\r\n        $this->MODULE_DESCRIPTION = Loc::getMessage('ADMIN_HELPER_INSTALL_DESCRIPTION');\r\n    }\r\n\r\n    function DoInstall()\r\n    {\r\n\r\n        $eventManager = \\Bitrix\\Main\\EventManager::getInstance();\r\n\r\n        RegisterModule($this->MODULE_ID);\r\n        $this->InstallFiles();\r\n\r\n        $eventManager->registerEventHandler(\r\n            'main',\r\n            'OnPageStart',\r\n            $this->MODULE_ID,\r\n            '\\DigitalWand\\AdminHelper\\EventHandlers',\r\n            'onPageStart'\r\n        );\r\n    }\r\n\r\n    function DoUninstall()\r\n    {\r\n        $eventManager = \\Bitrix\\Main\\EventManager::getInstance();\r\n\r\n        UnRegisterModule($this->MODULE_ID);\r\n\r\n        $eventManager->unRegisterEventHandler(\r\n            'main',\r\n            'OnPageStart',\r\n            $this->MODULE_ID,\r\n            '\\DigitalWand\\AdminHelper\\EventHandlers',\r\n            'onPageStart'\r\n        );\r\n    }\r\n\r\n    function InstallFiles()\r\n    {\r\n        CopyDirFiles(__DIR__ . '/admin', $_SERVER['DOCUMENT_ROOT'] . '/bitrix/admin');\r\n\r\n        return true;\r\n    }\r\n}"
  },
  {
    "path": "install/version.php",
    "content": "<?\r\ndefine('ADMIN_HELPER_VERSION', '2.0.0');\r\ndefine('ADMIN_HELPER_VERSION_DATE', '2016-01-22');\r\n?>\r\n"
  },
  {
    "path": "lang/ru/install/index.php",
    "content": "<?php\n$MESS['ADMIN_HELPER_INSTALL_NAME'] = 'API AdminHelper';\n$MESS['ADMIN_HELPER_INSTALL_DESCRIPTION'] = 'API для построения административного интерфейса для highload-инфоблоков';\n$MESS['ADMIN_HELPER_INSTALL_TITLE'] = 'Установка модуля \"digitalwand.admin_helper\"';\n$MESS['ADMIN_HELPER_INSTALL_COMPLETE_OK'] = 'Установка завершена.';\n$MESS['ADMIN_HELPER_INSTALL_BACK'] = 'Вернуться в управление модулями';\n$MESS['ADMIN_HELPER_UNINSTALL_COMPLETE'] = 'Удаление завершено.';"
  },
  {
    "path": "lang/ru/lib/EntityManager.php",
    "content": "<?php\n$MESS['DIGITALWAND_AH_RELATION_SHOULD_BE_MULTIPLE_FIELD'] = \"Связь должна быть множественным полем\";\n$MESS['DIGITALWAND_AH_ARGUMENT_CANT_CONTAIN_ID'] = \"Аргумент %A% не может содержать идентификатор элемента\";\n$MESS['DIGITALWAND_AH_ARGUMENT_SHOULD_CONTAIN_ID'] = \"Аргумент %A% должен содержать идентификатор элемента\";\n"
  },
  {
    "path": "lang/ru/lib/helper/AdminBaseHelper.php",
    "content": "<?php\n\n$MESS['DIGITALWAND_ADMIN_HELPER_GETMODEL_EXCEPTION'] = 'Попыка обращение к несуществующему HL-инфоблоку #CLASS#';\n$MESS['DIGITALWAND_ADMIN_HELPER_ACCESS_FORBIDDEN'] = 'Доступ запрещен';"
  },
  {
    "path": "lang/ru/lib/helper/AdminEditHelper.php",
    "content": "<?php\n$MESS['DEFAULT_TAB'] = 'Элемент';\n$MESS['DIGITALWAND_ADMIN_HELPER_RETURN_TO_LIST'] = 'Список';\n$MESS['DELETE'] = 'Удалить';\n$MESS['VALIDATION_ERROR'] = \"Не заполнены обязательные поля:\\n#FIELD_LIST#\";\n$MESS['VALIDATION_ERROR_FIELDS'] = \"Не заполнены обязательные поля:\";\n$MESS['DIGITALWAND_ADMIN_HELPER_NEW_ELEMENT'] = \"Новый элемент\";\n$MESS['DIGITALWAND_ADMIN_HELPER_EDIT_TITLE'] = \"Элемент #ID#\";\n$MESS['DIGITALWAND_ADMIN_HELPER_EDIT_DELETE_CONFIRM'] = \"Удалить запись?\";\n$MESS['DIGITALWAND_ADMIN_HELPER_ACTIONS'] = \"Действия\";\n$MESS['DIGITALWAND_ADMIN_HELPER_ADD_ELEMENT'] = \"Добавить элемент\";\n$MESS['DIGITALWAND_ADMIN_HELPER_DELETE_ELEMENT'] = \"Удалить элемент\";\n$MESS['DIGITALWAND_ADMIN_HELPER_EDIT_DELETE_FORBIDDEN'] = 'Не хватает прав доступа для удаления элемента';\n$MESS['DIGITALWAND_ADMIN_HELPER_EDIT_WRITE_FORBIDDEN'] = 'Не хватает прав доступа для изменения элемента';\n"
  },
  {
    "path": "lang/ru/lib/helper/AdminListHelper.php",
    "content": "<?php\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_CHECKED'] = 'Выбрано:';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_EDIT'] = 'Редактировать';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_DELETE'] = 'Удалить';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_ACTIVE'] = 'Активировать';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_DEACTIVATE'] = 'Деактивировать';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_DELETE_CONFIRM'] = 'Удалить выбранные записи?';\n$MESS['DIGITALWAND_ADMIN_HELPER_SAVE_ERROR'] = 'Ошибка сохранения элемента';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_CREATE_NEW'] = 'Создать элемент';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_CREATE_NEW_SECTION'] = 'Создать раздел';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_SECTION_UP'] = 'На раздел выше';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_SECTION_DELETE'] = 'Удалить раздел';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_SELECT'] = 'Выбрать';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_DELETE_FORBIDDEN'] = 'Не хватает прав доступа для удаления элемента';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_WRITE_FORBIDDEN'] = 'Не хватает прав доступа для изменения элемента';\n$MESS['DIGITALWAND_ADMIN_HELPER_LIST_SECTION_HELPER_NOT_FOUND'] = 'Хелпер для раздела не найден';"
  },
  {
    "path": "lang/ru/lib/widget/CheckboxWidget.php",
    "content": "<?php\n\n$MESS['DIGITALWAND_AH_CHECKBOX_YES'] = 'Да';\n$MESS['DIGITALWAND_AH_CHECKBOX_NO'] = 'Нет';"
  },
  {
    "path": "lang/ru/lib/widget/ComboBoxWidget.php",
    "content": "<?php\r\n\r\n$MESS['DIGITALWAND_AH_COMBO_BOX_LIST_EMPTY'] = 'Не выбрано';\r\n$MESS['DIGITALWAND_AH_MISSING_VARIANTS'] = 'Не удалось получить данные для выбора';"
  },
  {
    "path": "lang/ru/lib/widget/FileWidget.php",
    "content": "<?php\n\n$MESS['DIGITALWAND_AH_FAIL_ADD_FILE'] = 'Не удалось добавить файл #FILE_NAME#';"
  },
  {
    "path": "lang/ru/lib/widget/HelperWidget.php",
    "content": "<?php\n\n$MESS['DIGITALWAND_AH_REQUIRED_FIELD_ERROR'] = 'Обязательное поле \"#FIELD#\" не заполнено';\n$MESS['DIGITALWAND_AH_DUPLICATE_FIELD_ERROR'] = 'Значение поля \"#FIELD#\" не является уникальным';\n$MESS['DIGITALWAND_AH_MULTI_NOT_SUPPORT'] = 'Поле не поддерживает множественный режим';\n$MESS['DIGITALWAND_AH_MULTI_ADD'] = \"Добавить...\";"
  },
  {
    "path": "lang/ru/lib/widget/IblockElementWidget.php",
    "content": "<?php\r\n$MESS['IBLOCK_ELEMENT_NOT_FOUND'] = 'Элемент не найден';"
  },
  {
    "path": "lang/ru/lib/widget/NumberWidget.php",
    "content": "<?php\n$MESS['VALUE_IS_NOT_NUMERIC'] = 'Поле #FIELD# должно содержать число';"
  },
  {
    "path": "lang/ru/lib/widget/OrmElementWidget.php",
    "content": "<?php\n\n$MESS['TITLE_FIELD_NAME'] = 'Элемент не найден';\n$MESS['DIGITALWAND_AH_ORM_MISSING_ELEMENTS'] = 'Элементы не найдены';"
  },
  {
    "path": "lang/ru/lib/widget/UrlWidget.php",
    "content": "<?php\r\n$MESS['PROTOCOL_REQUIRED'] = 'Укажите протокол ссылки \"#FIELD#\" (например http://)';"
  },
  {
    "path": "lib/EntityManager.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse Bitrix\\Main\\ArgumentException;\nuse Bitrix\\Main\\Entity\\DataManager;\nuse Bitrix\\Main\\Entity;\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\nuse DigitalWand\\AdminHelper\\Widget\\HelperWidget;\n\n/**\n * Менеджер для управления моделью, способный анализировать её связи и сохранять данные в связанные сущности на\n * основании полученных данных от виджетов.\n *\n * Пример создания сущности:\n * ```\n * $filmManager = new EntityManager('\\Vendor\\Module\\FilmTable', array(\n *        // Данные сущности\n *        'TITLE' => 'Монстры на каникулах 2',\n *        'YEAR' => 2015,\n *        // У сущности FilmTable есть связь с RelatedLinksTable через поле RELATED_LINKS.\n *        // Если передать ей данные, то они будут обработаны\n *        // Представим, что у сущности RelatedLinksTable есть поля ID и VALUE (в этом поле хранится ссылка), FILM_ID\n *        // В большинстве случаев, данные передаваемые связям генерируются множественными виджетами\n *        'RELATED_LINKS' => array(\n *            // Переданный ниже массив будет обработан аналогично коду RelatedLinksTable::add(array('VALUE' =>\n * 'yandex.ru')); array('VALUE' => 'yandex.ru'),\n *            // Если в массив добавить ID, то запись обновится: RelatedLinksTable::update(3, array('ID' => 3, 'VALUE'\n * => 'google.com')); array('ID' => 3, 'VALUE' => 'google.com'),\n *            // ВНИМАНИЕ: данный класс руководствуется принципом: что передано для связи, то сохранится или обновится,\n * что не передано, будет удалено\n *            // То есть, если в поле связи RELATED_LINKS передать пустой массив, то все значения связи будут удалены\n *        )\n * ));\n * $filmManager->save();\n * ```\n *\n * Пример удаления сущности\n * ```\n * $articleManager = new EntityManager('\\Vendor\\Module\\ArticlesTable', array(), 7, $adminHelper);\n * $articleManager->delete();\n * ```\n *\n * Как работает сохранение данных? Дополнительный пример\n * Допустим, что есть модели NewsTable (новости) и NewsLinksTable (ссылки на дополнительную информацию о новости)\n *\n * У модели NewsTable есть связь с моделью NewsLinksTable через поле NEWS_LINKS:\n * ```\n * DataManager::getMap() {\n * ...\n * new Entity\\ReferenceField(\n *        'NEWS_LINKS',\n *        '\\Vendor\\Module\\NewsLinksTable',\n *        array('=this.ID' => 'ref.NEWS_ID'),\n *        'ref.FIELD' => new DB\\SqlExpression('?s', 'NEWS_LINKS'),\n *        'ref.ENTITY' => new DB\\SqlExpression('?s', 'news'),\n * ),\n * ...\n * }\n * ```\n *\n * Попробуем сохранить\n * ```\n * $newsManager = new EntityManager(\n *        '\\Vendor\\Module\\NewsTable',\n *        array(\n *            'TITLE' => 'News title',\n *            'CONTENT' => 'News content',\n *            'NEWS_LINKS' => array(\n *                array('LINK' => 'test.ru'),\n *                array('LINK' => 'test2.ru'),\n *                array('ID' => 'id ссылки', 'LINK' => 'test3.ru'),\n *            )\n *        ),\n *        null,\n *        $adminHelper\n * );\n * $newsManager->save();\n * ```\n *\n * В данном примере передаются данные для новости (заголовок и содержимое) и данные для поля-связи NEWS_LINKS.\n *\n * Далее EntityManager:\n * 1. Вырезает данные, которые предназначены связям\n * 2. Подставляет в них данные из основной модели на основе условий связи\n * Например для связи с полем NEWS_LINKS подставятся данные:\n *\n * ```\n * NewsLinksTable::ENTITY_ID => NewsTable::ID,\n * NewsLinksTable::FIELD => 'NEWS_LINKS',\n * NewsLinksTable::ENTITY => 'news'\n * ```\n *\n * 3. После подстановки данных они будут переданы модели связи подобно коду ниже:\n *\n * ```\n * NewsLinksTable::add(array(\n *          'ENTITY' => 'news',\n *          'FIELD' => 'NEWS_LINKS',\n *          'ENTITY_ID' => 'id сущности, например новости',\n *          'LINK' => 'test.ru'\n * ));\n * NewsLinksTable::add(array(\n *      'ENTITY' => 'news',\n *      'FIELD' => 'NEWS_LINKS',\n *      'ENTITY_ID' => 'id сущности',\n *      'LINK' => 'test2.ru'\n * ));\n * NewsLinksTable::update('id ссылки', array(\n *      'ENTITY' => 'news',\n *      'FIELD' => 'NEWS_LINKS',\n *      'ENTITY_ID' => 'id сущности',\n *      'LINK' => 'test3.ru'\n * ));\n * ```\n *\n * Обратите внимание, что в метод EntityManager::save() были изначально передано только поле LINK, поля ENTITY,\n * ENTITY_ID и FIELD были подставлены классом EntityManager автоматически (предыдущий пункт) А так же важно, что для\n * третьей ссылки был передан идентификатор, поэтому выполнился NewsLinksTable::update, а не NewsLinksTable::add\n *\n * 4. Далее `EntityManager` удаляет данные связанной модели `NewsLinksTable`, которые не были добавлены или обновлены.\n *\n * <b>Как работает удаление?</b>\n *\n * 1. EntityManager получает из `NewsTable::getMap()` поля-связи\n * 2. Получает поля описанные в интерфейсе генератора админки\n * 3. Удаляет значения для полей-связей, которые объявлены в интерфейсе\n *\n * <i>Примечание.</i>\n * EntityManager управляет только данными, которые получает при помощи связи стандартными средставами битрикса.\n * Например, при удалении NewsTable будут удалены только NewsLinksTable, где:\n *\n * ```\n * NewsLinksTable::ENTITY_ID => NewsTable::ID,\n * NewsLinksTable::FIELD => 'NEWS_LINKS',\n * NewsLinksTable::ENTITY => 'news'\n * ```\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Dmitriy Baibuhtin <dmitriy.baibuhtin@ya.ru>\n */\nclass EntityManager\n{\n\t/**\n\t * @var string Класс модели.\n\t */\n\tprotected $modelClass;\n\t/**\n\t * @var Entity\\Base Сущность модели.\n\t */\n\tprotected $model;\n\t/**\n\t * @var array Данные для обработки.\n\t */\n\tprotected $data;\n\t/**\n\t * @var integer Идентификатор записи.\n\t */\n\tprotected $itemId = null;\n\t/**\n\t * @var string Поле модели, в котором хранится идентификатор записи.\n\t */\n\tprotected $modelPk = null;\n\t/**\n\t * @var array Данные для связей (новые).\n\t */\n\tprotected $referencesData = array();\n\t/**\n\t * Вспомогательный массив метода $this->ReferenceDataSet()\n\t * @var array Данные для связей (то чт уже было в базе).\n\t */\n\tprotected $referenceDataSave = array();\n\t/**\n\t * @var AdminBaseHelper Хелпер.\n\t */\n\tprotected $helper;\n\t/**\n\t * @var array Предупреждения.\n\t */\n\tprotected $notes = array();\n\n\t/**\n\t * @param string $modelClass Класс основной модели, наследника DataManager.\n\t * @param array $data Массив с сохраняемыми данными.\n\t * @param integer $itemId Идентификатор сохраняемой записи.\n\t * @param AdminBaseHelper $helper Хелпер, инициирующий сохранение записи.\n\t */\n\tpublic function __construct($modelClass, array $data = array(), $itemId = null, AdminBaseHelper $helper)\n\t{\n\t\tLoc::loadMessages(__FILE__);\n\t\t\n\t\t$this->modelClass = $modelClass;\n\t\t$this->model = $modelClass::getEntity();\n\t\t$this->data = $data;\n\t\t$this->modelPk = $this->model->getPrimary();\n\t\t$this->helper = $helper;\n\n\t\tif (!empty($itemId)) {\n\t\t\t$this->setItemId($itemId);\n\t\t}\n\t}\n\n    /**\n     * Сохранить запись и данные связей.\n     *\n     * @return Entity\\AddResult|Entity\\UpdateResult\n     */\n    public function save()\n    {\n        $this->collectReferencesData();\n\n        /**\n         * @var DataManager $modelClass\n         */\n        $modelClass = $this->modelClass;\n\t\t$db = $this->model->getConnection();\n\t\t$db->startTransaction(); // начало транзакции\n\n\t\tif (empty($this->itemId)) {\n\t\t\t$result = $modelClass::add($this->data);\n\n\t\t\tif ($result->isSuccess()) {\n\t\t\t\t$this->setItemId($result->getId());\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t$result = $modelClass::update($this->itemId, $this->data);\n\t\t}\n\n        if ($result->isSuccess()) {\n\t\t\t$referencesDataResult = $this->processReferencesData();\n\t\t\tif($referencesDataResult->isSuccess()){\n\t\t\t\t$db->commitTransaction(); // ошибок нет - применяем изменения\n\t\t\t}else{\n\t\t\t\t$result = $referencesDataResult; // возвращаем ReferencesResult что бы вернуть ошибку\n\t\t\t\t$db->rollbackTransaction(); // что-то пошло не так - возвращаем все как было\n\t\t\t}\n\t\t} else {\n\t\t\t$db->rollbackTransaction();\n\t\t}\n\n\t\treturn $result;\n\t}\n\n    /**\n     * Удаление запись и данные связей.\n     *\n     * @return Entity\\DeleteResult\n     */\n    public function delete()\n    {\n        // Удаление данных зависимостей\n\t\t$db = $this->model->getConnection();\n\t\t$db->startTransaction(); // начало транзакции\n\n\t\t$result = $this->deleteReferencesData(); // удаляем зависимые сущности\n\n\t\tif(!$result->isSuccess()){ // если хотя бы одна из них не удалилась\n\t\t\t$db->rollbackTransaction(); // то восстанавливаем все\n\t\t\treturn $result; // возвращаем ошибку\n\t\t}\n\n\t\t$model = $this->modelClass;\n\n\t\t//Если передается массив, то получаем ИД записи\n        if (!is_null($this->itemId)) {\n            $result = $model::delete($this->itemId); // удаляем основную сущность\n        } elseif (!is_array($this->helper->getPk())) {\n            $result = $model::delete($this->helper->getPk()); // удаляем основную сущность\n        } else {\n            $result = new Entity\\DeleteResult();\n            $error = new Entity\\EntityError('Can\\'t find element\\'s ID');\n            $result->addError($error);\n        }\n\n\t\tif(!$result->isSuccess()){  // если не удалилась\n\t\t\t$db->rollbackTransaction(); // то восстанавливаем зависимые сущности\n\t\t\treturn $result; // возвращаем ошибку\n\t\t}\n\n\t\t$db->commitTransaction(); // все прошло без ошибок применяем изменения\n\t\treturn $result;\n    }\n\n\t/**\n\t * Получить список предупреждений\n\t * @return array\n\t */\n\tpublic function getNotes()\n\t{\n\t\treturn $this->notes;\n\t}\n\n\t/**\n\t * Добавить предупреждение\n\t *\n\t * @param $note\n\t * @param string $key Ключ для избежания дублирования сообщений\n\t *\n\t * @return bool\n\t */\n\tprotected function addNote($note, $key = null)\n\t{\n\t\tif ($key) {\n\t\t\t$this->notes[$key] = $note;\n\t\t}\n\t\telse {\n\t\t\t$this->notes[] = $note;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Установка текущего идентификатора модели.\n\t *\n\t * @param integer $itemId Идентификатор записи.\n\t */\n\tprotected function setItemId($itemId)\n\t{\n\t\t$this->itemId = $itemId;\n\t\t$this->data[$this->modelPk] = $this->itemId;\n\t}\n\n\t/**\n\t * Получение связей\n\t *\n\t * @return array\n\t */\n\tprotected function getReferences()\n\t{\n\t\t/**\n\t\t * @var DataManager $modelClass\n\t\t */\n\t\t$modelClass = $this->modelClass;\n\t\t$entity = $modelClass::getEntity();\n\t\t$fields = $entity->getFields();\n\t\t$references = array();\n\n\t\tforeach ($fields as $fieldName => $field) {\n\t\t\tif ($field instanceof Entity\\ReferenceField) {\n\t\t\t\t$references[$fieldName] = $field;\n\t\t\t}\n\t\t}\n\n\t\treturn $references;\n\t}\n\n\t/**\n\t * Извлечение данных для связей\n\t */\n\tprotected function collectReferencesData()\n\t{\n\t\t$result = new Entity\\Result();\n\t\t$references = $this->getReferences();\n\t\t// Извлечение данных управляемых связей\n\t\tforeach ($references as $fieldName => $reference) {\n\t\t\tif (array_key_exists($fieldName, $this->data)) {\n\t\t\t\tif (!is_array($this->data[$fieldName])) {\n\t\t\t\t\t$result->addError(new Entity\\EntityError(Loc::getMessage('DIGITALWAND_AH_RELATION_SHOULD_BE_MULTIPLE_FIELD')));\n\n\t\t\t\t\treturn $result;\n\t\t\t\t}\n\t\t\t\t// Извлечение данных для связи\n\t\t\t\t$this->referencesData[$fieldName] = $this->data[$fieldName];\n\t\t\t\tunset($this->data[$fieldName]);\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n    /**\n     * Обработка данных для связей.\n     *\n     * @throws ArgumentException\n     */\n    protected function processReferencesData()\n    {\n        /**\n         * @var DataManager $modelClass\n         */\n        $modelClass = $this->modelClass;\n        $entity = $modelClass::getEntity();\n        $fields = $entity->getFields();\n\t\t$result = new Entity\\Result(); // пустой Result у которого isSuccess вернет true\n\n\t\tforeach ($this->referencesData as $fieldName => $referenceDataSet) {\n\t\t\tif (!is_array($referenceDataSet)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @var Entity\\ReferenceField $reference\n\t\t\t */\n\t\t\t$reference = $fields[$fieldName];\n\t\t\t$referenceDataSet = $this->linkDataSet($reference, $referenceDataSet);\n\t\t\t$referenceStaleDataSet = $this->getReferenceDataSet($reference);\n\t\t\t$fieldWidget = $this->getFieldWidget($fieldName);\n\t\t\t$variantsField = $fieldWidget->getSettings('VARIANTS');\n\n\t\t\t// Создание и обновление привязанных данных\n\t\t\t$processedDataIds = array();\n\t\t\tforeach ($referenceDataSet as $referenceData) {\n\t\t\t\tif (empty($referenceData[$fieldWidget->getMultipleField('ID')])) {\n\t\t\t\t\t// Создание связанных данных\n\t\t\t\t\tif (!empty($referenceData[$fieldWidget->getMultipleField('VALUE')])) {\n\t\t\t\t\t\t$result = $this->createReferenceData($reference, $referenceData);\n\n                        if ($result->isSuccess()) {\n                            $processedDataIds[] = $result->getId();\n                        } else {\n\t\t\t\t\t\t\tbreak; // ошибка, прерываем обработку данных\n\t\t\t\t\t\t}\n                    }\n                } else {\n                    // Обновление связанных данных\n\t\t\t\t\t$result = $this->updateReferenceData($reference, $referenceData, $referenceStaleDataSet);\n\n                    if ($result->isSuccess()) {\n                        $processedDataIds[] = $referenceData[$fieldWidget->getMultipleField('ID')];\n                    } else {\n\t\t\t\t\t\tbreak; // ошибка, прерываем обработку данных\n\t\t\t\t\t}\n                }\n            }\n\n\t\t\tif($result->isSuccess()){ // Удаление записей, которые не были созданы или обновлены\n\t\t\t\tforeach ($referenceStaleDataSet as $referenceData) {\n\t\t\t\t\tif (\n                        !in_array($referenceData[$fieldWidget->getMultipleField('ID')], $processedDataIds)\n                        OR\n                        ($variantsField\n                            AND !in_array($referenceData[$fieldWidget->getMultipleField('ID')], $processedDataIds)\n                            AND array_key_exists($referenceData[$fieldWidget->getMultipleField('VALUE')], $variantsField))\n\t\t\t\t\t) {\n                        if ($fieldWidget->getSettings('DELETE_REFERENCED_DATA')) {\n                            $result = $this->deleteReferenceData($reference,\n                                $referenceData[$fieldWidget->getMultipleField('ID')]);\n                        } else {\n                            $result = $this->deleteReference($reference,\n                                $referenceData[$fieldWidget->getMultipleField('ID')]);\n                        }\n\t\t\t\t\t\tif(!$result->isSuccess()) {\n\t\t\t\t\t\t\tbreak; // ошибка, прерываем удаление данных\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n        }\n\n        $this->referencesData = array();\n\t\treturn $result;\n    }\n\n    /**\n     * Удаление данных всех связей, которые указаны в полях интерфейса раздела.\n     */\n    protected function deleteReferencesData()\n    {\n        $references = $this->getReferences();\n        $fields = $this->helper->getFields();\n\t\t$result = new Entity\\Result();\n        /**\n         * @var string $fieldName\n         * @var Entity\\ReferenceField $reference\n         */\n        foreach ($references as $fieldName => $reference) {\n            // Удаляются только данные связей, которые объявлены в интерфейсе\n            if (!isset($fields[$fieldName])) {\n                continue;\n            }\n\n\t\t\t$fieldWidget = $this->getFieldWidget($reference->getName());\n\t\t\t$referenceStaleDataSet = $this->getReferenceDataSet($reference);\n\n            foreach ($referenceStaleDataSet as $referenceData) {\n                $result = $this->deleteReferenceData($reference, $referenceData[$fieldWidget->getMultipleField('ID')]);\n\t\t\t\tif(!$result->isSuccess()){\n\t\t\t\t\treturn $result;\n\t\t\t\t}\n            }\n        }\n\t\treturn $result;\n    }\n\n    /**\n     * Создание связанной записи.\n     *\n     * @param Entity\\ReferenceField $reference\n     * @param array $referenceData\n     *\n     * @return \\Bitrix\\Main\\Entity\\AddResult\n     * @throws ArgumentException\n     * @throws \\Exception\n     */\n\tprotected function createReferenceData(Entity\\ReferenceField $reference, array $referenceData)\n\t{\n\t\t$referenceName = $reference->getName();\n\t\t$fieldParams = $this->getFieldParams($referenceName);\n\t\t$fieldWidget = $this->getFieldWidget($referenceName);\n\n\t\tif (!empty($referenceData[$fieldWidget->getMultipleField('ID')])) {\n\t\t\tthrow new ArgumentException(Loc::getMessage('DIGITALWAND_AH_ARGUMENT_CANT_CONTAIN_ID', array('%A%' => 'referenceData')), 'referenceData');\n\t\t}\n\n\t\t$refClass = $reference->getRefEntity()->getDataClass();\n\n        if(isset($referenceData['VALUE']) AND $fieldWidget->getMultipleField('VALUE') != 'VALUE'){\n            $referenceData[$fieldWidget->getMultipleField('VALUE')] = $referenceData['VALUE'];\n            unset($referenceData['VALUE']);\n        }\n\t\t$createResult = $refClass::add($referenceData);\n\n\t\tif (!$createResult->isSuccess()) {\n\t\t\t$this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_SAVE_ERROR',\n\t\t\t\tarray('#FIELD#' => $fieldParams['TITLE'])), 'CREATE_' . $referenceName);\n\t\t}\n\n\t\treturn $createResult;\n\t}\n\n    /**\n     * Обновление связанной записи\n     *\n     * @param Entity\\ReferenceField $reference\n     * @param array $referenceData\n     * @param array $referenceStaleDataSet\n     *\n     * @return Entity\\UpdateResult|null\n     * @throws ArgumentException\n     * @throws \\Exception\n     */\n\tprotected function updateReferenceData(\n\t\tEntity\\ReferenceField $reference,\n\t\tarray $referenceData,\n\t\tarray $referenceStaleDataSet\n\t)\n\t{\n\t\t$referenceName = $reference->getName();\n\t\t$fieldParams = $this->getFieldParams($referenceName);\n\t\t$fieldWidget = $this->getFieldWidget($referenceName);\n\n\t\tif (empty($referenceData[$fieldWidget->getMultipleField('ID')])) {\n\t\t\tthrow new ArgumentException(Loc::getMessage('DIGITALWAND_AH_ARGUMENT_SHOULD_CONTAIN_ID', array('%A%' => 'referenceData')), 'referenceData');\n\t\t}\n\n\t\t// Сравнение старых данных и новых, обновляется только при различиях\n\t\tif ($this->isDifferentData($referenceStaleDataSet[$referenceData[$fieldWidget->getMultipleField('ID')]],\n\t\t\t$referenceData)\n\t\t) {\n\t\t\t$refClass = $reference->getRefEntity()->getDataClass();\n\n\t\t\t$primary = $referenceData[$fieldWidget->getMultipleField('ID')];\n\t\t\tif(isset($referenceData['VALUE']) AND $fieldWidget->getMultipleField('VALUE') != 'VALUE'){\n                $referenceData[$fieldWidget->getMultipleField('VALUE')] = $referenceData['VALUE'];\n                unset($referenceData['VALUE']);\n            }\n\t\t\t$updateResult = $refClass::update($primary, $referenceData);\n\n\t\t\tif (!$updateResult->isSuccess()) {\n\t\t\t\t$this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_SAVE_ERROR',\n\t\t\t\t\tarray('#FIELD#' => $fieldParams['TITLE'])), 'UPDATE_' . $referenceName);\n\t\t\t}\n\n            return $updateResult;\n        } else {\n            return new Entity\\Result(); // пустой Result у которого isSuccess() вернет true\n        }\n    }\n\n    /**\n     * Удаление данных связи.\n     *\n     * @param Entity\\ReferenceField $reference\n     * @param $referenceId\n     *\n     * @return \\Bitrix\\Main\\Entity\\Result\n     * @throws \\Exception\n     */\n\tprotected function deleteReferenceData(Entity\\ReferenceField $reference, $referenceId)\n\t{\n\t\t$refClass = $reference->getRefEntity()->getDataClass();\n\t\t$deleteResult = $refClass::delete($referenceId);\n\n\t\tif (!$deleteResult->isSuccess()) {\n            $fieldParams = $this->getFieldParams($reference->getName());\n\t\t\t$this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_DELETE_ERROR',\n\t\t\t\tarray('#FIELD#' => $fieldParams['TITLE'])), 'DELETE_' . $reference->getName());\n\t\t}\n\n\t\treturn $deleteResult;\n\t}\n\n    /**\n     * Удаление данных связи.\n     *\n     * @param Entity\\ReferenceField $reference\n     * @param $referenceId\n     *\n     * @return \\Bitrix\\Main\\Entity\\Result\n     * @throws \\Exception\n     */\n    protected function deleteReference(Entity\\ReferenceField $reference, $referenceId)\n    {\n        $refClass = $reference->getRefEntity()->getDataClass();\n        $referenceName = $reference->getName();\n        $fieldWidget = $this->getFieldWidget($referenceName);\n        $updateResult = $refClass::update($referenceId, [$fieldWidget->getMultipleField('ENTITY_ID') => null]);\n\n        if (!$updateResult->isSuccess()) {\n            $fieldParams = $this->getFieldParams($reference->getName());\n            $this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_DELETE_ERROR',\n                array('#FIELD#' => $fieldParams['TITLE'])), 'DELETE_' . $reference->getName());\n        }\n\n        return $updateResult;\n    }\n\n    /**\n     * Получение данных связи.\n     *\n     * @param Entity\\ReferenceField $reference\n     *\n     * @return array\n     * @throws ArgumentException\n     */\n\tprotected function getReferenceDataSet(Entity\\ReferenceField $reference)\n\t{\n\t\t/**\n\t\t * @var DataManager $modelClass\n\t\t */\n\t\t$modelClass = $this->modelClass;\n\t\t$dataSet = array();\n\t\t$fieldWidget = $this->getFieldWidget($reference->getName());\n\t\t\n\t\t// Возможно запрос для данного поля уже делалася, получение значения из массива\n\t\tif ($this->referenceDataSave[ $reference->getName() ])\n\t\t\treturn $this->referenceDataSave[ $reference->getName() ];\n\n\t\t$rsData = $modelClass::getList(array(\n\t\t\t'select' => array('REF_' => $reference->getName() . '.*'),\n\t\t\t'filter' => array('=' . $this->modelPk => $this->itemId)\n\t\t));\n\n\t\twhile ($data = $rsData->fetch()) {\n\t\t\tif (empty($data['REF_' . $fieldWidget->getMultipleField('ID')])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$row = array();\n\t\t\tforeach ($data as $key => $value) {\n\t\t\t\t$row[str_replace('REF_', '', $key)] = $value;\n\t\t\t}\n\n            if (!isset($row['VALUE'])) {\n                $row['VALUE'] = $row[$fieldWidget->getMultipleField('VALUE')];\n            }\n\n\t\t\t$dataSet[$data['REF_' . $fieldWidget->getMultipleField('ID')]] = $row;\n\t\t}\n\n\t\t// Сохранить результат, для избежания повторного запроса к базе\n\t\t$this->referenceDataSave[ $reference->getName() ] = $dataSet;\n\n\t\t// Вернуть результат запроса\n\t\treturn $dataSet;\n\t}\n\n\t/**\n\t * В данные связи подставляются данные основной модели используя условия связи моделей из getMap().\n\t *\n\t * @param Entity\\ReferenceField $reference\n\t * @param array $referenceData Данные привязанной модели\n\t *\n\t * @return array\n\t */\n\tprotected function linkData(Entity\\ReferenceField $reference, array $referenceData)\n\t{\n\t\t// Парсим условия связи двух моделей\n\t\t$referenceConditions = $this->getReferenceConditions($reference);\n\n\t\t// Получение ID связанного элемента\n\t\tif ($ID = $this->getLinkDataId($referenceData, $this->getReferenceDataSet($reference)))\n\t\t\t$referenceData['ID'] = $ID;\n\n\t\tforeach ($referenceConditions as $refField => $refValue) {\n\t\t\t// Так как в условиях связи между моделями в основном отношения типа this.field => ref.field или\n\t\t\t// ref.field => SqlExpression, мы можем использовать это для подстановки данных\n\t\t\t// this.field - это поле основной модели\n\t\t\t// ref.field - поле модели из связи\n\t\t\t// customValue - это строка полученная из new SqlExpression('%s', ...)\n\t\t\tif (empty($refValue['thisField'])) {\n\t\t\t\t$referenceData[$refField] = $refValue['customValue'];\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$referenceData[$refField] = $this->data[$refValue['thisField']];\n\t\t\t}\n\t\t}\n\n\t\treturn $referenceData;\n\t}\n\n\t/**\n\t * Связывает набор связанных данных с основной моделю.\n\t *\n\t * @param Entity\\ReferenceField $reference\n\t * @param array $referenceDataSet\n\t *\n\t * @return array\n\t */\n\tprotected function linkDataSet(Entity\\ReferenceField $reference, array $referenceDataSet)\n\t{\n\t\tforeach ($referenceDataSet as $key => $referenceData) {\n\t\t\t$referenceDataSet[$key] = $this->linkData($reference, $referenceData);\n\t\t}\n\n\t\treturn $referenceDataSet;\n\t}\n\t\n\t/**\n\t * Возвращает ID связанного элемента. Для поиска используются ранее полученные данные,\n\t * Что позволяет избежать дополнительных запросов для поиска ID элементов.\n\t * В звязанной таблице имя поля идентификатора всегда 'ID', а имя поля значения всегда 'VALUE'\n\t *\n\t * @param array $referenceDataSet устанавливаемое значение\n\t * @param array $referenceStaleDataSet установленные ранее значения\n\t */\n\tprotected function getLinkDataId(array $referenceDataSet, array $referenceStaleDataSet)\n\t{\n\t\tforeach ($referenceStaleDataSet as $item) {\n\t\t\tif ($item['VALUE'] == $referenceDataSet['VALUE']) {\n\t\t\t\treturn $item['ID'];\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Парсинг условий связи между моделями.\n\t *\n\t * Ничего сложного нет, просто определяются соответствия полей основной модели и модели из связи. Например:\n\t *\n\t * `FilmLinksTable::FILM_ID => FilmTable::ID (ref.FILM_ID => this.ID)`\n\t *\n\t * Или, например:\n\t *\n\t * `MediaTable::TYPE => 'FILM' (ref.TYPE => new DB\\SqlExpression('?s', 'FILM'))`\n\t *\n\t * @param Entity\\ReferenceField $reference Данные поля из getMap().\n\t *\n\t * @return array Условия связи преобразованные в массив вида $conditions[$refField]['thisField' => $thisField,\n\t *     'customValue' => $customValue].\n\t *      $customValue - это результат парсинга SqlExpression.\n\t *      Если шаблон SqlExpression не равен %s, то условие исключается из результата.\n\t */\n\tprotected function getReferenceConditions(Entity\\ReferenceField $reference)\n\t{\n\t\t$conditionsFields = array();\n\n\t\tforeach ($reference->getReference() as $leftCondition => $rightCondition) {\n\t\t\t$thisField = null;\n\t\t\t$refField = null;\n\t\t\t$customValue = null;\n\n\t\t\t// Поиск this.... в левом условии\n\t\t\t$thisFieldMatch = array();\n\t\t\t$refFieldMatch = array();\n\t\t\tif (preg_match('/=this\\.([A-z]+)/', $leftCondition, $thisFieldMatch) == 1) {\n\t\t\t\t$thisField = $thisFieldMatch[1];\n\t\t\t} // Поиск ref.... в левом условии\n\t\t\telse {\n\t\t\t\tif (preg_match('/ref\\.([A-z]+)/', $leftCondition, $refFieldMatch) == 1) {\n\t\t\t\t\t$refField = $refFieldMatch[1];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Поиск expression value... в правом условии\n\t\t\t$refFieldMatch = array();\n\t\t\tif ($rightCondition instanceof \\Bitrix\\Main\\DB\\SqlExpression) {\n\t\t\t\t$customValueDirty = $rightCondition->compile();\n\t\t\t\t$customValue = preg_replace('/^([\\'\"])(.+)\\1$/', '$2', $customValueDirty);\n\t\t\t\tif ($customValueDirty == $customValue) {\n\t\t\t\t\t// Если значение выражения не обрамлено кавычками, значит оно не нужно нам\n\t\t\t\t\t$customValue = null;\n\t\t\t\t}\n\t\t\t} // Поиск ref.... в правом условии\n\t\t\telse {\n\t\t\t\tif (preg_match('/ref\\.([A-z]+)/', $rightCondition, $refFieldMatch) > 0) {\n\t\t\t\t\t$refField = $refFieldMatch[1];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Если не указано поле, которое нужно заполнить или не найдено содержимое для него, то исключаем условие\n\t\t\tif (empty($refField) || (empty($thisField) && empty($customValue))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$conditionsFields[$refField] = array(\n\t\t\t\t\t'thisField' => $thisField,\n\t\t\t\t\t'customValue' => $customValue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn $conditionsFields;\n\t}\n\n\t/**\n\t * Обнаружение отличий массивов\n\t * Метод не сранивает наличие аргументов, сравниваются только значения общих параметров\n\t *\n\t * @param array $data1\n\t * @param array $data2\n\t *\n\t * @return bool\n\t */\n\tprotected function isDifferentData(array $data1 = null, array $data2 = null)\n\t{\n\t    if(is_null($data1)) return true;\n\t\tforeach ($data1 as $key => $value) {\n\t\t\tif (isset($data2[$key]) && $data2[$key] != $value) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param $fieldName\n\t *\n\t * @return array|bool\n\t */\n\tprotected function getFieldParams($fieldName)\n\t{\n\t\t$fields = $this->helper->getFields();\n\n\t\tif (isset($fields[$fieldName]) && isset($fields[$fieldName]['WIDGET'])) {\n\t\t\treturn $fields[$fieldName];\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Получение виджета привязанного к полю.\n\t *\n\t * @param $fieldName\n\t *\n\t * @return HelperWidget|bool\n\t */\n\tprotected function getFieldWidget($fieldName)\n\t{\n\t\t$field = $this->getFieldParams($fieldName);\n\n\t\treturn isset($field['WIDGET']) ? $field['WIDGET'] : null;\n\t}\n}"
  },
  {
    "path": "lib/EventHandlers.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper;\n\nuse Bitrix\\Main\\Context;\nuse Bitrix\\Main\\Loader;\n\n/**\n * Перехватчики событий.\n *\n * Для каждого события, возникающего в системе, которе необходимо отлавливать «Админ-хелпером», создаётся\n * в данном классе одноимённый метод. Метод должен быть зарегистрирован в системе через установщик модуля.\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n */\nclass EventHandlers\n{\n    /**\n     * Автоматическое подключение модуля в админке.\n     *\n     * Таки образом, исключаем необходимость прописывать в генераторах админки своих модулей\n     * подключение «Админ-хелпера».\n     *\n     * @throws \\Bitrix\\Main\\LoaderException\n     */\n    public static function onPageStart()\n    {\n        if (Context::getCurrent()->getRequest()->isAdminSection())\n        {\n            Loader::includeModule('digitalwand.admin_helper');\n        }\n    }\n}"
  },
  {
    "path": "lib/Sorting.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper;\n\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\n\n/**\n * Оригинальный класс сохраняет в сессии выбранную сортировку по страницам админки.\n *\n * Поскольку у admin_helper всего одна страница в админке на все модели и представления,\n * нам необходимо сделать ключ сессии разным для разных классов-хелперов.\n *\n * Так мы сможем иметь свой порядок сортировки у каждого из них.\n *\n * @link:https://github.com/DigitalWand/digitalwand.admin_helper/issues/88\n */\nclass Sorting extends \\CAdminSorting\n{\n\t/** @noinspection PhpMissingParentConstructorInspection */\n\t/**\n\t * Изменения относительно базового конструктора отмечены комментариями\n\t *\n\t * @param string $table_id ID в \\CAdminList\n\t * @param string|bool $by_initial Поле сортировки по умолчанию\n\t * @param string|bool $order_initial Порядок сортирвоки по умолчанию\n\t * @param string $by_name Параметр, содержащий поле сортировки\n\t * @param string $ord_name Параметр, содержащий порядок сортировки\n\t * @param AdminBaseHelper $adminHelper\n\t */\n\tpublic function __construct(\n\t\t$table_id,\n\t\t$by_initial = false,\n\t\t$order_initial = false,\n\t\t$by_name = \"by\",\n\t\t$ord_name = \"order\",\n\t\tAdminBaseHelper $adminHelper = null // добавлено\n\t) {\n\t\t/** @global \\CMain $APPLICATION */\n\t\tglobal $APPLICATION;\n\n\t\t$this->by_name = $by_name;\n\t\t$this->ord_name = $ord_name;\n\t\t$this->table_id = $table_id;\n\t\t$this->by_initial = $by_initial;\n\t\t$this->order_initial = $order_initial;\n\n\t\t$uniq = md5(($adminHelper ? get_class($adminHelper) : '') . '_' . $APPLICATION->GetCurPage()); // изменено\n\n\t\t$aOptSort = array();\n\t\tif(isset($GLOBALS[$this->by_name]))\n\t\t{\n\t\t\t$_SESSION[\"SESS_SORT_BY\"][$uniq] = $GLOBALS[$this->by_name];\n\t\t\t$_SESSION[\"SESS_SORT_BY\"][$uniq] = $GLOBALS[$this->by_name];\n\t\t}\n\t\telseif(isset($_SESSION[\"SESS_SORT_BY\"][$uniq]))\n\t\t{\n\t\t\t$GLOBALS[$this->by_name] = $_SESSION[\"SESS_SORT_BY\"][$uniq];\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$aOptSort = \\CUserOptions::GetOption(\"list\", $this->table_id, array(\"by\"=>$by_initial, \"order\"=>$order_initial));\n\t\t\tif(!empty($aOptSort[\"by\"]))\n\t\t\t\t$GLOBALS[$this->by_name] = $aOptSort[\"by\"];\n\t\t\telseif($by_initial !== false)\n\t\t\t\t$GLOBALS[$this->by_name] = $by_initial;\n\t\t}\n\n\t\tif(isset($GLOBALS[$this->ord_name]))\n\t\t{\n\t\t\t$_SESSION[\"SESS_SORT_ORDER\"][$uniq] = $GLOBALS[$this->ord_name];\n\t\t}\n\t\telseif(isset($_SESSION[\"SESS_SORT_ORDER\"][$uniq]))\n\t\t{\n\t\t\t$GLOBALS[$this->ord_name] = $_SESSION[\"SESS_SORT_ORDER\"][$uniq];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif(empty($aOptSort[\"order\"]))\n\t\t\t\t$aOptSort = \\CUserOptions::GetOption(\"list\", $this->table_id, array(\"order\"=>$order_initial));\n\t\t\tif(!empty($aOptSort[\"order\"]))\n\t\t\t\t$GLOBALS[$this->ord_name] = $aOptSort[\"order\"];\n\t\t\telseif($order_initial !== false)\n\t\t\t\t$GLOBALS[$this->ord_name] = $order_initial;\n\t\t}\n\n\t\t$this->field = $GLOBALS[$this->by_name];\n\t\t$this->order = $GLOBALS[$this->ord_name];\n\t}\n}"
  },
  {
    "path": "lib/helper/AdminBaseHelper.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\nuse Bitrix\\Main\\Entity\\ReferenceField;\nuse Bitrix\\Main\\Loader;\nuse Bitrix\\Main\\LoaderException;\nuse Bitrix\\Main\\Localization\\Loc;\nuse Bitrix\\Main\\ModuleManager;\nuse DigitalWand\\AdminHelper\\EntityManager;\nuse DigitalWand\\AdminHelper\\Widget\\HelperWidget;\nuse Bitrix\\Main\\Entity\\DataManager;\nuse Bitrix\\Highloadblock as HL;\nuse Bitrix\\Main\\Context;\n\nLoader::includeModule('highloadblock');\nLoc::loadMessages(__FILE__);\n\n/**\n * Данный модуль реализует подход MVC для создания административного интерфейса.\n *\n * Возможность построения административного интерфейса появляется благодаря наличию единого API для CRUD-операциями над\n * сущностями. Поэтому построение админ. интерфейса средствами данного модуля возможно только для классов, реализующих\n * API ORM Битрикс. При желании использовать данный модуль для сущностей, не использующих ORM Битрикс, можно\n * подготовить для таких сущностей класс-обёртку, реализующий необходимые функции.\n *\n * Основные понятия модуля:\n * <ul>\n * <li>Мдель: \"model\" в терминах MVC. Класс, унаследованный от DataManager или реализующий аналогичный API.</li>\n * <li>Хэлпер: \"view\" в терминах MVC. Класс, реализующий отрисовку интерфейса списка или детальной страницы.</li>\n * <li>Роутер: \"controller\" в терминах MVC. Файл, принимающий все запросы к админке данного модуля, создающий нужные\n * хэлперы с нужными настройками. С ним напрямую работать не придётся.</li>\n * <li>Виджеты: \"delegate\" в терминах MVC. Классы, отвечающие за отрисовку элементов управления для отдельных полей\n * сущностей. В списке и на детальной.</li>\n * </ul>\n *\n * Схема работы с модулем следующая:\n * <ul>\n * <li>Реализация класса AdminListHelper - для управления страницей списка элементов</li>\n * <li>Реализация класса AdminEditHelper - для управления страницей просмотра/редактирования элемента</li>\n * <li>Реализация класса AdminInterface - для описания конфигурации полей админки и классы интерфейсов</li>\n * <li>Реализация класса AdminSectionListHelper - для описания странице списка разделов(если они используются)</li>\n * <li>Реализация класса AdminSectionEditHelper - для управления страницей просмотра/редактирования раздела(если они используются)</li>\n * <li>Если не хватает возможностей виджетов, идущих с модулем, можно реализовать свой виджет, унаследованный от любого\n * другого готового виджета или от абстрактного класса HelperWidget</li>\n * </ul>\n *\n * Устаревший функционал:\n * <ul>\n * <li>Файл Interface.php с вызовом AdminBaseHelper::setInterfaceSettings(), в который передается\n * конфигурация полей админки и классы.</li>\n *\n * Рекомендуемая файловая структура для модулей, использующих данный функционал:\n * <ul>\n * <li>Каталог <b>admin</b>. Достаточно поместить в него файл menu.php, отдельные файлы для списка и детальной\n * создавать не надо благодаря единому роутингу.</li>\n * <li>Каталог <b>classes</b> (или lib): содержит классы модли, представлений и делегатов.</li>\n * <li> -- <b>classes/admininterface</b>: каталог, содержащий классы \"view\", унаследованные от AdminListHelper,\n * AdminEditHelper, AdminInterface, AdminSectionListHelper и AdminSectionEditHelper.</li>\n * <li> -- <b>classes/widget</b>: каталог, содержащий виджеты (\"delegate\"), если для модуля пришлось создавать\n * свои.</li>\n * <li> -- <b>classes/model</b>: каталог с моделями, если пришлось переопределять поведение стандартынх функций getList\n * и т.д.</li>\n * </ul>\n *\n * Использовать данную структуру не обязательно, это лишь рекомендация, основанная на успешном опыте применения модуля\n * в ряде проектов.\n *\n * Единственное <b>обязательное</b> условие - расположение  всех реализуемых классов админ хелперов и админ интерфейсов\n * в одном неймспейсе\n *\n * При использовании разделов нужно обязательно прописать в модели элементов привязку к модели разделов, например:\n *\n * ```php\n * <?php\n * class ElementModel\n * {\n * \t\tpublic static function getMap()\n *  \t{\n * \t\t\treturn [\n * \t\t\t\t'CATEGORY' => [\n *\t\t\t\t\t'data_type' => 'Vendor\\Module\\CategoryTable',\n *\t\t\t\t\t'reference' => ['=this.CATEGORY_ID' => 'ref.ID'],\n *\t\t\t\t]\n * \t\t\t];\n * \t\t}\n * ```\n *\n * @see AdminInterface::fields()\n * @package AdminHelper\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nabstract class AdminBaseHelper\n{\n\t/**\n\t * @internal\n\t * @var string адрес обработчика запросов к админ. интерфейсу.\n\t */\n\tstatic protected $routerUrl = '/bitrix/admin/admin_helper_route.php';\n\n\t/**\n\t * @var string\n\t * Имя класса используемой модели. Используется для выполнения CRUD-операций.\n\t * При наследовании класса необходимо переопределить эту переменную, указав полное имя класса модели.\n\t *\n\t * @see DataManager\n\t * @api\n\t */\n\tstatic protected $model;\n\n\t/**\n\t * @var string\n\t * Имя класса используемого менеджера сущностей. Используется для выполнения CRUD-операций.\n\t *\n\t * @see DataManager\n\t * @api\n\t */\n\tstatic protected $entityManager = '\\DigitalWand\\AdminHelper\\EntityManager';\n\n\t/**\n\t * @var string\n\t * Назвние модуля данной модели.\n\t * При наследовании класса необходимо указать нзвание модуля, в котором он находится.\n\t * А можно и не указывать, в этому случае он определится автоматически по namespace класса\n\t * Используется для избежания конфликтов между именами представлений.\n\t *\n\t * @api\n\t */\n\tstatic public $module = array();\n\n\t/**\n\t * @var string[]\n\t * Название представления.\n\t * При наследовании класса необходимо указать название представления.\n\t * А можно и не указывать, в этому случае оно определится автоматически по namespace класса.\n\t * Оно будет использовано при построении URL к данному разделу админки.\n\t * Не должно содержать пробелов и других символов, требующих преобразований для\n\t * адресной строки браузера.\n\t *\n\t * @api\n\t */\n\tstatic protected $viewName = array();\n\n\t/**\n\t * @var array\n\t * Настройки интерфейса\n\t * @see AdminBaseHelper::setInterfaceSettings()\n\t * @internal\n\t */\n\tstatic protected $interfaceSettings = array();\n\n\t/**\n\t * @var array\n\t * Привязка класса интерфеса к классу хелпера\n\t */\n\tstatic protected $interfaceClass = array();\n\n\t/**\n\t * @var array\n\t * Хранит список отображаемых полей и настройки их отображения\n\t * @see AdminBaseHelper::setInterfaceSettings()\n\t */\n\tprotected $fields = array();\n\n\t/**\n\t * @var \\CMain\n\t * Замена global $APPLICATION;\n\t */\n\tprotected $app;\n\tprotected $validationErrors = array();\n\n\t/**\n\t * @var string\n\t * Позволяет непосредственно указать адрес страницы списка. Полезно, в случае, если такая станица реализована без\n\t * использования данного модуля. В случае, если поле определено для класса, роутинг не используется.\n\t *\n\t * @see AdminBaseHelper::getListPageUrl\n\t * @api\n\t */\n\tstatic protected $listPageUrl;\n\n\t/**\n\t * @var string\n\t * $viewName представления, отвечающего за страницу списка. Необходимо указывать только для классов, уналедованных\n\t * от AdminEditHelper.\n\t * Необязательное, сгенерируется автоматически если не определено\n\t *\n\t * @see AdminBaseHelper::getViewName()\n\t * @see AdminBaseHelper::getListPageUrl\n\t * @see AdminEditHelper\n\t * @api\n\t */\n\tstatic protected $listViewName;\n\n\t/**\n\t * @var string\n\t * Позволяет непосредственно указать адрес страницы просмотра/редактирования элемента. Полезно, в случае, если\n\t * такая станица реализована без использования данного модуля. В случае, если поле определено для класса,\n\t * роутинг не используется.\n\t *\n\t * @see AdminBaseHelper::getEditPageUrl\n\t * @api\n\t */\n\tstatic protected $editPageUrl;\n\n\t/**\n\t * @var string\n\t * $viewName представления, отвечающего за страницу редактирования/просмотра элемента. Необходимо указывать только\n\t *     для классов, уналедованных от AdminListHelper.\n\t *\n\t * @see AdminBaseHelper::getViewName()\n\t * @see AdminBaseHelper::getEditPageUrl\n\t * @see AdminListHelper\n\t * @api\n\t */\n\tstatic protected $editViewName;\n\n\t/**\n\t * @var string\n\t * Позволяет непосредственно указать адрес страницы просмотра/редактирования раздела. Полезно, в случае, если\n\t * такая станица реализована без использования данного модуля. В случае, если поле определено для класса,\n\t * роутинг не используется.\n\t *\n\t * @see AdminBaseHelper::getEditPageUrl\n\t * @api\n\t */\n\tstatic protected $sectionsEditPageUrl;\n\n\t/**\n\t * @var string\n\t * $viewName представления, отвечающего за страницу редактирования/просмотра раздела. Необходимо указывать только\n\t * для классов, уналедованных от AdminListHelper.\n\t * Необязательное, сгенерируется автоматически если не определено\n\t *\n\t * @see AdminBaseHelper::getViewName()\n\t * @see AdminBaseHelper::getEditPageUrl\n\t * @see AdminListHelper\n\t * @api\n\t */\n\tstatic protected $sectionsEditViewName;\n\n\t/**\n\t * @var array\n\t * Дополнительные параметры URL, которые будут добавлены к параметрам по-умолчанию, генерируемым автоматически\n\t * @api\n\t */\n\tprotected $additionalUrlParams = array();\n\n\t/**\n\t * @var string контекст выполнения. Полезен для информирования виджетов о том, какая операция в настоящий момент\n\t *     производится.\n\t */\n\tprotected $context = '';\n\n\t/**\n\t * Флаг использования разделов, необходимо переопределять в дочернем классе\n\t * @var bool\n\t */\n\tstatic protected $useSections = false;\n\n\t/**\n\t * Правило именования хелперов для разделов по умолчанию\n\t * @var string\n\t */\n\tstatic protected $sectionSuffix = 'Sections';\n\n\t/**\n\t * @param array $fields список используемых полей и виджетов для них\n\t * @param array $tabs список вкладок для детальной страницы\n\t * @param string $module название модуля\n\t */\n\tpublic function __construct(array $fields, array $tabs = array(), $module = \"\")\n\t{\n\t\tglobal $APPLICATION;\n\n\t\t$this->app = $APPLICATION;\n\n\t\t$settings = array(\n\t\t\t'FIELDS' => $fields,\n\t\t\t'TABS' => $tabs\n\t\t);\n\t\tif (static::setInterfaceSettings($settings)) {\n\t\t\t$this->fields = $fields;\n\t\t}\n\t\telse {\n\t\t\t$settings = static::getInterfaceSettings();\n\t\t\t$this->fields = $settings['FIELDS'];\n\t\t}\n\t}\n\n\t/**\n\t * @param string $viewName Имя вьюхи, для которой мы хотим получить натсройки\n\t *\n\t * @return array Возвращает настройки интерфейса для данного класса.\n\t *\n\t * @see AdminBaseHelper::setInterfaceSettings()\n\t * @api\n\t */\n\tpublic static function getInterfaceSettings($viewName = '')\n\t{\n\t\tif (empty($viewName)) {\n\t\t\t$viewName = static::getViewName();\n\t\t}\n\n\t\treturn self::$interfaceSettings[static::getModule()][$viewName]['interface'];\n\t}\n\n\t/**\n\t * Основная функция для конфигурации всего административного интерфейса.\n\t *\n\t * @param array $settings настройки полей и вкладок\n\t * @param array $helpers список классов-хэлперов, используемых для отрисовки админки\n\t * @param string $module название модуля\n\t *\n\t * @return bool false, если для данного класса уже были утановлены настройки\n\t *\n\t * @api\n\t */\n\tpublic static function setInterfaceSettings(array $settings, array $helpers = array(), $module = '')\n\t{\n\t\tforeach ($helpers as $helperClass => $helperSettings) {\n\t\t\tif (!is_array($helperSettings)) { // поддержка старого формата описания хелперов\n\t\t\t\t$helperClass = $helperSettings; // в значении передается класс хелпера а не настройки\n\t\t\t\t$helperSettings = array(); // настроек в старом формате нет\n\t\t\t}\n\t\t\t$success = $helperClass::registerInterfaceSettings($module, array_merge($settings, $helperSettings));\n\t\t\tif (!$success) return false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Привязывает класса хелпера из которого вызывается к интерфесу, используется при получении\n\t * данных об элементах управления из интерфейса.\n\t *\n     * @param $class\n\t */\n\tpublic static function setInterfaceClass($class)\n\t{\n\t\tstatic::$interfaceClass[get_called_class()] = $class;\n\t}\n\n\t/**\n\t * Возвращает класс интерфейса к которому привязан хелпер из которого вызван метод.\n     *\n\t * @return array\n\t */\n\tpublic static function getInterfaceClass()\n\t{\n\t\treturn isset(static::$interfaceClass[get_called_class()]) ? static::$interfaceClass[get_called_class()] : false;\n\t}\n\n\t/**\n\t * Регистрирует настройки интерфейса для текущего хелпера\n\t *\n\t * @param string $module имя текущего модуля\n\t * @param $interfaceSettings\n     *\n\t * @return bool\n\t * @internal\n\t */\n\tpublic static function registerInterfaceSettings($module, $interfaceSettings)\n\t{\n\t\tif (isset(self::$interfaceSettings[$module][static::getViewName()]) || empty($module)\n\t\t\t|| empty($interfaceSettings)\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tself::$interfaceSettings[$module][static::getViewName()] = array(\n\t\t\t'helper' => get_called_class(),\n\t\t\t'interface' => $interfaceSettings\n\t\t);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Получает настройки интерфейса для данного модуля и представления. Используется при роутинге.\n\t * Возвращается массив со следующими ключами:\n\t *\n\t * <ul>\n\t * <li> helper - название класса-хэлпера, который будет рисовать страницу</li>\n\t * <li> interface - настройки интерфейса для хелпера</li>\n\t * </ul>\n\t *\n\t * @param string $module Модуль, для которого нужно получить настройки.\n\t * @param string $view Название представления.\n     *\n\t * @return array\n\t * @internal\n\t */\n\tpublic static function getGlobalInterfaceSettings($module, $view)\n\t{\n\t\tif (!isset(self::$interfaceSettings[$module][$view])) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn array(\n\t\t\tself::$interfaceSettings[$module][$view]['helper'],\n\t\t\tself::$interfaceSettings[$module][$view]['interface'],\n\t\t);\n\t}\n\n\t/**\n     * Возвращает имя текущего представления.\n     *\n\t * @return string\n\t * @api\n\t */\n\tpublic static function getViewName()\n\t{\n\t\tif (!is_array(static::$viewName)) {\n\t\t\treturn static::$viewName;\n\t\t}\n\n\t\t$className = get_called_class();\n\n\t\tif (!isset(static::$viewName[$className])) {\n\t\t\t$classNameParts = explode('\\\\', trim($className, '\\\\'));\n\n\t\t\tif (count($classNameParts) > 2) {\n\t\t\t\t$classCaption = array_pop($classNameParts); // название класса без namespace\n\t\t\t\tpreg_match_all('/((?:^|[A-Z])[a-z]+)/', $classCaption, $matches);\n\t\t\t\t$classCaptionParts = $matches[0];\n\n\t\t\t\tif (end($classCaptionParts) == 'Helper') {\n\t\t\t\t\tarray_pop($classCaptionParts);\n\t\t\t\t}\n\n\t\t\t\tstatic::$viewName[$className] = strtolower(implode('_', $classCaptionParts));\n\t\t\t}\n\t\t}\n\n\t\treturn static::$viewName[$className];\n\t}\n\n\t/**\n\t * Возвращает поле модели которое используется для привязки к разделу из поля с типом совпадающим с классом модели\n\t * раздела.\n\t * @return string\n\t * @throws Exception\n\t */\n\tpublic static function getSectionField()\n\t{\n\t\t$sectionListHelper = static::getHelperClass(AdminSectionListHelper::className());\n\n\t\tif (empty($sectionListHelper))\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\t$sectionModelClass = $sectionListHelper::getModel();\n\t\t$modelClass = static::getModel();\n\n\t\tforeach ($modelClass::getMap() as $field => $data) {\n\t\t\tif ($data instanceof ReferenceField && $data->getDataType() . 'Table' === $sectionModelClass) {\n\t\t\t\treturn str_replace('=this.', '', reset($data->getReference()));\n\t\t\t}\n\t\t\tif (is_array($data) && $data['data_type'] === $sectionModelClass) {\n\t\t\t\treturn str_replace('=this.', '', key($data['reference']));\n\t\t\t}\n\t\t}\n\n\t\tthrow new Exception('References to section model not found');\n\t}\n\n\t/**\n     * Возвращает имя класса используемой модели.\n     *\n\t * @return \\Bitrix\\Main\\Entity\\DataManager|string\n\t *\n\t * @throws \\Bitrix\\Main\\ArgumentException\n\t * @throws \\Bitrix\\Main\\SystemException\n\t * @throws \\Exception\n\t * @api\n\t */\n\tpublic static function getModel()\n\t{\n\t\tif (static::$model) {\n\t\t\treturn static::getHLEntity(static::$model);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Возвращает имя модуля. Если оно не задано, то определяет автоматически из namespace класса.\n     *\n\t * @return string\n     *\n\t * @throws LoaderException\n\t * @api\n\t */\n\tpublic static function getModule()\n\t{\n\t\tif (!is_array(static::$module)) {\n\t\t\treturn static::$module;\n\t\t}\n\n\t\t$className = get_called_class();\n\n\t\tif (!isset(static::$module[$className])) {\n\t\t\t$classNameParts = explode('\\\\', trim($className, '\\\\'));\n\n\t\t\t$moduleNameParts = array();\n\t\t\t$moduleName = false;\n\n\t\t\twhile (count($classNameParts)) {\n\t\t\t\t$moduleNameParts[] = strtolower(array_shift($classNameParts));\n\t\t\t\t$moduleName = implode('.', $moduleNameParts);\n\n\t\t\t\tif (ModuleManager::isModuleInstalled($moduleName)) {\n\t\t\t\t\tstatic::$module[$className] = $moduleName;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (empty($moduleName)) {\n\t\t\t\tthrow new LoaderException('Module name not found');\n\t\t\t}\n\t\t}\n\n\t\treturn static::$module[$className];\n\t}\n\n\t/**\n\t * Возвращает модифцированный массив с описанием элемента управления по его коду. Берет название и настройки\n     * из админ-интерфейса, если они не заданы — используются значения по умолчанию.\n     *\n     * Если элемент управления описан в админ-интерфейсе, то дефолтные настройки и описанные в классе интерфейса\n     * будут совмещены (смержены).\n     *\n\t * @param $code\n\t * @param $params\n\t * @param array $keys\n     *\n\t * @return array|bool\n\t */\n\tprotected function getButton($code, $params, $keys = array('name', 'TEXT'))\n\t{\n\t\t$interfaceClass = static::getInterfaceClass();\n\t\t$interfaceSettings = static::getInterfaceSettings();\n\n\t\tif ($interfaceClass && !empty($interfaceSettings['BUTTONS'])) {\n\t\t\t$buttons = $interfaceSettings['BUTTONS'];\n\n\t\t\tif (is_array($buttons) && isset($buttons[$code])) {\n\t\t\t\tif ($buttons[$code]['VISIBLE'] == 'N') {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\t$params = array_merge($params, $buttons[$code]);\n\n\t\t\t\treturn $params;\n\t\t\t}\n\t\t}\n\n\t\t$text = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_' . $code);\n\n\t\tforeach ($keys as $key) {\n\t\t\t$params[$key] = $text;\n\t\t}\n\n\t\treturn $params;\n\t}\n\n\t/**\n\t * Возвращает список полей интерфейса.\n     *\n\t * @see AdminBaseHelper::setInterfaceSettings()\n     *\n\t * @return array\n     *\n\t * @api\n\t */\n\tpublic function getFields()\n\t{\n\t\treturn $this->fields;\n\t}\n\n\t/**\n\t * Окончательно выводит административную страницу.\n\t */\n\tabstract public function show();\n\n\t/**\n\t * Получает название таблицы используемой модели.\n     *\n\t * @return mixed\n\t */\n\tpublic function table()\n\t{\n\t\t/**\n         * @var DataManager $className\n         */\n\t\t$className = static::getModel();\n\n\t\treturn $className::getTableName();\n\t}\n\n\t/**\n\t * Возвращает первичный ключ таблицы используемой модели\n\t * Для HL-инфоблоков битрикс - всегда ID. Но может поменяться для какой-либо другой сущности.\n\t * @return string\n\t * @api\n\t */\n\tpublic static function pk()\n\t{\n\t\treturn 'ID';\n\t}\n\n\t/**\n\t * Возвращает значение первичного ключа таблицы используемой модели\n\t * @return array|int|null\n\t * \n\t * @api\n\t */\n\tpublic function getPk()\n\t{\n\t\treturn isset($_REQUEST['FIELDS'][$this->pk()]) ? $_REQUEST['FIELDS'][$this->pk()] : $_REQUEST[$this->pk()];\n\t}\n\n\t/**\n\t * Возвращает первичный ключ таблицы используемой модели разделов. Для HL-инфоблоков битрикс - всегда ID.\n     * Но может поменяться для какой-либо другой сущности.\n     *\n\t * @return string\n\t *\n     * @api\n\t */\n\tpublic function sectionPk()\n\t{\n\t\treturn 'ID';\n\t}\n\n\t/**\n\t * Устанавливает заголовок раздела в админке.\n     *\n\t * @param string $title\n\t *\n     * @api\n\t */\n\tpublic function setTitle($title)\n\t{\n\t\t$this->app->SetTitle($title);\n\t}\n\n\t/**\n\t * Функция для обработки дополнительных операций над элементами в админке. Как правило, должно оканчиваться\n     * LocalRedirect после внесения изменений.\n\t *\n\t * @param string $action Название действия.\n\t * @param null|int $id ID элемента.\n     *\n\t * @api\n\t */\n\tprotected function customActions($action, $id = null)\n\t{\n\t\treturn;\n\t}\n\n\t/**\n\t * Выполняется проверка прав на доступ к сущности.\n     *\n\t * @return bool\n\t *\n     * @api\n\t */\n\tprotected function hasRights()\n\t{\n\t\treturn true;\n\t}\n\n\t/**\n\t * Выполняется проверка прав на выполнение операций чтения элементов.\n     *\n\t * @return bool\n\t *\n     * @api\n\t */\n\tprotected function hasReadRights()\n\t{\n\t\treturn true;\n\t}\n\n\t/**\n\t * Выполняется проверка прав на выполнение операций редактирования элементов.\n\t *\n     * @return bool\n     *\n\t * @api\n\t */\n\tprotected function hasWriteRights()\n\t{\n\t\treturn true;\n\t}\n\n\t/**\n\t * Проверка прав на изменение определенного элемента.\n     *\n\t * @param array $element Массив данных элемента.\n     *\n\t * @return bool\n     *\n     * @api\n\t */\n\tprotected function hasWriteRightsElement($element = array())\n\t{\n\t\tif (!$this->hasWriteRights()) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Выполняется проверка прав на выполнение опреаций удаления элементов.\n     *\n\t * @return bool\n     *\n\t * @api\n\t */\n\tprotected function hasDeleteRights()\n\t{\n\t\treturn true;\n\t}\n\n\t/**\n\t * Выводит сообщения об ошибках.\n     *\n\t * @internal\n\t */\n\tprotected function showMessages()\n\t{\n\t\t$allErrors = $this->getErrors();\n\t\t$notes = $this->getNotes();\n\n\t\tif (!empty($allErrors)) {\n\t\t\t$errorList[] = implode(\"\\n\", $allErrors);\n\t\t}\n\t\tif ($e = $this->getLastException()) {\n\t\t\t$errorList[] = trim($e->GetString());\n\t\t}\n\n\t\tif (!empty($errorList)) {\n\t\t\t$errorText = implode(\"\\n\\n\", $errorList);\n\t\t\t\\CAdminMessage::ShowOldStyleError($errorText);\n\t\t}\n\t\telse {\n\t\t\tif (!empty($notes)) {\n\t\t\t\t$noteText = implode(\"\\n\\n\", $notes);\n\t\t\t\t\\CAdminMessage::ShowNote($noteText);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @return bool|\\CApplicationException\n     *\n\t * @internal\n\t */\n\tprotected function getLastException()\n\t{\n\t\tif (isset($_SESSION['APPLICATION_EXCEPTION']) AND !empty($_SESSION['APPLICATION_EXCEPTION'])) {\n\t\t\t/** @var CApplicationException $e */\n\t\t\t$e = $_SESSION['APPLICATION_EXCEPTION'];\n\t\t\tunset($_SESSION['APPLICATION_EXCEPTION']);\n\n\t\t\treturn $e;\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @param $e\n\t */\n\tprotected function setAppException($e)\n\t{\n\t\t$_SESSION['APPLICATION_EXCEPTION'] = $e;\n\t}\n\n\t/**\n\t * Добавляет ошибку или массив ошибок для показа пользователю.\n     *\n\t * @param array|string $errors\n\t *\n     * @api\n\t */\n\tpublic function addErrors($errors)\n\t{\n\t\tif (!is_array($errors)) {\n\t\t\t$errors = array($errors);\n\t\t}\n\n\t\tif (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {\n\t\t\t$_SESSION['ELEMENT_SAVE_ERRORS'] = array_merge($_SESSION['ELEMENT_SAVE_ERRORS'], $errors);\n\t\t}\n\t\telse {\n\t\t\t$_SESSION['ELEMENT_SAVE_ERRORS'] = $errors;\n\t\t}\n\t}\n\n\t/**\n\t * Добавляет уведомление или список уведомлений для показа пользователю.\n     *\n\t * @param array|string $notes\n\t *\n     * @api\n\t */\n\tpublic function addNotes($notes)\n\t{\n\t\tif (!is_array($notes)) {\n\t\t\t$notes = array($notes);\n\t\t}\n\n\t\tif (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {\n\t\t\t$_SESSION['ELEMENT_SAVE_NOTES'] = array_merge($_SESSION['ELEMENT_SAVE_NOTES'],\n\t\t\t\t$notes);\n\t\t}\n\t\telse {\n\t\t\t$_SESSION['ELEMENT_SAVE_NOTES'] = $notes;\n\t\t}\n\t}\n\n\t/**\n\t * @return bool|array\n     *\n\t * @api\n\t */\n\tprotected function getErrors()\n\t{\n\t\tif (isset($_SESSION['ELEMENT_SAVE_ERRORS']) AND !empty($_SESSION['ELEMENT_SAVE_ERRORS'])) {\n\t\t\t$errors = $_SESSION['ELEMENT_SAVE_ERRORS'];\n\t\t\tunset($_SESSION['ELEMENT_SAVE_ERRORS']);\n\n\t\t\treturn $errors;\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @return bool\n     *\n\t * @api\n\t */\n\tprotected function getNotes()\n\t{\n\t\tif (isset($_SESSION['ELEMENT_SAVE_NOTES']) AND !empty($_SESSION['ELEMENT_SAVE_NOTES'])) {\n\t\t\t$notes = $_SESSION['ELEMENT_SAVE_NOTES'];\n\t\t\tunset($_SESSION['ELEMENT_SAVE_NOTES']);\n\n\t\t\treturn $notes;\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Возвращает класс хелпера нужного типа из всех зарегистрированных хелперов в модуле и находящихся\n\t * в том же неймспейсе что класс хелпера из которого вызван этот метод\n\t *\n\t * Под типом понимается ближайший родитель из модуля AdminHelper.\n\t *\n\t * Например если нам нужно получить ListHelper для формирования ссылки на список из EditHelper,\n\t * то это будет вглядеть так $listHelperClass = static::getHelperClass(AdminListHelper::getClass())\n\t *\n\t * @param $class\n     *\n\t * @return string|bool\n\t */\n\tpublic static function getHelperClass($class)\n\t{\n\t\t$interfaceSettings = self::$interfaceSettings[static::getModule()];\n\n\t\tforeach ($interfaceSettings as $viewName => $settings) {\n\t\t\t$parentClasses = class_parents($settings['helper']);\n\t\t\tarray_pop($parentClasses); // AdminBaseHelper\n\n\t\t\t$parentClass = array_pop($parentClasses);\n\t\t\t$thirdClass = array_pop($parentClasses);\n\n\t\t\tif (in_array($thirdClass, array(AdminSectionListHelper::className(), AdminSectionEditHelper::className()))) {\n\t\t\t\t$parentClass = $thirdClass;\n\t\t\t}\n\n\t\t\tif ($parentClass == $class && class_exists($settings['helper'])) {\n\t\t\t\t$helperClassParts = explode('\\\\', $settings['helper']);\n\t\t\t\tarray_pop($helperClassParts);\n\t\t\t\t$helperNamespace = implode('\\\\', $helperClassParts);\n\n\t\t\t\t$сlassParts = explode('\\\\', get_called_class());\n\t\t\t\tarray_pop($сlassParts);\n\t\t\t\t$classNamespace = implode('\\\\', $сlassParts);\n\n\t\t\t\tif ($helperNamespace == $classNamespace) {\n\t\t\t\t\treturn $settings['helper'];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Возвращает относительный namespace до хелперов в виде URL параметра.\n     *\n\t * @return string\n\t */\n\tpublic static function getEntityCode()\n\t{\n\t\t$namespaceParts = explode('\\\\', get_called_class());\n\t\tarray_pop($namespaceParts);\n\t\tarray_shift($namespaceParts);\n\t\tarray_shift($namespaceParts);\n\n\t\tif (end($namespaceParts) == 'AdminInterface') {\n\t\t\tarray_pop($namespaceParts);\n\t\t}\n\n\t\treturn str_replace(\n\t\t\t'\\\\',\n\t\t\t'_',\n\t\t\timplode(\n\t\t\t\t'\\\\',\n\t\t\t\tarray_map('lcfirst', $namespaceParts)\n\t\t\t)\n\t\t);\n\t}\n\n\t/**\n\t * Возвращает URL страницы редактирования класса данного представления.\n     *\n\t * @param array $params\n\t *\n     * @return string\n\t *\n     * @api\n\t */\n\tpublic static function getEditPageURL($params = array())\n\t{\n\t\t$editHelperClass = str_replace('List', 'Edit', get_called_class());\n\t\tif (empty(static::$editViewName) && class_exists($editHelperClass)) {\n\t\t\treturn $editHelperClass::getViewURL($editHelperClass::getViewName(), static::$editPageUrl, $params);\n\t\t}\n\t\telse {\n\t\t\treturn static::getViewURL(static::$editViewName, static::$editPageUrl, $params);\n\t\t}\n\t}\n\n\t/**\n\t * Возвращает URL страницы редактирования класса данного представления.\n     *\n\t * @param array $params\n\t *\n     * @return string\n\t *\n     * @api\n\t */\n\tpublic static function getSectionsEditPageURL($params = array())\n\t{\n\t\t$sectionEditHelperClass = str_replace('List', 'SectionsEdit', get_called_class());\n\n        if (empty(static::$sectionsEditViewName) && class_exists($sectionEditHelperClass)) {\n\t\t\treturn $sectionEditHelperClass::getViewURL($sectionEditHelperClass::getViewName(), static::$sectionsEditPageUrl, $params);\n\t\t}\n\t\telse {\n\t\t\treturn static::getViewURL(static::$sectionsEditViewName, static::$sectionsEditPageUrl, $params);\n\t\t}\n\t}\n\n\t/**\n\t * Возвращает URL страницы списка класса данного представления.\n     *\n\t * @param array $params\n\t *\n     * @return string\n\t *\n     * @api\n\t */\n\tpublic static function getListPageURL($params = array())\n\t{\n\t\t$listHelperClass = str_replace('Edit', 'List', get_called_class());\n\n        if (empty(static::$listViewName) && class_exists($listHelperClass)) {\n\t\t\treturn $listHelperClass::getViewURL($listHelperClass::getViewName(), static::$listPageUrl, $params);\n\t\t}\n\t\telse {\n\t\t\treturn static::getViewURL(static::$listViewName, static::$listPageUrl, $params);\n\t\t}\n\t}\n\n\t/**\n\t * Получает URL для указанного представления\n\t *\n\t * @param string $viewName Название представления.\n\t * @param string $defaultURL Позволяет указать URL напрямую. Если указано, то будет использовано это значение.\n\t * @param array $params Дополнительные query-параметры в URL.\n     *\n\t * @return string\n\t *\n     * @internal\n\t */\n\tpublic static function getViewURL($viewName, $defaultURL, $params = array())\n\t{\n\t\t$params['entity'] = static::getEntityCode();\n\n\t\tif (isset($defaultURL)) {\n\t\t\t$url = $defaultURL . \"?lang=\" . LANGUAGE_ID;\n\t\t}\n\t\telse {\n\t\t\t$url = static::getRouterURL() . '?lang=' . LANGUAGE_ID . '&module=' . static::getModule() . '&view=' . $viewName;\n\t\t}\n\n\t\tif (!empty($params)) {\n\t\t\tunset($params['lang']);\n\t\t\tunset($params['module']);\n\t\t\tunset($params['view']);\n\n\t\t\t$query = http_build_query($params);\n\t\t\t$url .= '&' . $query;\n\t\t}\n\n\t\treturn $url;\n\t}\n\n\t/**\n\t * Возвращает адрес обработчика запросов к админ. интерфейсу.\n\t *\n     * @return string\n\t *\n     * @api\n\t */\n\tpublic static function getRouterURL()\n\t{\n\t\treturn static::$routerUrl;\n\t}\n\n    /**\n     * Возвращает URL страницы с хелпером. Как правило, метод вызывается при генерации административного\n     * меню (`menu.php`).\n     *\n     * @param array $params Дополнительные GET-параметры для подстановки в URL.\n     *\n     * @return string\n     */\n\tpublic static function getUrl(array $params = array())\n\t{\n\t\treturn static::getViewURL(static::getViewName(), null, $params);\n\t}\n\n\t/**\n\t * Получает виджет для текущего поля, выполняет базовую инициализацию.\n\t *\n\t * @param string $code Ключ поля для данного виджета (должен быть в массиве $data).\n\t * @param array $data Данные объекта в виде массива.\n\t *\n     * @return bool|\\DigitalWand\\AdminHelper\\Widget\\HelperWidget\n     *\n\t * @throws \\DigitalWand\\AdminHelper\\Helper\\Exception\n\t *\n     * @internal\n\t */\n\tpublic function createWidgetForField($code, &$data = array())\n\t{\n\t\tif (!isset($this->fields[$code]['WIDGET'])) {\n\t\t\t$error = str_replace('#CODE#', $code, 'Can\\'t create widget for the code \"#CODE#\"');\n\t\t\tthrow new Exception($error, Exception::CODE_NO_WIDGET);\n\t\t}\n\n\t\t/** @var HelperWidget $widget */\n\t\t$widget = $this->fields[$code]['WIDGET'];\n\n\t\t$widget->setHelper($this);\n\t\t$widget->setCode($code);\n\t\t$widget->setData($data);\n\t\t$widget->setEntityName($this->getModel());\n\n\t\t$this->onCreateWidgetForField($widget, $data);\n\n\t\tif (!$this->hasWriteRightsElement($data)) {\n\t\t\t$widget->setSetting('READONLY', true);\n\t\t}\n\n\t\treturn $widget;\n\t}\n\n\t/**\n\t * Метод вызывается при создании виджета для текущего поля. Может быть использован для изменения настроек виджета\n     * на основе передаваемых данных.\n\t *\n\t * @param \\DigitalWand\\AdminHelper\\Widget\\HelperWidget $widget\n\t * @param array $data\n\t */\n\tprotected function onCreateWidgetForField(&$widget, $data = array())\n\t{\n\t}\n\n\t/**\n\t * Если класс не объявлен, то битрикс генерирует новый класс в рантайме. Если класс уже есть, то возвращаем имя\n     * как есть.\n\t *\n\t * @param $className\n\t * @return \\Bitrix\\Highloadblock\\DataManager\n\t *\n\t * @throws \\Bitrix\\Main\\ArgumentException\n\t * @throws \\Bitrix\\Main\\SystemException\n\t * @throws Exception\n\t */\n\tpublic static function getHLEntity($className)\n\t{\n\t\tif (!class_exists($className)) {\n\t\t\t$info = static::getHLEntityInfo($className);\n\n\t\t\tif ($info) {\n\t\t\t\t$entity = HL\\HighloadBlockTable::compileEntity($info);\n\n\t\t\t\treturn $entity->getDataClass();\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$error = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_GETMODEL_EXCEPTION', array('#CLASS#' => $className));\n\t\t\t\t$exception = new Exception($error, Exception::CODE_NO_HL_ENTITY_INFORMATION);\n\n\t\t\t\tthrow $exception;\n\t\t\t}\n\t\t}\n\n\t\treturn $className;\n\t}\n\n\t/**\n\t * Получает запись из БД с информацией об HL.\n\t *\n\t * @param string $className Название класса, обязательно без Table в конце и без указания неймспейса.\n     *\n\t * @return array|false\n\t *\n     * @throws \\Bitrix\\Main\\ArgumentException\n\t */\n\tpublic static function getHLEntityInfo($className)\n\t{\n\t\t$className = str_replace('\\\\', '', $className);\n\t\t$pos = strripos($className, 'Table', -5);\n\n        if ($pos !== false) {\n\t\t\t$className = substr($className, 0, $pos);\n\t\t}\n\n        $parameters = array(\n\t\t\t'filter' => array(\n\t\t\t\t'NAME' => $className,\n\t\t\t),\n\t\t\t'limit' => 1\n\t\t);\n\n\t\treturn HL\\HighloadBlockTable::getList($parameters)->fetch();\n\t}\n\n\t/**\n\t * Отобразить страницу 404 ошибка\n\t */\n\tprotected function show404()\n\t{\n\t\t// инициализация глобальных переменных, необходимых для вывода страницы административного раздела в\n\t\t// текущей области видимости\n\t\tglobal $APPLICATION, $adminPage, $adminMenu, $USER;\n\t\t\\CHTTP::SetStatus(404);\n\t\tinclude $_SERVER['DOCUMENT_ROOT'] . BX_ROOT . '/admin/404.php';\n\t\tdie();\n\t}\n\n\t/**\n\t * Выставляет текущий контекст исполнения.\n     *\n\t * @param $context\n\t *\n     * @see $context\n\t */\n\tprotected function setContext($context)\n\t{\n\t\t$this->context = $context;\n\t}\n\n\tpublic function getContext()\n\t{\n\t\treturn $this->context;\n\t}\n\n\tpublic static function className()\n\t{\n\t\treturn get_called_class();\n\t}\n}"
  },
  {
    "path": "lib/helper/AdminEditHelper.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse DigitalWand\\AdminHelper\\EntityManager;\nuse DigitalWand\\AdminHelper\\Widget\\HelperWidget;\nuse Bitrix\\Main\\Entity\\DataManager;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Базовый класс для реализации детальной страницы админки.\n * При создании своего класса необходимо переопределить следующие переменные:\n * <ul>\n * <li> static protected $model</li>\n * </ul>\n *\n * Этого будет дастаточно для получения минимальной функциональности.\n *\n * @package AdminHelper\n * \n * @see AdminBaseHelper::$model\n * @see AdminBaseHelper::$module\n * @see AdminBaseHelper::$listViewName\n * @see AdminBaseHelper::$viewName\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nabstract class AdminEditHelper extends AdminBaseHelper\n{\n\tconst OP_SHOW_TAB_ELEMENTS = 'AdminEditHelper::showTabElements';\n\tconst OP_EDIT_ACTION_BEFORE = 'AdminEditHelper::editAction_before';\n\tconst OP_EDIT_ACTION_AFTER = 'AdminEditHelper::editAction_after';\n\n\t/**\n\t * @var array Данные сущности, редактируемой в данный момент. Ключи ассива — названия полей в БД.\n\t * @api\n\t */\n\tprotected $data;\n\t/**\n\t * @var array Вкладки страницы редактирования.\n\t */\n\tprotected $tabs = array();\n\t/**\n\t * @var array Элементы верхнего меню страницы.\n\t * @see AdminEditHelper::fillMenu()\n\t */\n\tprotected $menu = array();\n\t/**\n\t * @var \\CAdminForm\n\t */\n\tprotected $tabControl;\n\n\t/**\n\t * Производится инициализация переменных, обработка запросов на редактирование\n\t *\n\t * @param array $fields\n\t * @param array $tabs\n\t */\n\tpublic function __construct(array $fields, array $tabs = array())\n\t{\n\t\t$this->tabs = $tabs;\n\n\t\tif (empty($this->tabs)) {\n\t\t\t$this->tabs = array(\n\t\t\t\tarray(\n\t\t\t\t\t'DIV' => 'DEFAULT_TAB',\n\t\t\t\t\t'TAB' => Loc::getMessage('DEFAULT_TAB'),\n\t\t\t\t\t'ICON' => 'main_user_edit',\n\t\t\t\t\t'TITLE' => Loc::getMessage('DEFAULT_TAB'),\n\t\t\t\t\t'VISIBLE' => true,\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tif (!is_array(reset($this->tabs))) {\n\t\t\t\t$converted = array();\n\n\t\t\t\tforeach ($this->tabs as $tabCode => $tabName) {\n\t\t\t\t\t$tabVisible = true;\n\n\t\t\t\t\tif (is_array($tabName)) {\n\t\t\t\t\t\t$tabVisible = isset($tabName['VISIBLE']) ? $tabName['VISIBLE'] : $tabVisible;\n\t\t\t\t\t\t$tabName = $tabName['TITLE'];\n\t\t\t\t\t}\n\n\t\t\t\t\t$converted[] = array(\n\t\t\t\t\t\t'DIV' => $tabCode,\n\t\t\t\t\t\t'TAB' => $tabName,\n\t\t\t\t\t\t'ICON' => '',\n\t\t\t\t\t\t'TITLE' => $tabName,\n\t\t\t\t\t\t'VISIBLE' => $tabVisible,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t$this->tabs = $converted;\n\t\t\t}\n\t\t}\n\n\t\tparent::__construct($fields, $tabs);\n\n\t\t$this->tabControl = new \\CAdminForm(str_replace(\"\\\\\", \"\", get_called_class()), $this->tabs);\n\n\t\tif (isset($_REQUEST['apply']) OR isset($_REQUEST['save'])) {\n\t\t\tif (\n\t\t\t\tisset($_SERVER[\"HTTP_BX_AJAX\"])\n\t\t\t\t||\n\t\t\t\tisset($_SERVER[\"HTTP_X_REQUESTED_WITH\"]) && $_SERVER[\"HTTP_X_REQUESTED_WITH\"] === \"XMLHttpRequest\"\n\t\t\t) {\n\t\t\t\t\\CUtil::JSPostUnescape();\n\t\t\t}\n\t\t\t$this->data = $_REQUEST['FIELDS'];\n\n\t\t\tif (isset($_REQUEST[$this->pk()])) {\n\t\t\t\t//Первичный ключ проставляем отдельно, чтобы не вынуждать всегда указывать его в настройках интерфейса.\n\t\t\t\t$this->data[$this->pk()] = $_REQUEST[$this->pk()];\n\t\t\t}\n\n\t\t\tforeach ($fields as $code => $settings) {\n\t\t\t\tif (isset($_REQUEST[$code])) {\n\t\t\t\t\t$this->data[$code] = $_REQUEST[$code];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ($this->editAction()) {\n\t\t\t\tif (isset($_REQUEST['apply'])) {\n\t\t\t\t\t$id = $this->data[$this->pk()];\n\t\t\t\t\t$url = $this->app->GetCurPageParam($this->pk() . '=' . (is_array($id) ? $id[$this->pk()] : $id), array('ID'));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (isset($_REQUEST['save'])) {\n\t\t\t\t\t\t$listHelperClass = static::getHelperClass(AdminListHelper::className());\n\t\t\t\t\t\t$url = $listHelperClass::getUrl(array_merge($this->additionalUrlParams,\n\t\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t\t'restore_query' => 'Y'\n\t\t\t\t\t\t\t)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (isset($this->data[$this->pk()])) {\n\t\t\t\t\t$id = $this->data[$this->pk()];\n\t\t\t\t\t$url = $this->app->GetCurPageParam($this->pk() . '=' . $id);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tunset($this->data);\n\t\t\t\t\t$this->data = $_REQUEST['FIELDS']; //Заполняем, чтобы в случае ошибки сохранения поля не были пустыми\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isset($url)) {\n\t\t\t\tif (defined('BX_PUBLIC_MODE') && BX_PUBLIC_MODE === 1 && ($errors = $this->getErrors())) {\n\t\t\t\t\tob_end_clean();\n\t\t\t\t\t$jsMessage = \\CUtil::JSEscape(implode(\"\\n\", $errors));\n\t\t\t\t\techo '<script>top.BX.WindowManager.Get().ShowError(\"' . $jsMessage . '\");</script>';\n\t\t\t\t\tdie();\n\t\t\t\t}\n\t\t\t\t$this->setAppException($this->app->GetException());\n\t\t\t\tLocalRedirect($url);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t$helperFields = $this->getFields();\n\t\t\t$select = array_keys($helperFields);\n\n\t\t\tforeach ($select as $key => $field) {\n\t\t\t\tif (isset($helperFields[$field]['VIRTUAL'])\n\t\t\t\t\tAND $helperFields[$field]['VIRTUAL'] == true\n\t\t\t\t\tAND (!isset($helperFields[$field]['FORCE_SELECT']) OR $helperFields[$field]['FORCE_SELECT'] = false)\n\t\t\t\t) {\n\t\t\t\t\tunset($select[$key]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$this->data = $this->loadElement($select);\n\n\t\t\t$id = isset($_REQUEST[$this->pk()]) ? $_REQUEST[$this->pk()] : null;\n\n\t\t\tif ($this->data === false && !is_null($id)) {\n\t\t\t\t$this->show404();\n\t\t\t}\n\n\t\t\tif (isset($_REQUEST['action']) || isset($_REQUEST['action_button'])) {\n\t\t\t\t$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : $_REQUEST['action_button'];\n\t\t\t\t$this->customActions($action, $this->getPk());\n\t\t\t}\n\t\t}\n\n\t\t$this->setElementTitle();\n\t}\n\n\t/**\n\t * Возвращает верхнее меню страницы.\n\t * По-умолчанию две кнопки:\n\t * <ul>\n\t * <li> Возврат в список</li>\n\t * <li> Удаление элемента</li>\n\t * </ul>\n\t *\n\t * Добавляя новые кнопки, нужно указывать параметр URl \"action\", который будет обрабатываться в\n\t * AdminEditHelper::customActions()\n\t *\n\t * @param bool $showDeleteButton Управляет видимостью кнопки удаления элемента.\n     * \n     * @return array\n     * \n\t * @see AdminEditHelper::$menu\n\t * @see AdminEditHelper::customActions()\n\t * \n     * @api\n\t */\n\tprotected function getMenu($showDeleteButton = true)\n\t{\n\t\t$listHelper = static::getHelperClass(AdminListHelper::className());\n        \n\t\t$menu = array(\n\t\t\t$this->getButton('RETURN_TO_LIST', array(\n\t\t\t\t'LINK' => $listHelper::getUrl(array_merge($this->additionalUrlParams,\n\t\t\t\t\tarray('restore_query' => 'Y')\n\t\t\t\t)),\n\t\t\t\t'ICON' => 'btn_list',\n\t\t\t))\n\t\t);\n\n\t\t$arSubMenu = array();\n\n\t\tif (isset($this->data[$this->pk()]) && $this->hasWriteRights()) {\n\t\t\t$arSubMenu[] = $this->getButton('ADD_ELEMENT', array(\n\t\t\t\t'LINK' => static::getUrl(array_merge($this->additionalUrlParams,\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'action' => 'add',\n\t\t\t\t\t\t'lang' => LANGUAGE_ID,\n\t\t\t\t\t\t'restore_query' => 'Y',\n\t\t\t\t\t))),\n\t\t\t\t'ICON' => 'edit'\n\t\t\t));\n\t\t}\n\n\t\tif ($showDeleteButton && isset($this->data[$this->pk()]) && $this->hasDeleteRights()) {\n\t\t\t$arSubMenu[] = $this->getButton('DELETE_ELEMENT', array(\n\t\t\t\t'ONCLICK' => \"if(confirm('\" . Loc::getMessage('DIGITALWAND_ADMIN_HELPER_EDIT_DELETE_CONFIRM') . \"')) location.href='\" .\n\t\t\t\t\tstatic::getUrl(array_merge($this->additionalUrlParams,\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t'ID' => $this->data[$this->pk()],\n\t\t\t\t\t\t\t'action' => 'delete',\n\t\t\t\t\t\t\t'lang' => LANGUAGE_ID,\n\t\t\t\t\t\t\t'restore_query' => 'Y',\n\t\t\t\t\t\t))) . \"'\",\n\t\t\t\t'ICON' => 'delete'\n\t\t\t));\n\t\t}\n\n\t\tif (count($arSubMenu)) {\n\t\t\t$menu[] = array('SEPARATOR' => 'Y');\n\t\t\t$menu[] = $this->getButton('ACTIONS', array(\n\t\t\t\t'MENU' => $arSubMenu,\n\t\t\t\t'ICON' => 'btn_new'\n\t\t\t));\n\t\t}\n\n\t\treturn $menu;\n\t}\n\n    /**\n     * {@inheritdoc}\n     */\n\tpublic function show()\n\t{\n\t\tif (!$this->hasReadRights()) {\n\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_ACCESS_FORBIDDEN'));\n\t\t\t$this->showMessages();\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$context = new \\CAdminContextMenu($this->getMenu());\n\t\t$context->Show();\n\n\t\t$this->tabControl->BeginPrologContent();\n\t\t$this->showMessages();\n\t\t$this->showProlog();\n\t\t$this->tabControl->EndPrologContent();\n\n\t\t$this->tabControl->BeginEpilogContent();\n\t\t$this->showEpilog();\n\t\t$this->tabControl->EndEpilogContent();\n\n\t\t$query = $this->additionalUrlParams;\n        \n\t\tif (isset($_REQUEST[$this->pk()])) {\n\t\t\t$query[$this->pk()] = $_REQUEST[$this->pk()];\n\t\t}\n\t\telseif (isset($_REQUEST['SECTION_ID']) && $_REQUEST['SECTION_ID']) {\n\t\t\t$this->data[static::getSectionField()] = $_REQUEST['SECTION_ID'];\n\t\t}\n\n\t\t$this->tabControl->Begin(array(\n\t\t\t'FORM_ACTION' => static::getUrl($query)\n\t\t));\n\n\t\tforeach ($this->tabs as $tabSettings) {\n\t\t\tif ($tabSettings['VISIBLE']) {\n\t\t\t\t$this->showTabElements($tabSettings);\n\t\t\t}\n\t\t}\n\n\t\t$this->showEditPageButtons();\n\t\t$this->tabControl->ShowWarnings('editform', array()); //TODO: дописать\n\t\t$this->tabControl->Show();\n\t}\n\n\t/**\n\t * Отображение кнопок для управления элементом на странице редактирования.\n\t */\n\tprotected function showEditPageButtons()\n\t{\n\t\t$listHelper = static::getHelperClass(AdminListHelper::className());\n\t\n        $this->tabControl->Buttons(array(\n\t\t\t'back_url' => $listHelper::getUrl(array_merge($this->additionalUrlParams,\n\t\t\t\tarray(\n\t\t\t\t\t'lang' => LANGUAGE_ID,\n\t\t\t\t\t'restore_query' => 'Y',\n\t\t\t\t)))\n\t\t));\n\t}\n\n\t/**\n\t * Отрисовка верхней части страницы.\n     * \n\t * @api\n\t */\n\tprotected function showProlog()\n\t{\n\t}\n\n\t/**\n\t * Отрисовка нижней части страницы. По-умолчанию рисует все поля, которые не попали в вывод, как input hidden.\n     * \n\t * @api\n\t */\n\tprotected function showEpilog()\n\t{\n\t\techo bitrix_sessid_post();\n\t\n        $interfaceSettings = static::getInterfaceSettings();\n\n\t\tforeach ($interfaceSettings['FIELDS'] as $code => $settings) {\n\t\t\tif (!isset($settings['TAB']) AND isset($settings['FORCE_SELECT']) AND $settings['FORCE_SELECT'] == true) {\n\t\t\t\tprint '<input type=\"hidden\" name=\"FIELDS[' . $code . ']\" value=\"' . $this->data[$code] . '\" />';\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Отрисовывает вкладку со всеми привязанными к ней полями.\n\t *\n\t * @param $tabSettings\n     * \n\t * @internal\n\t */\n\tprivate function showTabElements($tabSettings)\n\t{\n\t\t$this->setContext(AdminEditHelper::OP_SHOW_TAB_ELEMENTS);\n\t\t$this->tabControl->BeginNextFormTab();\n\n\t\tforeach ($this->getFields() as $code => $fieldSettings) {\n\t\t\t$widget = $this->createWidgetForField($code, $this->data);\n\t\t\t$fieldTab = $widget->getSettings('TAB');\n\t\t\t$fieldOnCurrentTab = ($fieldTab == $tabSettings['DIV'] OR $tabSettings['DIV'] == 'DEFAULT_TAB');\n\n\t\t\tif (!$fieldOnCurrentTab) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$fieldSettings = $widget->getSettings();\n\n\t\t\tif (isset($fieldSettings['VISIBLE']) && $fieldSettings['VISIBLE'] === false) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->tabControl->BeginCustomField($code, $widget->getSettings('TITLE'));\n\t\t\t$pkField = ($code == $this->pk());\n\t\t\t$widget->showBasicEditField($pkField);\n\t\t\t$this->tabControl->EndCustomField($code);\n\t\t}\n\t}\n\n\t/**\n\t * Обработка запроса редактирования страницы. Этапы:\n\t * <ul>\n\t * <li> Проверка прав пользователя</li>\n\t * <li> Создание виджетов для каждого поля</li>\n\t * <li> Удаление значений для READONLY и HIDE_WHEN_CREATE полей</li>\n\t * <li> Изменение данных модели каждым виджетом (исходя из его внутренней логики)</li>\n\t * <li> Валидация значений каждого поля соответствующим виджетом</li>\n\t * <li> Проверка на ошибики валидации</li>\n\t * <li> В случае неудачи - выход из функции</li>\n\t * <li> В случае успеха - обновление или добавление элемента в БД</li>\n\t * <li> Постобработка данных модели каждым виджетом</li>\n\t * </ul>\n\t *\n\t * @return bool\n\t * \n     * @see HelperWidget::processEditAction();\n\t * @see HelperWidget::processAfterSaveAction();\n\t * \n     * @internal\n\t */\n\tprotected function editAction()\n\t{\n\t\t$this->setContext(AdminEditHelper::OP_EDIT_ACTION_BEFORE);\n\n\t\tif (!$this->hasWriteRights()) {\n\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_EDIT_WRITE_FORBIDDEN'));\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$allWidgets = array();\n\n\t\tforeach ($this->getFields() as $code => $settings) {\n\t\t\tif ($settings['READONLY'] && $code !== $this->pk()) {\n\t\t\t\tunset($this->data[$code]);\n\t\t\t}\n\t\t}\n\n\t\tforeach ($this->getFields() as $code => $settings) {\n\t\t\t$widget = $this->createWidgetForField($code, $this->data);\n\t\t\t$widget->processEditAction();\n\t\t\t$this->validationErrors = array_merge($this->validationErrors, $widget->getValidationErrors());\n\t\t\t$allWidgets[] = $widget;\n\n\t\t\tif ($widget->getSettings('READONLY') || empty($this->data[$this->pk()]) \n\t\t\t\t&& $widget->getSettings('HIDE_WHEN_CREATE')) {\n\t\t\t\tunset($this->data[$code]);\n\t\t\t}\n\t\t}\n\n\t\t$this->addErrors($this->validationErrors);\n\t\t$success = empty($this->validationErrors);\n\n\t\tif ($success) {\n\t\t\t$this->setContext(AdminEditHelper::OP_EDIT_ACTION_AFTER);\n\t\t\t$existing = false;\n\t\t\t$id = $this->getPk();\n\n\t\t\tif ($id) {\n\t\t\t\t$existing = $this->loadElement();\n\t\t\t}\n\n\t\t\tif ($existing) {\n\t\t\t\t$result = $this->saveElement($id);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$result = $this->saveElement();\n\t\t\t}\n\n\t\t\tif ($result) {\n\t\t\t\tif (!$result->isSuccess()) {\n\t\t\t\t\t$this->addErrors($result->getErrorMessages());\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// TODO Вывод ошибки\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t$this->data[$this->pk()] = $result->getId();\n\n\t\t\tforeach ($allWidgets as $widget) {\n\t\t\t\t/** @var HelperWidget $widget */\n\t\t\t\t$widget->setData($this->data);\n\t\t\t\t$widget->processAfterSaveAction();\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Функция загрузки элемента из БД. Можно переопределить, если требуется сложная логика и нет возможности\n\t * определить её в модели.\n\t *\n\t * @param array $select\n\t *\n\t * @return bool\n\t * @api\n\t */\n\tprotected function loadElement($select = array())\n\t{\n\t\tif ($this->getPk() !== null) {\n\t\t\t$className = static::getModel();\n\t\t\t$result = $className::getList(array(\n\t\t\t\t'filter' => array(\n\t\t\t\t\t$this->pk() => $this->getPk()\n\t\t\t\t),\n\t\t\t\t'select' => $select ?: array('*')\n\t\t\t));\n\n\t\t\treturn $result->fetch();\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Сохранение элемента. Можно переопределить, если требуется сложная логика и нет возможности определить её \n     * в модели.\n     * \n     * Операциями сохранения модели занимается EntityManager.\n\t *\n\t * @param bool $id\n\t * \n     * @return \\Bitrix\\Main\\Entity\\AddResult|\\Bitrix\\Main\\Entity\\UpdateResult\n\t * \n     * @throws \\Exception\n     * \n     * @see EntityManager\n\t * \n     * @api\n\t */\n\tprotected function saveElement($id = null)\n\t{\n\t\t/** @var EntityManager $entityManager */\n\t\t$entityManager = new static::$entityManager(static::getModel(), empty($this->data) ? array() : $this->data, $id, $this);\n\t\t$saveResult = $entityManager->save();\n\t\t$this->addNotes($entityManager->getNotes());\n\n\t\treturn $saveResult;\n\t}\n\n\t/**\n\t * Удаление элемента. Можно переопределить, если требуется сложная логика и нет возможности определить её в модели.\n\t *\n\t * @param $id\n\t * \n     * @return bool|\\Bitrix\\Main\\Entity\\DeleteResult\n\t * \n     * @throws \\Exception\n\t * \n     * @api\n\t */\n\tprotected function deleteElement($id)\n\t{\n\t\tif (!$this->hasDeleteRights()) {\n\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_EDIT_DELETE_FORBIDDEN'));\n\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t/** @var EntityManager $entityManager */\n\t\t$entityManager = new static::$entityManager(static::getModel(), empty($this->data) ? array() : $this->data, $id, $this);\n\n\t\t$deleteResult = $entityManager->delete();\n\t\t$this->addNotes($entityManager->getNotes());\n\n\t\treturn $deleteResult;\n\t}\n\n\t/**\n\t * Выполнение кастомных операций над объектом в процессе редактирования.\n\t *\n\t * @param string $action Название операции.\n\t * @param int|null $id ID элемента.\n     * \n\t * @see AdminEditHelper::fillMenu()\n\t * \n     * @api\n\t */\n\tprotected function customActions($action, $id = null)\n\t{\n\t\tif ($action == 'delete' AND !is_null($id)) {\n\t\t\t$result = $this->deleteElement($id);\n            \n\t\t\tif(!$result->isSuccess()){\n\t\t\t\t$this->addErrors($result->getErrorMessages());\n\t\t\t}\n\t\t\t\n            $listHelper = static::getHelperClass(AdminListHelper::className());\n            $redirectUrl = $listHelper::getUrl(array_merge(\n                $this->additionalUrlParams,\n                array('restore_query' => 'Y')\n            ));\n\t\t\t\n            LocalRedirect($redirectUrl);\n\t\t}\n\t}\n\n\t/**\n\t * Устанавливает заголовок исходя из данных текущего элемента.\n\t *\n\t * @see $data\n\t * @see AdminBaseHelper::setTitle()\n\t * \n     * @api\n\t */\n\tprotected function setElementTitle()\n\t{\n\t\tif (!empty($this->data)) {\n\t\t\t$title = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_EDIT_TITLE', array('#ID#' => $this->data[$this->pk()]));\n\t\t}\n\t\telse {\n\t\t\t$title = Loc::getMessage('DIGITALWAND_ADMIN_HELPER_NEW_ELEMENT');\n\t\t}\n\n\t\t$this->setTitle($title);\n\t}\n\n\t/**\n\t * @return \\CAdminForm\n\t */\n\tpublic function getTabControl()\n\t{\n\t\treturn $this->tabControl;\n\t}\n\n    /**\n     * @inheritdoc\n     */\n\tpublic static function getUrl(array $params = array())\n\t{\n\t\treturn static::getViewURL(static::getViewName(), static::$editPageUrl, $params);\n\t}\n}\n"
  },
  {
    "path": "lib/helper/AdminInterface.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\n/**\n * Базовый класс для описания админского интерфейса.\n * Включает в себя методы описывающие элементы управления, названия столбцов, типы полей и т.д.\n *\n * Есть 2 метода которые обязательно должны быть описаны в реализуемых классах:\n *\n * getFields()  - должен возвращать массив со списком табов и описанием полей для каждого таба\n * getHelpers() - должен возваращать массив со списком классов хелперов, также может включать\n * описание настроек элементов управления для хелпера.\n *\n * Для того что бы модуль мог корректна работать необходима регистрация классов унаследованных от AdminInterface.\n * Это можно сделтаь в include.php другого модуля(не рекомендуется) или AdminInterface зарегистрируется\n * автоматически если при генерации ссылок на страницы админского интерфейса использовался статический\n * метод getLink из соответствующего хелпера (ListHelper для списка элементов и EditHelper для страницы редактирования)\n *\n * При использовании разделов необходимо уведомить AdminInterface элементов и AdminInterface разделов о существовании\n * друг друга, что бы каждый из них регистрировал другого в момент собственной регистрации. Для этого достаточно указать полное\n * имя класса в методе getDependencies(), это нужно сделать как для AdminInterface элементов так и для AdminInterface разделов.\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nabstract class AdminInterface\n{\n\t/**\n\t * Список зарегистрированных интерфейсов\n\t * @var string\n\t */\n\tpublic static $registeredInterfaces = array();\n\n\t/**\n\t * Описание интерфейса админки: списка табов и полей. Метод должен вернуть массив вида:\n\t *\n\t * ```\n\t * array(\n\t *    'TAB_1' => array(\n\t *        'NAME' => Loc::getMessage('VENDOR_MODULE_ENTITY_TAB_1_NAME'),\n\t *        'FIELDS' => array(\n\t *            'FIELD_1' => array(\n\t *                'WIDGET' => new StringWidget(),\n\t *                'TITLE' => Loc::getMessage('VENDOR_MODULE_ENTITY_FIELD_1_TITLE'),\n\t *                ...\n\t *            ),\n\t *            'FIELD_2' => array(\n\t *                'WIDGET' => new NumberWidget(),\n\t *                'TITLE' => Loc::getMessage('VENDOR_MODULE_ENTITY_FIELD_2_TITLE'),\n\t *                ...\n\t *            ),\n\t *            ...\n\t *        )\n\t *    ),\n\t *    'TAB_2' => array(\n\t *        'NAME' => Loc::getMessage('VENDOR_MODULE_ENTITY_TAB_2_NAME'),\n\t *        'FIELDS' => array(\n\t *            'FIELD_3' => array(\n\t *                'WIDGET' => new DateTimeWidget(),\n\t *                'TITLE' => Loc::getMessage('VENDOR_MODULE_ENTITY_FIELD_3_TITLE'),\n\t *                ...\n\t *            ),\n\t *            'FIELD_4' => array(\n\t *                'WIDGET' => new UserWidget(),\n\t *                'TITLE' => Loc::getMessage('VENDOR_MODULE_ENTITY_FIELD_4_TITLE'),\n\t *                ...\n\t *            ),\n\t *            ...\n\t *        )\n\t *    ),\n\t *  ...\n\t * )\n\t * ```\n\t *\n\t * Где TAB_1..2 - символьные коды табов, FIELD_1..4 - название столбцов в таблице сущности. TITLE для поля задавать\n\t * не обязательно, в этому случае он будет запрашиваться из модели.\n\t *\n\t * Более подробную информацию о формате описания настроек виджетов см. в классе HelperWidget.\n\t *\n\t * @see DigitalWand\\AdminHelper\\Widget\\HelperWidget\n\t *\n\t * @return array[]\n\t */\n\tabstract public function fields();\n\n\t/**\n\t * Список классов хелперов с настройками. Метод должен вернуть массив вида:\n\t *\n\t * ```\n\t * array(\n\t *    '\\Vendor\\Module\\Entity\\AdminInterface\\EntityListHelper' => array(\n\t *        'BUTTONS' => array(\n\t *            'RETURN_TO_LIST' => array('TEXT' => Loc::getMessage('VENDOR_MODULE_ENTITY_RETURN_TO_LIST')),\n\t *            'ADD_ELEMENT' => array('TEXT' => Loc::getMessage('VENDOR_MODULE_ENTITY_ADD_ELEMENT'),\n\t *            ...\n\t *        )\n\t *    ),\n\t *    '\\Vendor\\Module\\Entity\\AdminInterface\\EntityEditHelper' => array(\n\t *        'BUTTONS' => array(\n\t *            'LIST_CREATE_NEW' => array('TEXT' => Loc::getMessage('VENDOR_MODULE_ENTITY_LIST_CREATE_NEW')),\n\t *            'LIST_CREATE_NEW_SECTION' => array('TEXT' => Loc::getMessage('VENDOR_MODULE_ENTITY_LIST_CREATE_NEW_SECTION'),\n\t *            ...\n\t *        )\n\t *    )\n\t * )\n\t * ```\n\t *\n\t * или\n\t *\n\t * ```\n\t * array(\n\t *    '\\Vendor\\Module\\Entity\\AdminInterface\\EntityListHelper',\n\t *    '\\Vendor\\Module\\Entity\\AdminInterface\\EntityEditHelper'\n\t * )\n\t * ```\n\t *\n\t * Где:\n\t * <ul>\n\t * <li> `Vendor\\Module\\Entity\\AdminInterface` - namespace до реализованных классов AdminHelper.\n\t * <li> `BUTTONS` - ключ для массива с описанием элементов управления (подробнее в методе getButton()\n\t *          класса AdminBaseHelper).\n\t * <li> `LIST_CREATE_NEW`, `LIST_CREATE_NEW_SECTION`, `RETURN_TO_LIST`, `ADD_ELEMENT` - символьные код элементов\n\t *          управления.\n\t * <li> `EntityListHelper` и `EntityEditHelper` - реализованные классы хелперов.\n\t *\n\t * Оба формата могут сочетаться друг с другом.\n\t *\n\t * @see \\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper::getButton()\n\t *\n\t * @return string[]\n\t */\n\tabstract public function helpers();\n\n\t/**\n\t * Список зависимых админских интерфейсов, которые будут зарегистрированы при регистраци админского интерфейса,\n\t * например, админские интерфейсы разделов.\n\t *\n\t * @return string[]\n\t */\n\tpublic function dependencies()\n\t{\n\t\treturn array();\n\t}\n\n\t/**\n\t * Регистрируем поля, табы и кнопки.\n\t */\n\tpublic function registerData()\n\t{\n\t\t$fieldsAndTabs = array('FIELDS' => array(), 'TABS' => array());\n\t\t$tabsWithFields = $this->fields();\n\n\t\t// приводим массив хелперов к формату класс => настройки\n\t\t$helpers = array();\n\n\t\tforeach ($this->helpers() as $key => $value) {\n\t\t\tif (is_array($value)) {\n\t\t\t\t$helpers[$key] = $value;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$helpers[$value] = array();\n\t\t\t}\n\t\t}\n\n\t\t$helperClasses = array_keys($helpers);\n\t\t/**\n\t\t * @var \\Bitrix\\Main\\Entity\\DataManager\n\t\t */\n\t\t$model = $helperClasses[0]::getModel();\n\t\tforeach ($tabsWithFields as $tabCode => $tab) {\n\t\t\t$fieldsAndTabs['TABS'][$tabCode] = $tab['NAME'];\n\n\t\t\tforeach ($tab['FIELDS'] as $fieldCode => $field) {\n\t\t\t\tif (empty($field['TITLE']) && $model) {\n\t\t\t\t    //Битрикс не использует параметр title при создании экземпляра ReferenceField.\n                    if (is_a($model::getEntity()->getField($fieldCode), 'Bitrix\\Main\\Entity\\ReferenceField')) {\n                        $map = $model::getMap();\n                        if(isset($map[$fieldCode]['title'])){\n                            $field['TITLE'] = $map[$fieldCode]['title'];\n                        }\n                    } else {\n                        $field['TITLE'] = $model::getEntity()->getField($fieldCode)->getTitle();\n                    }\n\t\t\t\t}\n\n\t\t\t\t$field['TAB'] = $tabCode;\n\t\t\t\t$fieldsAndTabs['FIELDS'][$fieldCode] = $field;\n\t\t\t}\n\t\t}\n\n\t\tAdminBaseHelper::setInterfaceSettings($fieldsAndTabs, $helpers, $helperClasses[0]::getModule());\n\n\t\tforeach ($helperClasses as $helperClass) {\n\t\t\t/**\n\t\t\t * @var AdminBaseHelper $helperClass\n\t\t\t */\n\t\t\t$helperClass::setInterfaceClass(get_called_class());\n\t\t}\n\t}\n\n\t/**\n\t * Регистрация интерфейса и его зависимостей.\n\t */\n\tpublic static function register()\n\t{\n\t\tif (!in_array(get_called_class(), static::$registeredInterfaces)) {\n\t\t\tstatic::$registeredInterfaces[] = get_called_class();\n\n\t\t\t$adminInterface = new static();\n\t\t\t$adminInterface->registerData();\n\n\t\t\tforeach ($adminInterface->dependencies() as $adminInterfaceClass) {\n\t\t\t\t$adminInterfaceClass::register();\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "lib/helper/AdminListHelper.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse Bitrix\\Main\\Entity\\DataManager;\nuse Bitrix\\Main\\DB\\Result;\nuse DigitalWand\\AdminHelper\\EntityManager;\nuse DigitalWand\\AdminHelper\\Sorting;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Базовый класс для реализации страницы списка админки.\n * При создании своего класса необходимо переопределить следующие переменные:\n * <ul>\n * <li> static protected $model </Li>\n * </ul>\n *\n * Этого будет дастаточно для получения минимальной функциональности\n * Также данный класс может использоваться для отображения всплывающих окон с возможностью выбора элемента из списка\n *\n * @see AdminBaseHelper::$model\n * @see AdminBaseHelper::$module\n * @see AdminBaseHelper::$editViewName\n * @see AdminBaseHelper::$viewName\n * @package AdminHelper\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nabstract class AdminListHelper extends AdminBaseHelper\n{\n\tconst OP_GROUP_ACTION = 'AdminListHelper::__construct_groupAction';\n\tconst OP_ADMIN_VARIABLES_FILTER = 'AdminListHelper::prepareAdminVariables_filter';\n\tconst OP_ADMIN_VARIABLES_HEADER = 'AdminListHelper::prepareAdminVariables_header';\n\tconst OP_GET_DATA_BEFORE = 'AdminListHelper::getData_before';\n\tconst OP_ADD_ROW_CELL = 'AdminListHelper::addRowCell';\n\tconst OP_CREATE_FILTER_FORM = 'AdminListHelper::createFilterForm';\n\tconst OP_CHECK_FILTER = 'AdminListHelper::checkFilter';\n\tconst OP_EDIT_ACTION = 'AdminListHelper::editAction';\n\n\t\n\t/**\n     \t* @var bool\n     \t* Показывать ли кнопки добавления раздела и элемента в списке  \n     \t*/\n\tprotected $showAdd = true;\n\t/**\n\t * @var bool\n\t * Выводить кнопку экспорта в Excel\n\t * @api\n\t */\n\tprotected $exportExcel = true;\n\t/**\n\t * @var bool\n\t * Выводить в списке кол-во элементов пункт Все\n\t */\n\tprotected $showAll = true;\n\t/**\n\t * @var bool\n\t * Является ли список всплывающим окном для выбора элементов из списка.\n\t * В этой версии не должно быть операций удаления/перехода к редактированию.\n\t */\n\tprotected $isPopup = false;\n\t/**\n\t * @var string\n\t * Название поля, в котором хранится результат выбора во всплывающем окне\n\t */\n\tprotected $fieldPopupResultName = '';\n\t/**\n\t * @var string\n\t * Уникальный индекс поля, в котором хранится результат выбора во всплывающем окне\n\t */\n\tprotected $fieldPopupResultIndex = '';\n\tprotected $sectionFields = array();\n\t/**\n\t * @var string\n\t * Название столбца, в котором хранится название элемента\n\t */\n\tprotected $fieldPopupResultElTitle = '';\n\t/**\n\t * @var string\n\t * Название функции, вызываемой при даблклике на строке списка, в случае, если список выводится в режиме\n\t *     всплывающего окна\n\t */\n\tprotected $popupClickFunctionName = 'selectRow';\n\t/**\n\t * @var string\n\t * Код функции, вызываемой при клике на строке списка\n\t * @see AdminListHelper::genPopupActionJS()\n\t */\n\tprotected $popupClickFunctionCode;\n\t/**\n\t * @var array\n\t * Массив с заголовками таблицы\n\t * @see \\CAdminList::AddHeaders()\n\t */\n\tprotected $arHeader = array();\n\t/**\n\t * @var array\n\t * параметры фильтрации списка в классическим битриксовом формате\n\t */\n\tprotected $arFilter = array();\n\t/**\n\t * @var array\n\t * Массив, хранящий тип фильтра для данного поля. Позволяет избежать лишнего парсинга строк.\n\t */\n\tprotected $filterTypes = array();\n\t/**\n\t * @var array\n\t * Поля, предназначенные для фильтрации\n\t * @see \\CAdminList::InitFilter();\n\t */\n\tprotected $arFilterFields = array();\n\t/**\n\t * Список полей, для которых доступна фильтрация\n\t * @var array\n\t * @see \\CAdminFilter::__construct();\n\t */\n\tprotected $arFilterOpts = array();\n\t/**\n\t * @var \\CAdminList\n\t */\n\tprotected $list;\n\t/**\n\t * @var string\n\t * Префикс таблицы. Нужен, чтобы обеспечить уникальность относительно других админ. интерфейсов.\n\t * Без его добавления к конструктору таблицы повычается вероятность, что возникнет конфликт с таблицей из другого\n\t * административного интерфейса, в результате чего неправильно будет работать паджинация, фильтрация. Вероятны\n\t * ошибки запросов к БД.\n\t */\n\tstatic protected $tablePrefix = \"digitalwand_admin_helper_\";\n\t/**\n\t * @var array\n\t * @see \\CAdminList::AddGroupActionTable()\n\t */\n\tprotected $groupActionsParams = array();\n\t/**\n\t * Текущие параметры пагинации,\n\t * требуются для составления смешанного списка разделов и элементов\n\t * @var array\n\t */\n\tprotected $navParams = array();\n\t/**\n\t * Количество элементов смешанном списке\n\t * @see AdminListHelper::CustomNavStart\n\t * @var int\n\t */\n\tprotected $totalRowsCount = 0;\n\t/**\n\t * Массив для слияния столбцов элементов и разделов\n\t * @var array\n\t */\n\tprotected $tableColumnsMap = array();\n\t/**\n\t * @var string\n\t * HTML верхней части таблицы\n\t * @api\n\t */\n\tpublic $prologHtml;\n\n\t/**\n\t * @var string\n\t * HTML нижней части таблицы\n\t * @api\n\t */\n\tpublic $epilogHtml;\n\n\t/**\n\t * Производится инициализация переменных, обработка запросов на редактирование\n\t *\n\t * @param array $fields\n\t * @param bool $isPopup\n\t * @throws \\Bitrix\\Main\\ArgumentException\n\t */\n\tpublic function __construct(array $fields, $isPopup = false)\n\t{\n\t\t$this->isPopup = $isPopup;\n\n\t\tif ($this->isPopup) {\n\t\t\t$this->fieldPopupResultName = preg_replace(\"/[^a-zA-Z0-9_:\\\\[\\\\]]/\", \"\", $_REQUEST['n']);\n\t\t\t$this->fieldPopupResultIndex = preg_replace(\"/[^a-zA-Z0-9_:]/\", \"\", $_REQUEST['k']);\n\t\t\t$this->fieldPopupResultElTitle = $_REQUEST['eltitle'];\n\t\t}\n\n\t\tparent::__construct($fields);\n\n\t\t$this->restoreLastGetQuery();\n\t\t$this->prepareAdminVariables();\n\n\t\t$className = static::getModel();\n\t\t$oSort = $this->initSortingParameters();\n\t\t$this->list = new \\CAdminList($this->getListTableID(), $oSort);\n\t\t$this->list->InitFilter($this->arFilterFields);\n\n\t\tif ($this->list->EditAction() AND $this->hasWriteRights()) {\n\t\t\tglobal $FIELDS;\n\t\t\tforeach ($FIELDS as $id => $fields) {\n\t\t\t\tif (!$this->list->IsUpdated($id)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$this->editAction($id, $fields);\n\t\t\t}\n\t\t}\n\t\tif ($IDs = $this->list->GroupAction() AND $this->hasWriteRights()) {\n\n\t\t    //Элементы выбраны галочкой \"Для всех\". Нужно собрать все элементы и разделы,\n            //попадающие под текущий фильтр, и передать их ID на удаление\n\n\t\t\tif ($_REQUEST['action_target'] == 'selected') {\n                $this->setContext(AdminListHelper::OP_GROUP_ACTION);\n\n                //Если находимся в подразделе, то его нужно учесть при фильтрации\n                if (isset($_GET['SECTION_ID'])) {\n                    $sectionField = static::getSectionField();\n                    $this->arFilter[$sectionField] = $_GET['SECTION_ID'];\n                }\n\n                $IDs = array();\n\n                //Текущий фильтр должен быть модифицирован виждтами\n                //для соответствия результатов фильтрации тому, что видит пользователь в интерфейсе.\n                $raw = array(\n                    'SELECT' => $this->pk(),\n                    'FILTER' => $this->arFilter,\n                    'SORT' => array()\n                );\n\n                foreach ($this->fields as $code => $settings) {\n                    $widget = $this->createWidgetForField($code);\n                    $widget->changeGetListOptions($this->arFilter, $raw['SELECT'], $raw['SORT'], $raw);\n                }\n\n                $res = $className::getList(array(\n                    'filter' => $this->arFilter,\n                    'select' => array($this->pk()),\n                ));\n\n                while ($el = $res->Fetch()) {\n                    $IDs[] = $el[$this->pk()];\n                }\n\n                //Собираем ID разделов, если они используются\n                $sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());\n                if ($sectionEditHelperClass) {\n                    $sectionFilter = $this->arFilter;\n                    $sectionsInterfaceSettings = static::getInterfaceSettings($sectionEditHelperClass::getViewName());\n\n                    if ($sectionEditHelperClass && !isset($_REQUEST['model-section'])) {\n                        $sectionClassName = $sectionEditHelperClass::getModel();\n                    } else {\n                        $sectionClassName = $_REQUEST['model-section'];\n                    }\n\n                    foreach ($sectionFilter as $elementFieldName => $elementFilterValue) {\n                        $elementFieldNameEscaped = $this->escapeFilterFieldName($elementFieldName);\n                        if (!isset($sectionsInterfaceSettings['FIELDS'][$elementFieldNameEscaped])) {\n                            unset($sectionFilter[$elementFieldName]);\n                        }\n                    }\n                    if (isset($_GET['SECTION_ID'])) {\n                        $sectionField = $sectionEditHelperClass::getSectionField();\n                        $sectionFilter[$sectionField] = $_GET['SECTION_ID'];\n                    }\n\n                    $res = $sectionClassName::getList(array(\n                        'filter' => $sectionFilter,\n                        'select' => array($this->pk()),\n                    ));\n\n                    while ($el = $res->Fetch()) {\n                        $IDs[] = 's' . $el[$this->pk()];\n                    }\n                }\n            }\n\n\t\t\t$filteredIDs = array();\n\n\t\t\tforeach ($IDs as $id) {\n\t\t\t\tif (strlen($id) <= 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$filteredIDs[] = IntVal($id);\n\t\t\t}\n\t\t\t$this->groupActions($IDs, $_REQUEST['action']);\n\t\t}\n\n\t\tif (isset($_REQUEST['action']) || isset($_REQUEST['action_button']) && count($this->getErrors()) == 0) {\n\t\t\t$listHelperClass = $this->getHelperClass(AdminListHelper::className());\n\t\t\t$id = isset($_GET['ID']) ? $_GET['ID'] : null;\n\t\t\t$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : $_REQUEST['action_button'];\n\t\t\tif ($action != 'edit' && $_REQUEST['cancel'] != 'Y') {\n\t\t\t\t$params = $_GET;\n\t\t\t\tunset($params['action']);\n\t\t\t\tunset($params['action_button']);\n\t\t\t\t$this->customActions($action, $id);\n\t\t\t\tLocalRedirect($listHelperClass::getUrl($params));\n\t\t\t}\n\t\t}\n\n\t\tif ($this->isPopup()) {\n\t\t\t$this->genPopupActionJS();\n\t\t}\n\n\t\t// Получаем параметры навигации\n\t\t$navUniqSettings = array(\n\t\t\t'nPageSize' => 20,\n\t\t\t'sNavID' => $this->getListTableID()\n\t\t);\n\t\t$this->navParams = array(\n\t\t\t'nPageSize' => \\CAdminResult::GetNavSize($this->getListTableID(), $navUniqSettings),\n\t\t\t'navParams' => \\CAdminResult::GetNavParams($navUniqSettings)\n\t\t);\n\t}\n\n\t/**\n\t * Инициализирует параметры сортировки на основании запроса\n\t * @return \\CAdminSorting\n\t */\n\tprotected function initSortingParameters()\n\t{\n\t\t$sortByParameter = 'by';\n\t\t$sortOrderParameter = 'order';\n\n\t\treturn new Sorting($this->getListTableID(), $this->pk(), 'desc', $sortByParameter, $sortOrderParameter, $this);\n\t}\n\n\t/**\n\t * Подготавливает переменные, используемые для инициализации списка.\n\t *\n\t * - добавляет поля в список фильтра только если FILTER не задано false по умолчанию для виджета и поле не является\n\t * полем связи сущностью разделов\n\t */\n\tprotected function prepareAdminVariables()\n\t{\n\t\t$this->arHeader = array();\n\t\t$this->arFilter = array();\n\t\t$this->arFilterFields = array();\n\t\t$arFilter = array();\n\t\t$this->filterTypes = array();\n\t\t$this->arFilterOpts = array();\n\n\t\t$sectionField = static::getSectionField();\n\n\t\tforeach ($this->fields as $code => $settings) {\n\t\t\t$widget = $this->createWidgetForField($code);\n\n\t\t\tif (\n\t\t\t\t($sectionField != $code && $widget->getSettings('FILTER') !==false)\n\t\t\t\t&&\n\t\t\t\t((isset($settings['FILTER']) AND $settings['FILTER'] != false) OR !isset($settings['FILTER']))\n\t\t\t) {\n\n\t\t\t\t$this->setContext(AdminListHelper::OP_ADMIN_VARIABLES_FILTER);\n\t\t\t\t$filterVarName = 'find_' . $code;\n\t\t\t\t$this->arFilterFields[] = $filterVarName;\n\t\t\t\t$filterType = '';\n\n\t\t\t\tif (is_string($settings['FILTER'])) {\n\t\t\t\t\t$filterType = $settings['FILTER'];\n\t\t\t\t}\n\n\t\t\t\tif (isset($_REQUEST[$filterVarName])\n\t\t\t\t\tAND !isset($_REQUEST['del_filter'])\n\t\t\t\t\tAND $_REQUEST['del_filter'] != 'Y'\n\t\t\t\t) {\n\t\t\t\t\t$arFilter[$filterType . $code] = $_REQUEST[$filterVarName];\n\t\t\t\t\t$this->filterTypes[$code] = $filterType;\n\t\t\t\t}\n\n\t\t\t\t$this->arFilterOpts[$code] = $widget->getSettings('TITLE');\n\t\t\t}\n\n\t\t\tif (!isset($settings['LIST']) || $settings['LIST'] === true) {\n\t\t\t\t$this->setContext(AdminListHelper::OP_ADMIN_VARIABLES_HEADER);\n\t\t\t\t$mergedColumn = false;\n\t\t\t\t// проверяем есть ли столбец раздела с таким названием\n\t\t\t\tif ($widget->getSettings('LIST_TITLE')) {\n\t\t\t\t\t$sectionHeader = $this->getSectionsHeader();\n\t\t\t\t\tforeach ($sectionHeader as $sectionColumn) {\n\t\t\t\t\t\tif ($sectionColumn['content'] == $widget->getSettings('LIST_TITLE')) {\n\t\t\t\t\t\t\t// добавляем столбец элементов в карту столбцов\n\t\t\t\t\t\t\t$this->tableColumnsMap[$code] = $sectionColumn['id'];\n\t\t\t\t\t\t\t$mergedColumn = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!$mergedColumn) {\n\t\t\t\t\t$this->arHeader[] = array(\n\t\t\t\t\t\t\"id\" => $code,\n\t\t\t\t\t\t\"content\" => $widget->getSettings('LIST_TITLE') ? $widget->getSettings('LIST_TITLE') : $widget->getSettings('TITLE'),\n\t\t\t\t\t\t\"sort\" => $code,\n\t\t\t\t\t\t\"default\" => !isset($settings['HEADER']) || $settings['HEADER'] === true,\n\t\t\t\t\t\t'admin_list_helper_sort' => $widget->getSettings('LIST_COLUMN_SORT') ? $widget->getSettings('LIST_COLUMN_SORT') : 100\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ($this->checkFilter($arFilter)) {\n\t\t\t$this->arFilter = $arFilter;\n\t\t}\n\n\t\tif (static::getHelperClass(AdminSectionEditHelper::className())) {\n\t\t\t$this->arFilter[static::getSectionField()] = $_GET['ID'];\n\t\t}\n\t}\n\n\t/**\n\t * Возвращает список столбцов для разделов\n\t * @return array\n\t */\n\tpublic function getSectionsHeader()\n\t{\n\t\t$arSectionsHeaders = array();\n\t\t$sectionHelper = static::getHelperClass(AdminSectionEditHelper::className());\n\t\t$sectionsInterfaceSettings = static::getInterfaceSettings($sectionHelper::getViewName());\n\t\t$this->sectionFields = $sectionsInterfaceSettings['FIELDS'];\n\n\t\tforeach ($sectionsInterfaceSettings['FIELDS'] as $code => $settings) {\n\n\t\t\tif (!isset($settings['LIST']) || $settings['LIST'] === true) {\n\t\t\t\t$arSectionsHeaders[] = array(\n\t\t\t\t\t\"id\" => $code,\n\t\t\t\t\t\"content\" => isset($settings['LIST_TITLE']) ? $settings['LIST_TITLE'] : $settings['TITLE'],\n\t\t\t\t\t\"sort\" => $code,\n\t\t\t\t\t\"default\" => !isset($settings['HEADER']) || $settings['HEADER'] === true,\n\t\t\t\t\t'admin_list_helper_sort' => isset($settings['LIST_COLUMN_SORT']) ? $settings['LIST_COLUMN_SORT'] : 100\n\t\t\t\t);\n\t\t\t}\n\t\t\tunset($settings['WIDGET']);\n\n\t\t\tforeach ($settings as $c => $v) {\n\t\t\t\t$sectionsInterfaceSettings['FIELDS'][$code]['WIDGET']->setSetting($c, $v);\n\t\t\t}\n\t\t}\n\n\t\treturn $arSectionsHeaders;\n\t}\n\n\t/**\n\t * Производит проверку корректности данных (в массиве $_REQUEST), переданных в фильтр\n\t * @TODO: нужно сделать вывод сообщений об ошибке фильтрации.\n\t * @param $arFilter\n\t * @return bool\n\t */\n\tprotected function checkFilter($arFilter)\n\t{\n\t\t$this->setContext(AdminListHelper::OP_CHECK_FILTER);\n\t\t$filterValidationErrors = array();\n\t\tforeach ($this->filterTypes as $code => $type) {\n\t\t\t$widget = $this->createWidgetForField($code);\n\t\t\t$value = $arFilter[$type . $code];\n\t\t\tif (!$widget->checkFilter($type, $value)) {\n\t\t\t\t$filterValidationErrors = array_merge($filterValidationErrors,\n\t\t\t\t\t$widget->getValidationErrors());\n\t\t\t}\n\t\t}\n\n\t\treturn empty($filterValidationErrors);\n\t}\n\n\t/**\n\t * Подготавливает массив с настройками контекстного меню. По-умолчанию добавлена кнопка \"создать элемент\".\n\t *\n\t * @see $contextMenu\n\t *\n\t * @api\n\t */\n\tprotected function getContextMenu()\n\t{\n\t\t$contextMenu = array();\n\t\t/** @var AdminSectionEditHelper $sectionEditHelper */\n\t\t$sectionEditHelper = static::getHelperClass(AdminSectionEditHelper::className());\n\t\tif ($sectionEditHelper) {\n\t\t\t$sectionId = $_GET['SECTION_ID'] ?: $_GET['ID'] ?: null;\n\t\t\t$this->additionalUrlParams['SECTION_ID'] = $sectionId = $sectionId > 0 ? (int)$sectionId : null;\n\t\t}\n\n\t\t/**\n\t\t * Если задан для разделов добавляем кнопку создать раздел и\n\t\t * кнопку на уровень вверх если это не корневой раздел\n\t\t */\n\t\tif (isset($sectionId)) {\n\t\t\t$params = $this->additionalUrlParams;\n\t\t\t$sectionModel = $sectionEditHelper::getModel();\n\t\t\t$sectionField = $sectionEditHelper::getSectionField();\n\t\t\t$section = $sectionModel::getById(\n\t\t\t\t$this->getCommonPrimaryFilterById($sectionModel, null, $sectionId)\n\t\t\t)->Fetch();\n\t\t\tif ($this->isPopup()) {\n\t\t\t\t$params = array_merge($_GET);\n\t\t\t}\n\t\t\tif ($section[$sectionField]) {\n\t\t\t\t$params['ID'] = $section[$sectionField];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tunset($params['ID']);\n\t\t\t}\n\t\t\tunset($params['SECTION_ID']);\n\t\t\t$contextMenu[] = $this->getButton('LIST_SECTION_UP', array(\n\t\t\t\t'LINK' => static::getUrl($params),\n\t\t\t\t'ICON' => 'btn_list'\n\t\t\t));\n\t\t}\n\n\t\t/**\n\t\t * Добавляем кнопку создать элемент и создать раздел\n\t\t */\n\t\tif (!$this->isPopup() && $this->hasWriteRights() && $this->showAdd) {\n\t\t\t$editHelperClass = static::getHelperClass(AdminEditHelper::className());\n\t\t\tif ($editHelperClass) {\n\t\t\t\t$contextMenu[] = $this->getButton('LIST_CREATE_NEW', array(\n\t\t\t\t\t'LINK' => $editHelperClass::getUrl($this->additionalUrlParams),\n\t\t\t\t\t'ICON' => 'btn_new'\n\t\t\t\t));\n\t\t\t}\n\t\t\t$sectionsHelperClass = static::getHelperClass(AdminSectionEditHelper::className());\n\t\t\tif ($sectionsHelperClass) {\n\t\t\t\t$contextMenu[] = $this->getButton('LIST_CREATE_NEW_SECTION', array(\n\t\t\t\t\t'LINK' => $sectionsHelperClass::getUrl($this->additionalUrlParams),\n\t\t\t\t\t'ICON' => 'btn_new'\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\n\t\treturn $contextMenu;\n\t}\n\n\t/**\n\t * Возвращает массив с настройками групповых действий над списком.\n\t *\n\t * @return array\n\t *\n\t * @api\n\t */\n\tprotected function getGroupActions()\n\t{\n\t\t$result = array();\n\n\t\tif (!$this->isPopup()) {\n\t\t\tif ($this->hasDeleteRights()) {\n\t\t\t\t$result = array('delete' => Loc::getMessage(\"DIGITALWAND_ADMIN_HELPER_LIST_DELETE\"));\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Обработчик групповых операций. По-умолчанию прописаны операции активации / деактивации и удаления.\n\t *\n\t * @param array $IDs\n\t * @param string $action\n\t *\n\t * @api\n\t */\n\tprotected function groupActions($IDs, $action)\n\t{\n\t\t$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\t$listHelperClass = $this->getHelperClass(AdminListHelper::className());\n\n\t\t$className = static::getModel();\n\t\tif (isset($_REQUEST['model'])) {\n\t\t\t$className = $_REQUEST['model'];\n\t\t}\n\n\t\tif ($sectionEditHelperClass && !isset($_REQUEST['model-section'])) {\n\t\t\t$sectionClassName = $sectionEditHelperClass::getModel();\n\t\t}\n\t\telse {\n\t\t\t$sectionClassName = $_REQUEST['model-section'];\n\t\t}\n\n\t\tif ($action == 'delete') {\n\t\t\tif ($this->hasDeleteRights()) {\n\t\t\t\t$complexPrimaryKey = is_array($className::getEntity()->getPrimary());\n\t\t\t\tif ($complexPrimaryKey) {\n\t\t\t\t\t$IDs = $this->getIds();\n\t\t\t\t}\n\n\t\t\t\t// ищем правильный урл для перехода\n\t\t\t\tif (!empty($IDs[0])) {\n\n\t\t\t\t\t$id = $complexPrimaryKey ? $IDs[0][$this->pk()] : $IDs[0];\n\t\t\t\t\t$model = $className;\n\n\t\t\t\t\tif (strpos($id, 's') === 0) {\n\t\t\t\t\t\t$model = $sectionClassName;\n\t\t\t\t\t\t$listHelper = $this->getHelperClass(AdminSectionListHelper::className());\n\t\t\t\t\t\tif (!$listHelper) {\n\t\t\t\t\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SECTION_HELPER_NOT_FOUND'));\n\t\t\t\t\t\t\tunset($_GET['ID']);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$id = substr($id, 1);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$listHelper = $listHelperClass;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ($listHelper) {\n\t\t\t\t\t\t$id = $this->getCommonPrimaryFilterById($model, null, $id);\n\t\t\t\t\t\t$element = $model::getById($id)->Fetch();\n\t\t\t\t\t\t$sectionField = $listHelper::getSectionField();\n\t\t\t\t\t\tif ($element[$sectionField]) {\n\t\t\t\t\t\t\t$_GET[$this->pk()] = $element[$sectionField];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tunset($_GET['ID']);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tforeach ($IDs as $id) {\n\t\t\t\t\t$model = $className;\n\t\t\t\t\t$id = $complexPrimaryKey ? $id[$this->pk()] : $id;\n\t\t\t\t\tif (strpos($id, 's') === 0) {\n\t\t\t\t\t\t$model = $sectionClassName;\n\t\t\t\t\t\t$id = substr($id, 1);\n\t\t\t\t\t}\n\t\t\t\t\t/** @var EntityManager $entityManager */\n\t\t\t\t\t$entityManager = new static::$entityManager($model, empty($this->data) ? array() : $this->data, $id,\n\t\t\t\t\t\t$this);\n\t\t\t\t\t$result = $entityManager->delete();\n\t\t\t\t\t$this->addNotes($entityManager->getNotes());\n\t\t\t\t\tif (!$result->isSuccess()) {\n\t\t\t\t\t\t$this->addErrors($result->getErrorMessages());\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_FORBIDDEN'));\n\t\t\t}\n\t\t}\n\n\t\tif ($action == 'delete-section') {\n\t\t\tif ($this->hasDeleteRights()) {\n\n\t\t\t\t// ищем правильный урл для перехода\n\t\t\t\tif (!empty($IDs[0])) {\n\t\t\t\t\t$id = $this->getCommonPrimaryFilterById($sectionClassName, null, $IDs[0]);\n\t\t\t\t\t$sectionListHelperClass = $this->getHelperClass(AdminSectionListHelper::className());\n\t\t\t\t\tif ($sectionListHelperClass) {\n\t\t\t\t\t\t$element = $sectionClassName::getById($id)->Fetch();\n\t\t\t\t\t\t$sectionField = $sectionListHelperClass::getSectionField();\n\t\t\t\t\t\tif ($element[$sectionField]) {\n\t\t\t\t\t\t\t$_GET[$this->pk()] = $element[$sectionField];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tunset($_GET['ID']);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunset($_GET['ID']);\n\t\t\t\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SECTION_HELPER_NOT_FOUND'));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tforeach ($IDs as $id) {\n\t\t\t\t\t$id = $this->getCommonPrimaryFilterById($sectionClassName, null, $id);\n\t\t\t\t\t$entityManager = new static::$entityManager($sectionClassName, array(), $id, $this);\n\t\t\t\t\t$result = $entityManager->delete();\n\t\t\t\t\t$this->addNotes($entityManager->getNotes());\n\t\t\t\t\tif(!$result->isSuccess()){\n\t\t\t\t\t\t$this->addErrors($result->getErrorMessages());\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_FORBIDDEN'));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Сохранение полей для отной записи, отредактированной в списке.\n\t * Этапы:\n\t * <ul>\n\t * <li> Выборка элемента по ID, чтобы удостовериться, что он существует. В противном случае  возвращается\n\t * ошибка</li>\n\t * <li> Создание виджета для каждой ячейки, валидация значений поля</li>\n\t * <li> TODO: вывод ошибок валидации</li>\n\t * <li> Сохранение записи</li>\n\t * <li> Вывод ошибок сохранения, если таковые появились</li>\n\t * <li> Модификация данных сроки виджетами.</li>\n\t * </ul>\n\t *\n\t * @param int $id ID записи в БД\n\t * @param array $fields Поля с изменениями\n\t *\n\t * @see HelperWidget::processEditAction();\n\t * @see HelperWidget::processAfterSaveAction();\n\t */\n\tprotected function editAction($id, $fields)\n\t{\n\t\t$this->setContext(AdminListHelper::OP_EDIT_ACTION);\n\t\tif(strpos($id, 's')===0){ // для раделов другой класс модели\n\t\t\t$editHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\t\t$sectionsInterfaceSettings = static::getInterfaceSettings($editHelperClass::getViewName());\n\t\t\t$className = $editHelperClass::getModel();\n\t\t\t$id = str_replace('s','',$id);\n\t\t}else{\n\t\t\t$className = static::getModel();\n\t\t\t$sectionsInterfaceSettings = false;\n\t\t}\n\n\t\t$idForLog = $id;\n\t\t$complexPrimaryKey = is_array($className::getEntity()->getPrimary());\n\t\tif ($complexPrimaryKey) {\n\t\t\t$oldRequest = $_REQUEST;\n\t\t\t$_REQUEST = array($this->pk() => $id);\n\t\t\t$id = $this->getCommonPrimaryFilterById($className, null, $id);\n\t\t\t$idForLog = json_encode($id);\n\t\t\t$_REQUEST = $oldRequest;\n\t\t}\n\n\t\t$el = $className::getById($id);\n\t\tif ($el->getSelectedRowsCount() == 0) {\n\t\t\t$this->list->AddGroupError(Loc::getMessage(\"MAIN_ADMIN_SAVE_ERROR\"), $idForLog);\n\t\t\treturn;\n\t\t}\n\n\t\t// замена кодов для столбцов элементов соединенных со столбцами разделов\n\t\tif($sectionsInterfaceSettings==false){\n\t\t\t$tableColumnsMap = array_flip($this->tableColumnsMap);\n\t\t\t$replacedFields = array();\n\t\t\tforeach($fields as $key => $value){\n\t\t\t\tif(!empty($tableColumnsMap[$key])) {\n\t\t\t\t\t$key = $tableColumnsMap[$key];\n\t\t\t\t}\n\t\t\t\t$replacedFields[$key] = $value;\n\t\t\t}\n\t\t\t$fields = $replacedFields;\n\t\t}\n\n\t\t$allWidgets = array();\n\t\tforeach ($fields as $key => $value) {\n\t\t\tif($sectionsInterfaceSettings!==false){ // для разделов свои виджеты\n\t\t\t\t$widget = $sectionsInterfaceSettings['FIELDS'][$key]['WIDGET'];\n\t\t\t}else{\n\t\t\t\t$widget = $this->createWidgetForField($key, $fields); // для элементов свои\n\t\t\t}\n\n\t\t\t$widget->processEditAction();\n\t\t\t$this->validationErrors = array_merge($this->validationErrors, $widget->getValidationErrors());\n\t\t\t$allWidgets[] = $widget;\n\t\t}\n\t\t//FIXME: может, надо добавить вывод ошибок ДО сохранения?..\n\t\t$this->addErrors($this->validationErrors);\n\n\t\t$result = $className::update($id, $fields);\n\t\t$errors = $result->getErrorMessages();\n\t\tif (empty($this->validationErrors) AND !empty($errors)) {\n\t\t\t$fieldList = implode(\"\\n\", $errors);\n\t\t\t$this->list->AddGroupError(Loc::getMessage(\"MAIN_ADMIN_SAVE_ERROR\") . \" \" . $fieldList, $idForLog);\n\t\t}\n\n\t\tif (!empty($errors)) {\n\t\t\tforeach ($allWidgets as $widget) {\n\t\t\t\t/** @var \\DigitalWand\\AdminHelper\\Widget\\HelperWidget $widget */\n\t\t\t\t$widget->setData($fields);\n\t\t\t\t$widget->processAfterSaveAction();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Является ли список всплывающим окном для выбора элементов из списка.\n\t * В этой версии не должно быть операций удаления/перехода к редактированию.\n\t *\n\t * @return boolean\n\t */\n\tpublic function isPopup()\n\t{\n\t\treturn $this->isPopup;\n\t}\n\n\t/**\n\t * Функция определяет js-функцию для двойонго клика по строке.\n\t * Вызывается в том случае, если окно открыто в режиме попапа.\n\t *\n\t * @api\n\t */\n\tprotected function genPopupActionJS()\n\t{\n\t\t$this->popupClickFunctionCode = '<script>\n\t\t\tfunction ' . $this->popupClickFunctionName . '(data){\n\t\t\t\tvar input = window.opener.document.getElementById(\"' . $this->fieldPopupResultName . '[' . $this->fieldPopupResultIndex . ']\");\n\t\t\t\tif(!input)\n\t\t\t\t\tinput = window.opener.document.getElementById(\"' . $this->fieldPopupResultName . '\");\n\t\t\t\tif(input)\n\t\t\t\t{\n\t\t\t\t\tinput.value = data.ID;\n\t\t\t\t\tif (window.opener.BX)\n\t\t\t\t\t\twindow.opener.BX.fireEvent(input, \"change\");\n\t\t\t\t}\n\t\t\t\tvar span = window.opener.document.getElementById(\"sp_' . md5($this->fieldPopupResultName) . '_' . $this->fieldPopupResultIndex . '\");\n\t\t\t\tif(!span)\n\t\t\t\t\tspan = window.opener.document.getElementById(\"sp_' . $this->fieldPopupResultName . '\");\n\t\t\t\tif(!span)\n\t\t\t\t\tspan = window.opener.document.getElementById(\"' . $this->fieldPopupResultName . '_link\");\n\t\t\t\tif(span)\n\t\t\t\t\tspan.innerHTML = data[\"' . $this->fieldPopupResultElTitle . '\"];\n\t\t\t\twindow.close();\n\t\t\t}\n\t\t</script>';\n\t}\n\n\t/**\n\t * Основной цикл отображения списка. Этапы:\n\t * <ul>\n\t * <li> Вывод заголовков страницы </li>\n\t * <li> Определение списка видимых колонок и колонок, участвующих в выборке. </li>\n\t * <li> Создание виджета для каждого поля выборки </li>\n\t * <li> Модификация параметров запроса каждым из виджетов </li>\n\t * <li> Выборка данных </li>\n\t * <li> Вывод строк таблицы. Во время итерации по строкам возможна модификация данных строки. </li>\n\t * <li> Отрисовка футера таблицы, добавление контекстного меню </li>\n\t * </ul>\n\t *\n\t * @param array $sort Настройки сортировки.\n\t *\n\t * @see AdminListHelper::getList();\n\t * @see AdminListHelper::getMixedData();\n\t * @see AdminListHelper::modifyRowData();\n\t * @see AdminListHelper::addRowCell();\n\t * @see AdminListHelper::addRow();\n\t * @see HelperWidget::changeGetListOptions();\n\t */\n\tpublic function buildList($sort)\n\t{\n\t\t$this->setContext(AdminListHelper::OP_GET_DATA_BEFORE);\n\n\t\t$headers = $this->arHeader;\n\n\t\t$sectionEditHelper = static::getHelperClass(AdminSectionEditHelper::className());\n\n\t\tif ($sectionEditHelper) { // если есть реализация класса AdminSectionEditHelper, значит используются разделы\n\t\t\t$sectionHeaders = $this->getSectionsHeader();\n\t\t\tforeach ($sectionHeaders as $sectionHeader) {\n\t\t\t\t$found = false;\n\t\t\t\tforeach ($headers as $i => $elementHeader) {\n\t\t\t\t\tif ($sectionHeader['content'] == $elementHeader['content'] || $sectionHeader['id'] == $elementHeader['id']) {\n\t\t\t\t\t\tif (!$elementHeader['default'] && $sectionHeader['default']) {\n\t\t\t\t\t\t\t$headers[$i] = $sectionHeader;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$found = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!$found) {\n\t\t\t\t\t$headers[] = $sectionHeader;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// сортировка столбцов с сохранением исходной позиции в\n\t\t// массиве для развнозначных элементов\n\t\t// массив $headers модифицируется\n\t\t$this->mergeSortHeader($headers);\n\n\t\t$this->list->AddHeaders($headers);\n\t\t$visibleColumns = $this->list->GetVisibleHeaderColumns();\n\n\t\t$modelClass = $this->getModel();\n\t\t$elementFields = array_keys($modelClass::getEntity()->getFields());\n\n\t\tif ($sectionEditHelper) {\n\t\t\t$sectionsVisibleColumns = array();\n\t\t\tforeach ($visibleColumns as $k => $v) {\n\t\t\t\tif (isset($this->sectionFields[$v])) {\n\t\t\t\t\tif(!in_array($v, $elementFields)){\n\t\t\t\t\t\tunset($visibleColumns[$k]);\n\t\t\t\t\t}\n\t\t\t\t\tif (!isset($this->sectionFields[$v]['LIST']) || $this->sectionFields[$v]['LIST'] !== false) {\n\t\t\t\t\t\t$sectionsVisibleColumns[] = $v;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t$visibleColumns = array_values($visibleColumns);\n\t\t\t$visibleColumns = array_merge($visibleColumns, array_keys($this->tableColumnsMap));\n\t\t}\n\n\t\t$className = static::getModel();\n\t\t$visibleColumns[] = $this->pk();\n\t\t$sectionsVisibleColumns[] = $this->sectionPk();\n\n\t\t$raw = array(\n\t\t\t'SELECT' => $visibleColumns,\n\t\t\t'FILTER' => $this->arFilter,\n\t\t\t'SORT' => $sort\n\t\t);\n\n\t\tforeach ($this->fields as $name => $settings) {\n\t\t\t$key = array_search($name, $visibleColumns);\n\t\t\tif ((isset($settings['VIRTUAL']) AND $settings['VIRTUAL'] == true)) {\n\t\t\t\tunset($visibleColumns[$key]);\n\t\t\t\tunset($this->arFilter[$name]);\n\t\t\t\tunset($sort[$name]);\n\t\t\t}\n\t\t\tif (isset($settings['LIST']) && $settings['LIST'] === false) {\n\t\t\t\tunset($visibleColumns[$key]);\n\t\t\t}\n\t\t\tif (isset($settings['FORCE_SELECT']) AND $settings['FORCE_SELECT'] == true) {\n\t\t\t\t$visibleColumns[] = $name;\n\t\t\t}\n\t\t}\n\n\t\t$visibleColumns = array_unique($visibleColumns);\n\t\t$sectionsVisibleColumns = array_unique($sectionsVisibleColumns);\n\n\t\tforeach ($this->fields as $code => $settings) {\n            if($_REQUEST['del_filter'] !== 'Y') {\n                $widget = $this->createWidgetForField($code);\n                $widget->changeGetListOptions($this->arFilter, $visibleColumns, $sort, $raw);\n            }\n\t\t\t// Множественные поля не должны быть в селекте\n\t\t\tif (!empty($settings['MULTIPLE'])) {\n            \t$visibleColumns = array_diff($visibleColumns, array($code));\n\t\t\t}\n\t\t}\n\n\t\tif ($sectionEditHelper) // Вывод разделов и элементов в одном списке\n\t\t{\n\t\t\t$mixedData = $this->getMixedData($sectionsVisibleColumns, $visibleColumns, $sort, $raw);\n\t\t\t$res = new \\CDbResult;\n\t\t\t$res->InitFromArray($mixedData);\n\t\t\t$res = new \\CAdminResult($res, $this->getListTableID());\n\t\t\t$res->nSelectedCount = $this->totalRowsCount;\n\t\t\t// используем кастомный NavStart что бы определить правильное количество страниц и элементов в списке\n\t\t\t$this->customNavStart($res);\n\t\t\t$this->list->NavText($res->GetNavPrint(Loc::getMessage(\"PAGES\")));\n\t\t\twhile ($data = $res->NavNext(false)) {\n\t\t\t\t$this->modifyRowData($data);\n\t\t\t\tif ($data['IS_SECTION']) // для разделов своя обработка\n\t\t\t\t{\n\t\t\t\t\tlist($link, $name) = $this->getRow($data, $this->getHelperClass(AdminSectionEditHelper::className()));\n\t\t\t\t\t$row = $this->list->AddRow('s' . $data[$this->pk()], $data, $link, $name);\n\t\t\t\t\tforeach ($this->sectionFields as $code => $settings) {\n\t\t\t\t\t\tif (in_array($code, $sectionsVisibleColumns)) {\n\t\t\t\t\t\t\t$this->addRowSectionCell($row, $code, $data);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t$row->AddActions($this->getRowActions($data, true));\n\t\t\t\t}\n\t\t\t\telse // для элементов своя\n\t\t\t\t{\n\t\t\t\t\t$this->modifyRowData($data);\n\t\t\t\t\tlist($link, $name) = $this->getRow($data);\n\t\t\t\t\t// объединение полей элемента с полями раздела\n\t\t\t\t\tforeach ($this->tableColumnsMap as $elementCode => $sectionCode) {\n\t\t\t\t\t\tif (isset($data[$elementCode])) {\n\t\t\t\t\t\t\t$data[$sectionCode] = $data[$elementCode];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t$row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);\n\t\t\t\t\tforeach ($this->fields as $code => $settings) {\n\t\t\t\t\t\tif(in_array($code, $visibleColumns)) {\n\t\t\t\t\t\t\t$this->addRowCell($row, $code, $data,\n\t\t\t\t\t\t\tisset($this->tableColumnsMap[$code]) ? $this->tableColumnsMap[$code] : false);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t$row->AddActions($this->getRowActions($data));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse // Обычный вывод элементов без использования разделов\n\t\t{\n\t\t\t$this->totalRowsCount = $className::getCount($this->getElementsFilter($this->arFilter));\n\t\t\t$res = $this->getData($className, $this->arFilter, $visibleColumns, $sort, $raw);\n\t\t\t$res = new \\CAdminResult($res, $this->getListTableID());\n\t\t\t$this->customNavStart($res);\n\t\t\t// отключаем отображение всех элементов, если установлено св-во\n\t\t\t$res->bShowAll = $this->showAll;\n\t\t\t$this->list->NavText($res->GetNavPrint(Loc::getMessage(\"PAGES\")));\n\t\t\twhile ($data = $res->NavNext(false)) {\n\t\t\t\t$this->modifyRowData($data);\n\t\t\t\tlist($link, $name) = $this->getRow($data);\n\t\t\t\t$row = $this->list->AddRow($data[$this->pk()], $data, $link, $name);\n\t\t\t\tforeach ($this->fields as $code => $settings) {\n\t\t\t\t\tif(in_array($code, $visibleColumns)) {\n\t\t\t\t\t\t$this->addRowCell($row, $code, $data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t$row->AddActions($this->getRowActions($data));\n\t\t\t}\n\t\t}\n\n\t\t$this->list->AddFooter($this->getFooter($res));\n\t\t$this->list->AddGroupActionTable($this->getGroupActions(), $this->groupActionsParams);\n\t\t$this->list->AddAdminContextMenu($this->getContextMenu(), $this->exportExcel);\n\n\t\t$this->list->BeginPrologContent();\n\t\techo $this->prologHtml;\n\t\t$this->list->EndPrologContent();\n\n\t\t$this->list->BeginEpilogContent();\n\t\techo $this->epilogHtml;\n\t\t$this->list->EndEpilogContent();\n\n\t\t// добавляем ошибки в CAdminList для режимов list и frame\n\t\t$errors = $this->getErrors();\n\t\tif(in_array($_GET['mode'], array('list','frame')) && is_array($errors)) {\n\t\t\tforeach($errors as $error) {\n\t\t\t\t$this->list->addGroupError($error);\n\t\t\t}\n\t\t}\n\n\t\t$this->list->CheckListMode();\n\t}\n\n\t/**\n\t * Функция сортировки столбцов c сохранением порядка равнозначных элементов\n\t * @param $array\n\t */\n\tprotected function mergeSortHeader(&$array)\n\t{\n\t\t// для сортировки нужно хотя бы 2 элемента\n\t\tif (count($array) < 2) return;\n\n\t\t// делим массив пополам\n\t\t$halfway = count($array) / 2;\n\t\t$array1 = array_slice($array, 0, $halfway);\n\t\t$array2 = array_slice($array, $halfway);\n\n\t\t// реукрсивно сортируем каждую половину\n\t\t$this->mergeSortHeader($array1);\n\t\t$this->mergeSortHeader($array2);\n\n\t\t// если последний элемент первой половины меньше или равен первому элементу\n\t\t// второй половины, то просто соединяем массивы\n\t\tif ($this->mergeSortHeaderCompare(end($array1), $array2[0]) < 1) {\n\t\t\t$array = array_merge($array1, $array2);\n\t\t\treturn;\n\t\t}\n\n\t\t// соединяем 2 отсортированных половины в один отсортированный массив\n\t\t$array = array();\n\t\t$ptr1 = $ptr2 = 0;\n\t\twhile ($ptr1 < count($array1) && $ptr2 < count($array2)) {\n\t\t\t// собираем в 1 массив последовательную цепочку\n\t\t\t// элементов из 2-х отсортированных половинок\n\t\t\tif ($this->mergeSortHeaderCompare($array1[$ptr1], $array2[$ptr2]) < 1) {\n\t\t\t\t$array[] = $array1[$ptr1++];\n\t\t\t}\n\t\t\telse {\n\t\t\t\t$array[] = $array2[$ptr2++];\n\t\t\t}\n\t\t}\n\n\t\t// если в исходных массивах что-то осталось забираем в основной массив\n\t\twhile ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];\n\t\twhile ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];\n\n\t\treturn;\n\t}\n\n\t/**\n\t * Функция сравнения столбцов по их весу в сортировке\n\t * @param $a\n\t * @param $b\n\t * @return int\n\t */\n\tpublic function mergeSortHeaderCompare($a, $b)\n\t{\n\t\t$a = $a['admin_list_helper_sort'];\n\t\t$b = $b['admin_list_helper_sort'];\n\t\tif ($a == $b) {\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn ($a < $b) ? -1 : 1;\n\t}\n\n\t/**\n\t * Получение смешанного списка из разделов и элементов.\n\t *\n\t * @param $sectionsVisibleColumns\n\t * @param $elementVisibleColumns\n\t * @param $sort\n\t * @param $raw\n\t * @return array\n\t */\n\tprotected function getMixedData($sectionsVisibleColumns, $elementVisibleColumns, $sort, $raw)\n\t{\n\t\t$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\t$elementEditHelperClass = $this->getHelperClass(AdminEditHelper::className());\n\t\t$sectionField = $sectionEditHelperClass::getSectionField();\n\t\t$sectionId = $_GET['SECTION_ID'] ? $_GET['SECTION_ID'] : $_GET['ID'];\n\t\t$returnData = array();\n\t\t/**\n\t\t * @var DataManager $sectionModel\n\t\t */\n\t\t$sectionModel = $sectionEditHelperClass::getModel();\n\t\t$sectionFilter = array();\n\n\t\t// добавляем из фильтра те поля которые есть у разделов\n\t\tforeach ($this->arFilter as $field => $value) {\n\t\t\t$fieldName = $this->escapeFilterFieldName($field);\n\n\t\t\tif(!empty($this->tableColumnsMap[$fieldName])) {\n\t\t\t\t$field = str_replace($fieldName, $this->tableColumnsMap[$fieldName], $field);\n\t\t\t\t$fieldName = $this->tableColumnsMap[$fieldName];\n\t\t\t}\n\n\t\t\tif (isset($this->sectionFields[$fieldName])) {\n\t\t\t\t$sectionFilter[$field] = $value;\n\t\t\t}\n\t\t}\n\n\t\t$sectionFilter[$sectionField] = $sectionId;\n\n\t\t$raw['SELECT'] = array_unique($raw['SELECT']);\n\n\t\t// при использовании в качестве popup окна исключаем раздел из выборке\n\t\t// что бы не было возможности сделать раздел родителем самого себя\n\t\tif (!empty($_REQUEST['self_id'])) {\n\t\t\t$sectionFilter['!' . $this->sectionPk()] = $_REQUEST['self_id'];\n\t\t}\n\n\t\t$sectionSort = array();\n\t\t$limitData = $this->getLimits();\n\t\t// добавляем к общему количеству элементов количество разделов\n\t\t$this->totalRowsCount = $sectionModel::getCount($this->getSectionsFilter($sectionFilter));\n\t\tforeach ($sort as $field => $direction) {\n\t\t\tif (in_array($field, $sectionsVisibleColumns)) {\n\t\t\t\t$sectionSort[$field] = $direction;\n\t\t\t}\n\t\t}\n\t\t// добавляем к выборке разделы\n\t\t$rsSections = $sectionModel::getList(array(\n\t\t\t'filter' => $this->getSectionsFilter($sectionFilter),\n\t\t\t'select' => $sectionsVisibleColumns,\n\t\t\t'order' => $sectionSort,\n\t\t\t'limit' => $limitData[1],\n\t\t\t'offset' => $limitData[0],\n\t\t));\n\n\t\twhile ($section = $rsSections->fetch()) {\n\t\t\t$section['IS_SECTION'] = true;\n\t\t\t$returnData[] = $section;\n\t\t}\n\n\t\t// расчитываем offset и limit для элементов\n\t\tif (count($returnData) > 0) {\n\t\t\t$elementOffset = 0;\n\t\t}\n\t\telse {\n\t\t\t$elementOffset = $limitData[0] - $this->totalRowsCount;\n\t\t}\n\n\t\t// для списка разделов элементы не нужны\n\t\tif (static::getHelperClass(AdminSectionListHelper::className()) == static::className()) {\n\t\t\treturn $returnData;\n\t\t}\n\n\t\t$elementLimit = $limitData[1] - count($returnData);\n\t\t$elementModel = static::$model;\n\t\t$elementFilter = $this->arFilter;\n\t\tif(get_called_class() != static::getHelperClass(AdminSectionListHelper::className())) {\n            $elementFilter[$elementEditHelperClass::getSectionField()] = $sectionId;\n        }\n\t\t// добавляем к общему количеству элементов количество элементов\n\t\t$this->totalRowsCount += $elementModel::getCount($this->getElementsFilter($elementFilter));\n\n\t\t// возвращае данные без элементов если разделы занимают всю страницу выборки\n\t\tif (!empty($returnData) && $limitData[0] == 0 && $limitData[1] == $this->totalRowsCount) {\n\t\t\treturn $returnData;\n\t\t}\n\n\t\t$elementSort = array();\n\t\tforeach ($sort as $field => $direction) {\n\t\t\tif (in_array($field, $elementVisibleColumns)) {\n\t\t\t\t$elementSort[$field] = $direction;\n\t\t\t}\n\t\t}\n\n\t\t$elementParams = array(\n\t\t\t'filter' => $this->getElementsFilter($elementFilter),\n\t\t\t'select' => $elementVisibleColumns,\n\t\t\t'order' => $elementSort,\n\t\t);\n\t\tif ($elementLimit > 0 && $elementOffset >= 0) {\n\t\t\t$elementParams['limit'] = $elementLimit;\n\t\t\t$elementParams['offset'] = $elementOffset;\n\t\t\t// добавляем к выборке элементы\n\t\t\t$rsSections = $elementModel::getList($elementParams);\n\n\t\t\twhile ($element = $rsSections->fetch()) {\n\t\t\t\t$element['IS_SECTION'] = false;\n\t\t\t\t$returnData[] = $element;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Вернем результат с первой страницы если на текущей нет элементов.\n\t\t * Для списка элементов аналогичная проверка есть в $this->getLimits()\n\t\t */\n\t\tif (!count($returnData) && $this->totalRowsCount > 0)\n\t\t{\n\t\t\t$this->navParams['navParams']['PAGEN'] = 1;\n\t\t\treturn $this->getMixedData($sectionsVisibleColumns, $elementVisibleColumns, $sort, $raw);\n\t\t}\n\n\t\treturn $returnData;\n\t}\n\n\t/**\n\t * Огранчения выборки из CAdminResult\n\t * @return array\n\t */\n\tprotected function getLimits()\n\t{\n\t\tif ($this->navParams['navParams']['SHOW_ALL']) {\n\t\t\treturn array();\n\t\t}\n\t\telse {\n\t\t\tif (!intval($this->navParams['navParams']['PAGEN']) OR !isset($this->navParams['navParams']['PAGEN'])) {\n\t\t\t\t$this->navParams['navParams']['PAGEN'] = 1;\n\t\t\t}\n\t\t\t$from = $this->navParams['nPageSize'] * ((int)$this->navParams['navParams']['PAGEN'] - 1);\n\n\t\t\t/**\n\t\t\t * Вернем результат с первой страницы если на текущей нет элементов.\n\t\t\t *\n\t\t\t * $this->totalRowsCount еще не заполнен при смешанном отображении элементов и разделов,\n\t\t\t * в $this->>getMixedData() есть отдельная проверка на этот счет\n\t\t\t */\n\t\t\tif ($this->totalRowsCount && $from >= $this->totalRowsCount)\n\t\t\t{\n\t\t\t\t$this->navParams['navParams']['PAGEN'] = 1;\n\t\t\t\t$from = 0;\n\t\t\t}\n\n\t\t\treturn array($from, $this->navParams['nPageSize']);\n\t\t}\n\t}\n\n\t/**\n\t * Очищает название поля от операторов фильтра\n\t * @param string $fieldName названия поля из фильтра\n\t * @return string название поля без без операторов фильтра\n\t */\n\tprotected function escapeFilterFieldName($fieldName)\n\t{\n\t\treturn str_replace(array('!','<', '<=', '>', '>=', '><', '=', '%'), '', $fieldName);\n\t}\n\n\t/**\n\t * Выполняет CDBResult::NavNext с той разницей, что общее количество элементов берется не из count($arResult),\n\t * а из нашего параметра, полученного из SQL-запроса.\n\t * array_slice также не делается.\n\t *\n\t * @param \\CAdminResult $res\n\t */\n\tprotected function customNavStart(&$res)\n\t{\n\t\t$res->NavStart($this->navParams['nPageSize'],\n\t\t\t$this->navParams['navParams']['SHOW_ALL'],\n\t\t\t(int)$this->navParams['navParams']['PAGEN']\n\t\t);\n\t\t// отключаем отображение всех элементов\n\t\t$res->bShowAll = $this->showAll;\n\n\t\t$res->NavRecordCount = $this->totalRowsCount;\n\t\tif ($res->NavRecordCount < 1)\n\t\t\treturn;\n\n\t\tif ($res->NavShowAll)\n\t\t\t$res->NavPageSize = $res->NavRecordCount;\n\n\t\t$res->NavPageCount = floor($res->NavRecordCount / $res->NavPageSize);\n\t\tif ($res->NavRecordCount % $res->NavPageSize > 0)\n\t\t\t$res->NavPageCount++;\n\n\t\t$res->NavPageNomer =\n\t\t\t($res->PAGEN < 1 || $res->PAGEN > $res->NavPageCount\n\t\t\t\t?\n\t\t\t\t(\\CPageOption::GetOptionString(\"main\", \"nav_page_in_session\", \"Y\") != \"Y\"\n\t\t\t\t|| $_SESSION[$res->SESS_PAGEN] < 1\n\t\t\t\t|| $_SESSION[$res->SESS_PAGEN] > $res->NavPageCount\n\t\t\t\t\t?\n\t\t\t\t\t1\n\t\t\t\t\t:\n\t\t\t\t\t$_SESSION[$res->SESS_PAGEN]\n\t\t\t\t)\n\t\t\t\t:\n\t\t\t\t$res->PAGEN\n\t\t\t);\n\t}\n\n\t/**\n\t * Преобразует данные строки, перед тем как добавлять их в список.\n\t *\n\t * @param $data\n\t *\n\t * @see AdminListHelper::getList()\n\t *\n\t * @api\n\t */\n\tprotected function modifyRowData(&$data)\n\t{\n\t}\n\n\t/**\n\t * Настройки строки таблицы.\n\t *\n\t * @param array $data Данные текущей строки БД.\n\t * @param bool|string $class Класс хелпера через метод getUrl которого идет получение ссылки.\n\t *\n\t * @return array Возвращает ссылку на детальную страницу и её название.\n\t *\n\t * @api\n\t */\n\tprotected function getRow($data, $class = false)\n\t{\n\t\tif (empty($class)) {\n\t\t\t$class = static::getHelperClass(AdminEditHelper::className());\n\t\t}\n\t\tif ($this->isPopup()) {\n\t\t\treturn array();\n\t\t}\n\t\telse {\n\t\t\t$query = array_merge($this->additionalUrlParams, array(\n\t\t\t\t'lang' => LANGUAGE_ID,\n\t\t\t\t$this->pk() => $data[$this->pk()]\n\t\t\t));\n\n\t\t\treturn array($class::getUrl($query));\n\t\t}\n\t}\n\n\t/**\n\t * Для каждой ячейки(раздела) таблицы создаёт виджет соответствующего типа.\n\t * Виджет подготавливает необходимый HTML для списка.\n\t *\n\t * @param \\CAdminListRow $row\n\t * @param $code Сивольный код поля.\n\t * @param $data Данные текущей строки.\n\t *\n\t * @throws Exception\n\t *\n\t * @see HelperWidget::generateRow()\n\t */\n\tprotected function addRowSectionCell($row, $code, $data)\n\t{\n\t\t$sectionEditHelper = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\tif (!isset($this->sectionFields[$code]['WIDGET'])) {\n\t\t\t$error = str_replace('#CODE#', $code, 'Can\\'t create widget for the code \"#CODE#\"');\n\t\t\tthrow new Exception($error, Exception::CODE_NO_WIDGET);\n\t\t}\n\n\t\t/**\n\t\t * @var \\DigitalWand\\AdminHelper\\Widget\\HelperWidget $widget\n\t\t */\n\t\t$widget = $this->sectionFields[$code]['WIDGET'];\n\n\t\t$widget->setHelper($this);\n\t\t$widget->setCode($code);\n\t\t$widget->setData($data);\n\t\t$widget->setEntityName($sectionEditHelper::getModel());\n\n\t\t$this->setContext(AdminListHelper::OP_ADD_ROW_CELL);\n\t\t$widget->generateRow($row, $data);\n\t}\n\n\t/**\n\t * Возвращает массив со списком действий при клике правой клавишей мыши на строке таблицы\n\t * По-умолчанию:\n\t * <ul>\n\t * <li> Редактировать элемент </li>\n\t * <li> Удалить элемент </li>\n\t * <li> Если это всплывающее окно - запустить кастомную JS-функцию. </li>\n\t * </ul>\n\t *\n\t * @param $data Данные текущей строки.\n\t * @param $section Признак списка для раздела.\n\t *\n\t * @return array\n\t *\n\t * @see CAdminListRow::AddActions\n\t *\n\t * @api\n\t */\n\tprotected function getRowActions($data, $section = false)\n\t{\n\t\t$actions = array();\n\n\t\tif ($this->isPopup()) {\n\t\t\t$jsData = \\CUtil::PhpToJSObject($data);\n\t\t\t$actions['select'] = array(\n\t\t\t\t'ICON' => 'select',\n\t\t\t\t'DEFAULT' => true,\n\t\t\t\t'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_SELECT'),\n\t\t\t\t\"ACTION\" => 'javascript:' . $this->popupClickFunctionName . '(' . $jsData . ')'\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t$viewQueryString = 'module=' . static::getModule() . '&view=' . static::getViewName() . '&entity=' . static::getEntityCode();\n\t\t\t$query = array_merge($this->additionalUrlParams,\n\t\t\t\tarray($this->pk() => $data[$this->pk()]));\n\t\t\tif ($this->hasWriteRights()) {\n\t\t\t\t$sectionHelperClass = static::getHelperClass(AdminSectionEditHelper::className());\n\t\t\t\t$editHelperClass = static::getHelperClass(AdminEditHelper::className());\n\n\t\t\t\t$actions['edit'] = array(\n\t\t\t\t\t'ICON' => 'edit',\n\t\t\t\t\t'DEFAULT' => true,\n\t\t\t\t\t'TEXT' => Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_EDIT'),\n\t\t\t\t\t'ACTION' => $this->list->ActionRedirect($section ? $sectionHelperClass::getUrl($query) : $editHelperClass::getUrl($query))\n\t\t\t\t);\n\t\t\t}\n\t\t\tif ($this->hasDeleteRights()) {\n\t\t\t\t$actions['delete'] = array(\n\t\t\t\t\t'ICON' => 'delete',\n\t\t\t\t\t'TEXT' => Loc::getMessage(\"DIGITALWAND_ADMIN_HELPER_LIST_DELETE\"),\n\t\t\t\t\t'ACTION' => \"if(confirm('\" . Loc::getMessage('DIGITALWAND_ADMIN_HELPER_LIST_DELETE_CONFIRM') . \"')) \" . $this->list->ActionDoGroup($data[$this->pk()],\n\t\t\t\t\t\t\t$section ? \"delete-section\" : \"delete\", $viewQueryString)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn $actions;\n\t}\n\n\t/**\n\t * Для каждой ячейки таблицы создаёт виджет соответствующего типа. Виджет подготавливает необходимый HTML-код\n\t * для списка.\n\t *\n\t * @param \\CAdminListRow $row Объект строки списка записей.\n\t * @param string $code Сивольный код поля.\n\t * @param array $data Данные текущей строки.\n\t * @param bool $virtualCode\n\t *\n\t * @throws Exception\n\t *\n\t * @see HelperWidget::generateRow()\n\t */\n\tprotected function addRowCell($row, $code, $data, $virtualCode = false)\n\t{\n\t\t$widget = $this->createWidgetForField($code, $data);\n\t\t$this->setContext(AdminListHelper::OP_ADD_ROW_CELL);\n\n\t\t// устанавливаем виртуальный код ячейки, используется при слиянии столбцов\n\t\tif ($virtualCode) {\n\t\t\t$widget->setCode($virtualCode);\n\t\t}\n\n\t\t$widget->generateRow($row, $data);\n\n\t\tif ($virtualCode) {\n\t\t\t$widget->setCode($code);\n\t\t}\n\t}\n\n\t/**\n\t * Производит выборку данных. Функцию стоит переопределить в случае, если необходима своя логика, и её нельзя\n\t * вынести в класс модели.\n\t *\n\t * @param DataManager $className\n\t * @param array $filter\n\t * @param array $select\n\t * @param array $sort\n\t * @param array $raw\n\t *\n\t * @return Result\n\t *\n\t * @api\n\t */\n\tprotected function getData($className, $filter, $select, $sort, $raw)\n\t{\n\t\t$limits = $this->getLimits();\n\t\t$parameters = array(\n\t\t\t'filter' => $this->getElementsFilter($filter),\n\t\t\t'select' => $select,\n\t\t\t'order' => $sort,\n\t\t\t'offset' => $limits[0],\n\t\t\t'limit' => $limits[1],\n\t\t);\n\n\t\t/** @var Result $res */\n\t\t$res = $className::getList($parameters);\n\n\t\treturn $res;\n\t}\n\n\t/**\n\t * Подготавливает массив с настройками футера таблицы Bitrix\n\t * @param \\CAdminResult $res - результат выборки данных\n\t * @see \\CAdminList::AddFooter()\n\t * @return array[]\n\t */\n\tprotected function getFooter($res)\n\t{\n\t\treturn array(\n\t\t\t$this->getButton('MAIN_ADMIN_LIST_SELECTED', array(\"value\" => $res->SelectedRowsCount())),\n\t\t\t$this->getButton('MAIN_ADMIN_LIST_CHECKED', array(\"value\" => $res->SelectedRowsCount()), array(\n\t\t\t\t\"counter\" => true,\n\t\t\t\t\"value\" => \"0\",\n\t\t\t)),\n\t\t);\n\t}\n\n\t/**\n\t * Выводит форму фильтрации списка\n\t */\n\tpublic function createFilterForm()\n\t{\n\t\t//нужно пробрасывать параметр popup в форму, если она является таковой\n\t\tif($this->isPopup())\n\t\t{\n\t\t\t$this->additionalUrlParams['popup'] = 'Y';\n\t\t}\n\n\t\t$this->setContext(AdminListHelper::OP_CREATE_FILTER_FORM);\n\t\tprint ' <form name=\"find_form\" method=\"GET\" action=\"' . static::getUrl($this->additionalUrlParams) . '?\">';\n\n\t\t$sectionHelper = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\tif($sectionHelper) {\n\t\t\t$sectionsInterfaceSettings = static::getInterfaceSettings($sectionHelper::getViewName());\n\t\t\tforeach($this->arFilterOpts as $code => $name) {\n\t\t\t\tif(!empty($this->tableColumnsMap[$code])) {\n                    $newName = $sectionsInterfaceSettings['FIELDS'][$this->tableColumnsMap[$code]]['WIDGET']\n                        ->getSettings('TITLE');\n                    $this->arFilterOpts[$code] = $newName;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$oFilter = new \\CAdminFilter($this->getListTableID() . '_filter', $this->arFilterOpts);\n\t\t$oFilter->Begin();\n\n\t\tforeach ($this->arFilterOpts as $code => $name) {\n\t\t\t$widget = $this->createWidgetForField($code);\n\t\t\tif($widget->getSettings('TITLE') != $this->arFilterOpts[$code]) {\n\t\t\t\t$widget->setSetting('TITLE', $this->arFilterOpts[$code]);\n\t\t\t}\n\t\t\t$widget->showFilterHtml();\n\t\t}\n\n\t\t$oFilter->Buttons(array(\n\t\t\t\"table_id\" => $this->getListTableID(),\n\t\t\t\"url\" => static::getUrl($this->additionalUrlParams),\n\t\t\t\"form\" => \"find_form\",\n\t\t));\n\t\t$oFilter->End();\n\n\t\tprint '</form>';\n\t}\n\n\t/**\n\t * Возвращает ID таблицы, который не должен конфликтовать с ID в других разделах админки, а также нормально\n\t * парситься в JS\n\t *\n\t * @return string\n\t */\n\tprotected function getListTableID()\n\t{\n\t\treturn str_replace('.', '', static::$tablePrefix . $this->table());\n\t}\n\n\t/**\n\t * Выводит сформированный список.\n\t * Сохраняет обработанный GET-запрос в сессию\n\t */\n\tpublic function show()\n\t{\n\t\tif (!$this->hasReadRights()) {\n\t\t\t$this->addErrors(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_ACCESS_FORBIDDEN'));\n\t\t\t$this->showMessages();\n\n\t\t\treturn false;\n\t\t}\n\t\t$this->showMessages();\n\t\t$this->list->DisplayList();\n\n\t\tif ($this->isPopup()) {\n\t\t\tprint $this->popupClickFunctionCode;\n\t\t}\n\n\t\t$this->saveGetQuery();\n\t}\n\n\t/**\n\t * Сохраняет параметры запроса для поторного использования после возврата с других страниц (к примеру, после\n\t * перехода с детальной обратно в список - чтобы вернуться в точности в тот раздел, с которого ранее ушли)\n\t */\n\tprivate function saveGetQuery()\n\t{\n\t\t$_SESSION['LAST_GET_QUERY'][get_called_class()] = $_GET;\n\t}\n\n\t/**\n\t * Восстанавливает последний GET-запрос, если в текущем задан параметр restore_query=Y\n\t */\n\tprivate function restoreLastGetQuery()\n\t{\n\t\tif (!isset($_SESSION['LAST_GET_QUERY'][get_called_class()])\n\t\t\tOR !isset($_REQUEST['restore_query'])\n\t\t\tOR $_REQUEST['restore_query'] != 'Y'\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\t$_GET = array_merge($_GET, $_SESSION['LAST_GET_QUERY'][get_called_class()]);\n\t\t$_REQUEST = array_merge($_REQUEST, $_SESSION['LAST_GET_QUERY'][get_called_class()]);\n\t}\n\n\t/**\n\t * @inheritdoc\n\t */\n\tpublic static function getUrl(array $params = array())\n\t{\n\t\treturn static::getViewURL(static::getViewName(), static::$listPageUrl, $params);\n\t}\n\n\t/**\n\t * Кастомизация фильтра разделов\n\t * @param $filter\n\t * @return mixed\n\t */\n\tprotected function getSectionsFilter(array $filter)\n\t{\n\t\treturn $filter;\n\t}\n\n\t/**\n\t * Кастомизация фильтра элементов\n\t * @param $filter\n\t * @return mixed\n\t */\n\tprotected function getElementsFilter($filter)\n\t{\n\t\treturn $filter;\n\t}\n\n\t/**\n\t * Список идентификаторов для групповых операций\n\t *\n\t * @return array\n\t */\n\tprotected function getIds()\n\t{\n\t\t$className = static::getModel();\n\t\tif (isset($_REQUEST['model'])) {\n\t\t\t$className = $_REQUEST['model'];\n\t\t}\n\n\t\t$sectionEditHelperClass = $this->getHelperClass(AdminSectionEditHelper::className());\n\t\tif ($sectionEditHelperClass && !isset($_REQUEST['model-section'])) {\n\t\t\t$sectionClassName = $sectionEditHelperClass::getModel();\n\t\t}\n\t\telse {\n\t\t\t$sectionClassName = $_REQUEST['model-section'];\n\t\t}\n\n        $pkValue = $this->getPk();\n        if (isset($pkValue[$this->pk()]) && is_array($pkValue[$this->pk()])) {\n\t\t\tforeach ($pkValue[$this->pk()] as $id) {\n\t\t\t\t$class = strpos($id, 's') === 0 ? $sectionClassName : $className;\n\t\t\t\t$ids[] = $this->getCommonPrimaryFilterById($class, null, $id);\n\t\t\t}\n\t\t} else {\n\t\t\t$ids = array($this->getPk());\n\t\t}\n\n\t\treturn $ids;\n\t}\n\n\t/**\n\t * Получить оставшуюся часть составного первичного ключа\n\t *\n\t * @param $className\n\t * @param null $sectionClassName\n\t * @param $id\n\t * @return array\n\t */\n\tprotected function getCommonPrimaryFilterById($className, $sectionClassName = null, $id)\n\t{\n\t\tif ($this->getHelperClass($sectionClassName) && strpos($id, 's') === 0) {\n\t\t\t$primary = $sectionClassName::getEntity()->getPrimaryArray();\n\t\t} else {\n\t\t\t$primary = $className::getEntity()->getPrimaryArray();\n\t\t}\n\n\t\tif (count($primary) === 1) {\n\t\t\treturn array($this->pk() => $id);\n\t\t}\n\n\t\t$key = $this->getPk();\n\t\t$key[$this->pk()] = $id;\n\n\t\treturn $key;\n\t}\n}\n"
  },
  {
    "path": "lib/helper/AdminSectionEditHelper.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\n/**\n * Класс-обертка для хелпера редактирования разделов.\n * \n * Все хелперы отвечающие за редактирование разделов должны наследовать от этого класса. Название класса используется \n * для определения к какому типу принадлежит хелпер:\n * - список элементов,\n * - редактирования элементов,\n * - список разделов,\n * - редактирование раздела.\n * \n * @see AdminBaseHelper::getHelperClass\n * \n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nclass AdminSectionEditHelper extends AdminEditHelper\n{\n}"
  },
  {
    "path": "lib/helper/AdminSectionListHelper.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\n/**\n * Класс-обертка для хелпера списка разделов.\n * \n * Все хелперы отвечающие за вывод списка разделов должны наследовать от этого класса.\n * \n * @see AdminSectionEditHelper\n * \n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Artem Yarygin <artx19@yandex.ru>\n */\nclass AdminSectionListHelper extends AdminListHelper\n{\n}"
  },
  {
    "path": "lib/helper/Exception.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Helper;\n\nclass Exception extends \\Exception\n{\n\tconst CODE_NO_WIDGET = 1;\n\tconst CODE_NO_HL_ENTITY_INFORMATION = 2;\n\n}"
  },
  {
    "path": "lib/widget/CheckboxWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Виджет \"галочка\"\n *\n * Доступные опции:\n * <ul>\n * <li> <b>FIELD_TYPE</b> - Тип данных для хранения булевых значений (строка, целые числа, булево) </li>\n * </ul>\n */\nclass CheckboxWidget extends HelperWidget\n{\n    /**\n     * Строковый тип чекбокса (Y/N)\n     * FIXME: если верить битриксу, мжет быть ещё и экзотичный случай со строками \"true\" и \"false\"!\n     */\n    const TYPE_STRING = 'string';\n    /**\n     * Целочисленный тип чекбокса (1/0)\n     */\n    const TYPE_INT = 'integer';\n    /**\n     * Булевый тип чекбокса\n     */\n    const TYPE_BOOLEAN = 'boolean';\n    /**\n     * Значение положительного варианта для строкового чекбокса\n     */\n    const TYPE_STRING_YES = 'Y';\n    /**\n     * Значение отрицательного варианта для строкового чекбокса\n     */\n    const TYPE_STRING_NO = 'N';\n    /**\n     * Значение положительного варианта для целочисленного чекбокса\n     */\n    const TYPE_INT_YES = 1;\n    /**\n     * Значение отрицательного варианта для целочисленного чекбокса\n     */\n    const TYPE_INT_NO = 0;\n\n    protected static $defaults = array(\n        'EDIT_IN_LIST' => true\n    );\n\n    /**\n     * @inheritdoc\n     */\n    public function generateRow(&$row, $data)\n    {\n        $modeType = $this->getCheckboxType();\n\n        $globalYes = '';\n        $globalNo = '';\n\n        switch ($modeType) {\n            case self::TYPE_STRING: {\n                $globalYes = self::TYPE_STRING_YES;\n                $globalNo = self::TYPE_STRING_NO;\n                break;\n            }\n            case self::TYPE_INT:\n            case self::TYPE_BOOLEAN: {\n                $globalYes = self::TYPE_INT_YES;\n                $globalNo = self::TYPE_INT_NO;\n                break;\n            }\n        }\n\n        if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {\n            $checked = intval($this->getValue() == $globalYes) ? 'checked' : '';\n            $js = 'var input = document.getElementsByName(\\'' . $this->getEditableListInputName() . '\\')[0];\n                   input.value = this.checked ? \\'' . $globalYes . '\\' : \\'' . $globalNo . '\\';';\n            $editHtml = '<input type=\"checkbox\"\n                                value=\"' . static::prepareToTagAttr($this->getValue()) . '\" ' . $checked . '\n                                onchange=\"' . $js . '\"/>\n                         <input type=\"hidden\"\n                                value=\"' . static::prepareToTagAttr($this->getValue()) . '\"\n                                name=\"' . $this->getEditableListInputName() . '\" />';\n            $row->AddEditField($this->getCode(), $editHtml);\n        }\n\n        if (intval($this->getValue() == $globalYes)) {\n            $value = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES');\n        } else {\n            $value = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');\n        }\n\n        $row->AddViewField($this->getCode(), $value);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function showFilterHtml()\n    {\n        $filterHtml = '<tr>';\n        $filterHtml .= '<td>' . $this->getSettings('TITLE') . '</td>';\n        $filterHtml .= '<td> <select  name=\"' . $this->getFilterInputName() . '\">';\n        $filterHtml .= '<option value=\"\"></option>';\n\n        $modeType = $this->getCheckboxType();\n\n        $langYes = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES');\n        $langNo = Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');\n\n        switch ($modeType) {\n            case self::TYPE_STRING: {\n                $filterHtml .= '<option value=\"' . self::TYPE_STRING_YES . '\">' . $langYes . '</option>';\n                $filterHtml .= '<option value=\"' . self::TYPE_STRING_NO . '\">' . $langNo . '</option>';\n                break;\n            }\n            case self::TYPE_INT:\n            case self::TYPE_BOOLEAN: {\n                $filterHtml .= '<option value=\"' . self::TYPE_INT_YES . '\">' . $langYes . '</option>';\n                $filterHtml .= '<option value=\"' . self::TYPE_INT_NO . '\">' . $langNo . '</option>';\n                break;\n            }\n        }\n\n        $filterHtml .= '</select></td>';\n        $filterHtml .= '</tr>';\n\n        print $filterHtml;\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function getValueReadonly()\n    {\n        $code = $this->getCode();\n        $value = isset($this->data[$code]) ? $this->data[$code] : null;\n        $modeType = $this->getCheckboxType();\n\n        switch ($modeType) {\n            case static::TYPE_STRING: {\n                $value = $value == 'Y' ? Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES') : Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');\n                break;\n            }\n            case static::TYPE_INT:\n            case static::TYPE_BOOLEAN: {\n                $value = $value ? Loc::getMessage('DIGITALWAND_AH_CHECKBOX_YES') : Loc::getMessage('DIGITALWAND_AH_CHECKBOX_NO');\n                break;\n            }\n        }\n\n        return static::prepareToOutput($value);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function processEditAction()\n    {\n        parent::processEditAction();\n\n        if ($this->getCheckboxType() === static::TYPE_BOOLEAN) {\n            $this->data[$this->getCode()] = (bool)$this->data[$this->getCode()];\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function getEditHtml()\n    {\n        $html = '';\n\n        $modeType = $this->getCheckboxType();\n\n        switch ($modeType) {\n            case static::TYPE_STRING: {\n                $checked = $this->getValue() == self::TYPE_STRING_YES ? 'checked' : '';\n\n                $html = '<input type=\"hidden\" name=\"' . $this->getEditInputName() . '\" value=\"' . self::TYPE_STRING_NO . '\" />';\n                $html .= '<input type=\"checkbox\" name=\"' . $this->getEditInputName() . '\" value=\"' . self::TYPE_STRING_YES . '\" ' . $checked . ' />';\n                break;\n            }\n            case static::TYPE_INT:\n            case static::TYPE_BOOLEAN: {\n                $checked = $this->getValue() == self::TYPE_INT_YES ? 'checked' : '';\n\n                $html = '<input type=\"hidden\" name=\"' . $this->getEditInputName() . '\" value=\"' . self::TYPE_INT_NO . '\" />';\n                $html .= '<input type=\"checkbox\" name=\"' . $this->getEditInputName() . '\" value=\"' . self::TYPE_INT_YES . '\" ' . $checked . ' />';\n                break;\n            }\n        }\n\n        return $html;\n    }\n\n    /**\n     * Получить тип чекбокса по типу поля.\n     * По-умолчанию возвращает TYPE_STRING\n     * @return mixed\n     */\n    public function getCheckboxType()\n    {\n        $settingsFieldType = $this->getSettings('FIELD_TYPE');\n        $checkTypes = array(static::TYPE_STRING, static::TYPE_BOOLEAN, static::TYPE_INT);\n        $columnName = $this->getCode();\n\n        if ($settingsFieldType AND in_array($settingsFieldType, $checkTypes)) {\n            return $settingsFieldType;\n\n        } else {\n            $entity = $this->getEntityName();\n            $entityMap = $entity::getMap();\n\n            if (!isset($entityMap[$columnName])) {\n                foreach ($entityMap as $field/** @var \\Bitrix\\Main\\Entity\\ScalarField $field */) {\n                    if($field instanceof \\Bitrix\\Main\\Entity\\ReferenceField)\n                        continue;\n                    if (is_object($field) AND $field->getColumnName() === $columnName) {\n                        return $field->getDataType(); //FIXME: deprecated? На что нужно заменить?\n                    }\n                }\n\n            } elseif (isset($entityMap[$columnName]['values']) AND\n                is_array($entityMap[$columnName]['values']) AND\n                count($entityMap[$columnName]['values']) == 2\n            ) {\n                $value = reset($entityMap[$columnName]['values']);\n                if (is_string($value)) {\n                    return static::TYPE_STRING;\n                } elseif (is_bool($value) OR is_integer($value)) {\n                    return static::TYPE_BOOLEAN;\n                }\n\n            } elseif (isset($entityMap[$columnName]['data_type'])) {\n                return $entityMap[$columnName]['data_type'];\n\n            }\n\n            /**\n             * Теоретически, рзработчик мог ввести полную хрень, указывая варианты значений для сущности\n             * В этом случае ни одна проверка выше не сработает.\n             * FIXME: а нужен ли эксепшн?\n             */\n//            throw new \\Bitrix\\Main\\ArgumentTypeException(\"Unknown checkbox type\");\n        }\n\n        return static::TYPE_STRING;\n    }\n}\n"
  },
  {
    "path": "lib/widget/ComboBoxWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Выпадающий список.\n *\n * Доступные опции:\n * <ul>\n * <li> STYLE - inline-стили</li>\n * <li> VARIANTS - массив с вариантами значений или функция для их получения в формате ключ=>заголовок\n *        Например:\n *            [\n *                1=>'Первый пункт',\n *                2=>'Второй пункт'\n *            ]\n * </li>\n * <li> DEFAULT_VARIANT - ID варианта по-умолчанию</li>\n * </ul>\n */\nclass ComboBoxWidget extends HelperWidget\n{\n    static protected $defaults = array(\n        'EDIT_IN_LIST' => true\n    );\n\n    /**\n     * @inheritdoc\n     *\n     * @see AdminEditHelper::showField();\n     *\n     * @param bool $forFilter\n     *\n     * @return mixed\n     */\n    protected function getEditHtml()\n    {\n        return $this->getComboBox();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function getMultipleEditHtml()\n    {\n        return $this->getComboBox(true);\n    }\n\n    /**\n     * Возвращает ХТМЛ-код с комбобоксом.\n     *\n     * @param bool $multiple Множественный режим.\n     * @param bool $forFilter Комбобокс будет выводиться в блоке с фильтром.\n     *\n     * @return string\n     */\n    protected function getComboBox($multiple = false, $forFilter = false)\n    {\n        if ($multiple) {\n            $value = $this->getMultipleValue();\n        } else {\n            $value = $this->getValue();\n        }\n\n        $style = $this->getSettings('STYLE');\n\n        $variants = $this->getVariants();\n\n        if (!$multiple)\n        {\n            array_unshift($variants, array(\n                'ID' => null,\n                'TITLE' => null\n            ));\n        }\n\n        if (empty($variants)) {\n            $comboBox = Loc::getMessage('DIGITALWAND_AH_MISSING_VARIANTS');\n        } else {\n            $name = $forFilter ? $this->getFilterInputName() : $this->getEditInputName();\n            $comboBox = '<select name=\"' . $name . ($multiple ? '[]' : null) . '\"\n                '. ($multiple ? 'multiple=\"multiple\"' : null) . '\n                style=\"' . $style . '\">';\n\n            foreach ($variants as $variant) {\n                $selected = false;\n\n                if ($variant['ID'] == $value) {\n                    $selected = true;\n                }\n\n                if ($multiple && in_array($variant['ID'], $value)) {\n                    $selected = true;\n                } elseif ($variant['ID'] === $value) {\n                    $selected = true;\n                }\n\n                $comboBox .= \"<option value='\" . static::prepareToTagAttr($variant['ID']) . \"' \" . ($selected ? \"selected\" : \"\") . \">\"\n                    . static::prepareToTagAttr($variant['TITLE']) . \"</option>\";\n            }\n\n            $comboBox .= '</select>';\n        }\n\n        return $comboBox;\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function getValueReadonly()\n    {\n        $variants = $this->getVariants();\n        $value = $variants[$this->getValue()]['TITLE'];\n\n        return static::prepareToOutput($value);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function getMultipleValueReadonly()\n    {\n        $variants = $this->getVariants();\n        $values = $this->getMultipleValue();\n        $result = '';\n\n        if (empty($variants)) {\n            $result = Loc::getMessage('DIGITALWAND_AH_MISSING_VARIANTS');\n        } else {\n            foreach ($variants as $id => $data) {\n                $name = strlen($data[\"TITLE\"]) > 0 ? $data[\"TITLE\"] : \"\";\n\n                if (in_array($id, $values)) {\n                    $result .= static::prepareToOutput($name) . '<br/>';\n                }\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Возвращает массив в следующем формате:\n     * <code>\n     * array(\n     *      '123' => array('ID' => 123, 'TITLE' => 'ololo'),\n     *      '456' => array('ID' => 456, 'TITLE' => 'blablabla'),\n     *      '789' => array('ID' => 789, 'TITLE' => 'pish-pish'),\n     * )\n     * </code>\n     * \n     * Результат будет выводиться в комбобоксе.\n     * @return array\n     */\n    protected function getVariants()\n    {\n        $variants = $this->getSettings('VARIANTS');\n\n        if (is_callable($variants)) {\n            $var = $variants();\n\n            if (is_array($var)) {\n                return $this->formatVariants($var);\n            }\n        }elseif (is_array($variants) AND !empty($variants)) {\n            return $this->formatVariants($variants);\n        }\n\n        return array();\n    }\n\n    /**\n     * Приводит варианты к нужному формату, если они заданы в виде одномерного массива.\n     *\n     * @param $variants\n     *\n     * @return array\n     */\n    protected function formatVariants($variants)\n    {\n        $formatted = array();\n\n        foreach ($variants as $id => $data) {\n            if (!is_array($data)) {\n                $formatted[$id] = array(\n                    'ID' => $id,\n                    'TITLE' => $data\n                );\n            }\n        }\n\n        return $formatted;\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function generateRow(&$row, $data)\n    {\n        if ($this->settings['EDIT_IN_LIST'] AND !$this->settings['READONLY']) {\n            $row->AddInputField($this->getCode(), array('style' => 'width:90%'));\n        } else {\n            $row->AddViewField($this->getCode(), $this->getValueReadonly());\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function showFilterHtml()\n    {\n        print '<tr>';\n        print '<td>' . $this->getSettings('TITLE') . '</td>';\n        print '<td>' . $this->getComboBox(false, true) . '</td>';\n        print '</tr>';\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function processEditAction()\n    {\n        if ($this->getSettings('MULTIPLE')) {\n            $sphere = $this->data[$this->getCode()];\n            unset($this->data[$this->getCode()]);\n\n            foreach ($sphere as $sphereKey) {\n                $this->data[$this->getCode()][] = array('VALUE' => $sphereKey);\n            }\n        }\n\n        parent::processEditAction();\n    }\n}\n"
  },
  {
    "path": "lib/widget/DateTimeWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nclass DateTimeWidget extends HelperWidget\n{\n\tstatic protected $defaults = array(\n\t\t'FILTER' => 'BETWEEN',\n\t);\n\t\n\t/**\n\t * Генерирует HTML для редактирования поля\n\t * @see AdminEditHelper::showField();\n\t * @return mixed\n\t */\n\tprotected function getEditHtml()\n\t{\n\t\treturn \\CAdminCalendar::CalendarDate($this->getEditInputName(), ConvertTimeStamp(strtotime($this->getValue()), \"FULL\"), 10, true);\n\t}\n\n\t/**\n\t * Генерирует HTML для поля в списке\n\t * @see AdminListHelper::addRowCell();\n\t * @param CAdminListRow $row\n\t * @param array $data - данные текущей строки\n\t * @return mixed\n\t */\n\tpublic function generateRow(&$row, $data)\n\t{\n\t\tif (isset($this->settings['EDIT_IN_LIST']) AND $this->settings['EDIT_IN_LIST'])\n\t\t{\n\t\t\t$row->AddCalendarField($this->getCode());\n\t\t}\n\t\telse\n\t\t{\n\t\t\t$arDate = ParseDateTime($this->getValue());\n\n\t\t\tif ($arDate['YYYY'] < 10)\n\t\t\t{\n\t\t\t\t$stDate = '-';\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$stDate = ConvertDateTime($this->getValue(), \"DD.MM.YYYY HH:MI:SS\", \"ru\");\n\t\t\t}\n\n\t\t\t$row->AddViewField($this->getCode(), $stDate);\n\t\t}\n\t}\n\n\t/**\n\t * Генерирует HTML для поля фильтрации\n\t * @see AdminListHelper::createFilterForm();\n\t * @return mixed\n\t */\n\tpublic function showFilterHtml()\n\t{\n\t\tlist($inputNameFrom, $inputNameTo) = $this->getFilterInputName();\n\t\tprint '<tr>';\n\t\tprint '<td>' . $this->settings['TITLE'] . '</td>';\n\t\tprint '<td width=\"0%\" nowrap>' . CalendarPeriod($inputNameFrom, $$inputNameFrom, $inputNameTo, $$inputNameTo, \"find_form\") . '</td>';\n\t}\n\n\t/**\n\t * Сконвертируем дату в формат Mysql\n\t * @return boolean\n\t */\n\tpublic function processEditAction()\n\t{\n\t\ttry\n\t\t{\n\t\t\t$this->setValue(new \\Bitrix\\Main\\Type\\Datetime($this->getValue()));\n\t\t} catch (\\Exception $e)\n\t\t{\n\t\t}\n\t\tif (!$this->checkRequired())\n\t\t{\n\t\t\t$this->addError('REQUIRED_FIELD_ERROR');\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "lib/widget/FileWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse Bitrix\\Main\\UI\\FileInput;\nuse Bitrix\\Main\\Application;\nuse CTempFile;\n\n/**\n * Для множественного поля в таблице должен быть столбец FILE_ID.\n * Настройки класса:\n * <ul>\n * <li><b>DESCRIPTION_FIELD</b> - bool нужно ли поле описания</li>\n * <li><b>MULTIPLE</b> - bool является ли поле множественным</li>\n * <li><b>IMAGE</b> - bool отображать ли изображение файла, для старого вида отображения</li>\n * </ul>\n */\nclass FileWidget extends HelperWidget\n{\n    protected static $defaults = array(\n        'IMAGE' => false,\n        'DESCRIPTION_FIELD' => false,\n        'EDIT_IN_LIST' => false,\n        'FILTER' => false,\n        'UPLOAD' => true,\n        'MEDIALIB' => true,\n        'FILE_DIALOG' => true,\n        'CLOUD' => true,\n        'DELETE' => true,\n        'EDIT' => true,\n    );\n\n    /**\n     * {@inheritdoc}\n     */\n    public function __construct(array $settings = array())\n    {\n        Loc::loadMessages(__FILE__);\n        \n        parent::__construct($settings);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getEditHtml()\n    {\n        if (class_exists('\\Bitrix\\Main\\UI\\FileInput', true) && $this->getSettings('IMAGE') === true) {\n            $html = FileInput::createInstance(array(\n                'name' => $this->getEditInputName('_FILE'),\n                'description' => $this->getSettings('DESCRIPTION_FIELD'),\n                'upload' => $this->getSettings('UPLOAD'),\n                'allowUpload' => 'I',\n                'medialib' => $this->getSettings('MEDIALIB'),\n                'fileDialog' => $this->getSettings('FILE_DIALOG'),\n                'cloud' => $this->getSettings('CLOUD'),\n                'delete' => $this->getSettings('DELETE'),\n                'edit' => $this->getSettings('EDIT'),\n                'maxCount' => 1\n            ))->show($this->getValue());\n        } else {\n            $html = \\CFileInput::Show($this->getEditInputName('_FILE'), ($this->getValue() > 0 ? $this->getValue() : 0),\n                array(\n                    'IMAGE' => $this->getSettings('IMAGE') === true ? 'Y' : 'N',\n                    'PATH' => 'Y',\n                    'FILE_SIZE' => 'Y',\n                    'ALLOW_UPLOAD' => 'I',\n                ), array(\n                    'upload' => $this->getSettings('UPLOAD'),\n                    'medialib' => $this->getSettings('MEDIALIB'),\n                    'file_dialog' => $this->getSettings('FILE_DIALOG'),\n                    'cloud' => $this->getSettings('CLOUD'),\n                    'del' => $this->getSettings('DELETE'),\n                    'description' => $this->getSettings('DESCRIPTION_FIELD'),\n                )\n            );\n        }\n\n        if ($this->getValue()) {\n            $html .= '<input type=\"hidden\" name=\"' . $this->getEditInputName() . '\" value=' . $this->getValue() . '>';\n        }\n\n        return $html;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getMultipleEditHtml()\n    {\n        $inputHidden = array();\n        $inputName = array();\n        \n        if (!empty($this->data['ID'])) {\n            $entityName = $this->entityName;\n            \n            $rsEntityData = $entityName::getList(array(\n                'select' => array('REFERENCE_' => $this->getCode() . '.*'),\n                'filter' => array('=ID' => $this->data['ID'])\n            ));\n\n            while ($referenceData = $rsEntityData->fetch()) {\n                $inputName[$this->code . '[' . $referenceData['REFERENCE_ID'] . ']'] = $referenceData['REFERENCE_VALUE'];\n                $inputHidden[$referenceData['REFERENCE_ID']] = $referenceData['REFERENCE_VALUE'];\n            }\n        }\n\n        if (class_exists('\\Bitrix\\Main\\UI\\FileInput', true) && $this->getSettings('IMAGE') === true) {\n            $html = \\Bitrix\\Main\\UI\\FileInput::createInstance(array(\n                'name' => $this->code . '[n#IND#]',\n                'description' => $this->getSettings('DESCRIPTION_FIELD'),\n                'upload' => $this->getSettings('UPLOAD'),\n                'allowUpload' => 'I',\n                'medialib' => $this->getSettings('MEDIALIB'),\n                'fileDialog' => $this->getSettings('FILE_DIALOG'),\n                'cloud' => $this->getSettings('CLOUD'),\n                'delete' => $this->getSettings('DELETE'),\n                'edit' => $this->getSettings('EDIT')\n            ))->show($inputName);\n        } else {\n            $html = \\CFileInput::ShowMultiple($inputName, $this->code . '[n#IND#]',\n                array(\n                    'IMAGE' => $this->getSettings('IMAGE') === true ? 'Y' : 'N',\n                    'PATH' => 'Y',\n                    'FILE_SIZE' => 'Y',\n                    'DIMENSIONS' => 'Y',\n                    'IMAGE_POPUP' => 'Y',\n                ), \n                false, \n                array(\n                    'upload' => $this->getSettings('UPLOAD'),\n                    'medialib' => $this->getSettings('MEDIALIB'),\n                    'file_dialog' => $this->getSettings('FILE_DIALOG'),\n                    'cloud' => $this->getSettings('CLOUD'),\n                    'del' => $this->getSettings('DELETE'),\n                    'description' => $this->getSettings('DESCRIPTION_FIELD'),\n                )\n            );\n        }\n\n        foreach ($inputHidden as $key => $input) {\n            if (!empty($input)) {\n                $html .= '<input type=\"hidden\" name=\"' . $this->code . '[' . $key . '][ID]\" value=' . $key . '>\n\t\t\t\t\t<input type=\"hidden\" name=\"' . $this->code . '[' . $key . '][VALUE]\" value=' . $input . '>';\n            }\n        }\n\n        return $html;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function generateRow(&$row, $data)\n    {\n        $html = '';\n        \n        if ($this->getSettings('MULTIPLE')) {\n            \n        } else {\n            $path = \\CFile::GetPath($data[$this->code]);\n            $rsFile = \\CFile::GetByID($data[$this->code]);\n            $file = $rsFile->Fetch();\n\n            if ($path) {\n                $html = '<a href=\"' . $path . '\" >' . $file['FILE_NAME'] . ' (' . $file['FILE_DESCRIPTION'] . ')' . '</a>';\n            }\n            \n            $row->AddViewField($this->code, $html);\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function showFilterHtml()\n    {\n        // TODO: Implement genFilterHTML() method.\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function processEditAction()\n    {\n        if ($this->getSettings('MULTIPLE')) {\n            if ($this->getSettings('READONLY') === true) {\n                //удаляем все добавленные файлы в режиме только для чтения\n                foreach ($this->data[$this->code] as $key => $value) {\n                    if (!is_array($value)) {\n                        unset($this->data[$this->code][$key]);\n                    }\n                }\n                return false;\n            }\n\n            if (class_exists('\\Bitrix\\Main\\UI\\FileInput', true) && $this->getSettings('IMAGE') === true) {\n                foreach ($this->data[$this->code] as $key => $value) {\n                    if (is_array($value) && ($value['name'] || $value['tmp_name'])) {\n                        $_FILES[$this->code]['name'][$key] = $value['name'];\n                        $_FILES[$this->code]['type'][$key] = $value['type'];\n                        $_FILES[$this->code]['tmp_name'][$key] = $this->correctTmpName($value['tmp_name']);\n                        $_FILES[$this->code]['error'][$key] = $value['error'];\n                        $_FILES[$this->code]['size'][$key] = $value['size'];\n                        unset($this->data[$this->code][$key]);\n                    } else {\n                        $_FILES[$this->code]['name'][$key] = '';\n                    }\n                }\n                if (!count($this->data[$this->code])) {\n                    unset($this->data[$this->code]);\n                }\n            }\n\n            if (!empty($_FILES[$this->getCode()])) {\n                foreach ($_FILES[$this->getCode()]['name'] as $key => $fileName) {\n                    if (empty($fileName)\n                        || empty($_FILES[$this->getCode()]['tmp_name'][$key])\n                        || !empty($_FILES[$this->getCode()]['error'][$key])\n                    ) {\n                        if (isset($_REQUEST[$this->getCode() . '_del'][$key])) {\n                            if (is_array($this->data[$this->getCode()][$key]) &&\n                                !empty($this->data[$this->getCode()][$key]['VALUE'])\n                            ) {\n                                \\CFile::Delete(intval($this->data[$this->getCode()][$key]['VALUE']));\n                            } else {\n                                \\CFile::Delete(intval($this->data[$this->getCode()][$key]));\n                            }\n                            unset($this->data[$this->getCode()][$key]);\n                        } elseif ($this->data[$this->getCode()][$key]['VALUE']) {\n                            \\CFile::UpdateDesc($this->data[$this->getCode()][$key]['VALUE'],\n                                $_REQUEST[$this->getCode() . '_descr'][$key]);\n                        }\n                        continue;\n                    } elseif (is_int($key)) {\n                        //Удаляем старый файл при замене\n                        if (is_array($this->data[$this->getCode()][$key]) &&\n                            !empty($this->data[$this->getCode()][$key]['VALUE'])\n                        ) {\n                            \\CFile::Delete(intval($this->data[$this->getCode()][$key]['VALUE']));\n                        } else {\n                            \\CFile::Delete(intval($this->data[$this->getCode()][$key]));\n                        }\n                    }\n\n                    $description = null;\n\n                    if (isset($_REQUEST[$this->getCode() . '_descr'][$key])) {\n                        $description = $_REQUEST[$this->getCode() . '_descr'][$key];\n                    }\n\n                    if (empty($this->data[$this->getCode()][$key])) {\n                        unset($this->data[$this->getCode()][$key]);\n                    }\n\n                    $fileId = $this->saveFile($fileName, $_FILES[$this->getCode()]['tmp_name'][$key], false, $description);\n\n                    if ($fileId) {\n                        $this->data[$this->getCode()][$key] = array('VALUE' => $fileId);\n                    } else {\n                        $this->addError('DIGITALWAND_AH_FAIL_ADD_FILE', array(\n                            'FILE_NAME' => $_FILES[$this->getCode()]['name'][$key]\n                        ));\n                    }\n                }\n            }\n        } else {\n            if (class_exists('\\Bitrix\\Main\\UI\\FileInput', true) && $this->getSettings('IMAGE') === true) {\n                if (is_array($this->data[$this->code . '_FILE']) && ($this->data[$this->code . '_FILE']['name'] ||\n                        $this->data[$this->code . '_FILE']['tmp_name'])\n                ) {\n                    $_FILES['FIELDS']['name'][$this->code . '_FILE'] = $this->data[$this->code . '_FILE']['name'];\n                    $_FILES['FIELDS']['type'][$this->code . '_FILE'] = $this->data[$this->code . '_FILE']['type'];\n                    $_FILES['FIELDS']['tmp_name'][$this->code . '_FILE']\n                        = $this->correctTmpName($this->data[$this->code . '_FILE']['tmp_name']);\n                    $_FILES['FIELDS']['error'][$this->code . '_FILE'] = $this->data[$this->code . '_FILE']['error'];\n                    $_FILES['FIELDS']['size'][$this->code . '_FILE'] = $this->data[$this->code . '_FILE']['size'];\n                }\n            }\n\n            unset($this->data[$this->code . '_FILE']);\n            \n            if ($this->getSettings('READONLY') === true) {\n                return false;\n            }\n\n            if (empty($_FILES['FIELDS']['name'][$this->code . '_FILE'])\n                || empty($_FILES['FIELDS']['tmp_name'][$this->code . '_FILE'])\n                || !empty($_FILES['FIELDS']['error'][$this->code . '_FILE'])\n            ) {\n                if (isset($_REQUEST['FIELDS_del'][$this->code . '_FILE']) AND $_REQUEST['FIELDS_del'][$this->code . '_FILE'] == 'Y') {\n                    \\CFile::Delete(intval($this->data[$this->code]));\n                    $this->data[$this->code] = 0;\n                } elseif ($this->data[$this->code] && isset($_REQUEST['FIELDS_descr'][$this->code . '_FILE'])) {\n                    \\CFile::UpdateDesc($this->data[$this->code],\n                        $_REQUEST['FIELDS_descr'][$this->code . '_FILE']);\n                }\n                return false;\n            }\n\n            $description = null;\n\n            if (isset($_REQUEST['FIELDS_descr'][$this->code . '_FILE'])) {\n                $description = $_REQUEST['FIELDS_descr'][$this->code . '_FILE'];\n            }\n\n            $name = $_FILES['FIELDS']['name'][$this->code . '_FILE'];\n            $path = $_FILES['FIELDS']['tmp_name'][$this->code . '_FILE'];\n            $type = $_FILES['FIELDS']['type'][$this->code . '_FILE'];\n\n            $this->saveFile($name, $path, $type, $description);\n        }\n        parent::processEditAction();\n    }\n\n    protected function saveFile($name, $path, $type = false, $description = null)\n    {\n        if (!$path) {\n            return false;\n        }\n\n        $file = \\CFile::MakeFileArray($path, $type);\n\n        if (!$file) {\n            return false;\n        }\n\n        if (!empty($description)) {\n            $file['description'] = $description;\n        }\n\n        if ($this->getSettings('IMAGE') === true && stripos($file['type'], \"image\") === false) {\n            $this->addError('FILE_FIELD_TYPE_ERROR');\n\n            return false;\n        }\n\n        $file['name'] = $name;\n\n        $moduleId = $this->helper->getModule();\n        $file['MODULE_ID'] = $moduleId;\n\n        $fileId = \\CFile::SaveFile($file, $moduleId);\n\n        if (!$this->getSettings('MULTIPLE')) {\n            $code = $this->code;\n            \n            if (isset($this->data[$code])) {\n                \\CFile::Delete($this->data[$code]);\n            }\n\n            $this->data[$code] = $fileId;\n        }\n\n        return $fileId;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getValueReadonly()\n    {\n        $this->setSetting('UPLOAD', false);\n        $this->setSetting('MEDIALIB', false);\n        $this->setSetting('FILE_DIALOG', false);\n        $this->setSetting('CLOUD', false);\n        $this->setSetting('DELETE', false);\n        $this->setSetting('EDIT', false);\n\n        return $this->getEditHtml();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getMultipleValueReadonly()\n    {\n        $this->setSetting('UPLOAD', false);\n        $this->setSetting('MEDIALIB', false);\n        $this->setSetting('FILE_DIALOG', false);\n        $this->setSetting('CLOUD', false);\n        $this->setSetting('DELETE', false);\n        $this->setSetting('EDIT', false);\n\n        return $this->getMultipleEditHtml();\n    }\n\n    /**\n     * Корректирует путь до временного файла\n     * т.к. в новых версиях ядра путь до файла передается относительно временной папки загрузки, а не корня сайта.\n     *\n     * @param string $tmpName\n     * @return string\n     */\n    protected function correctTmpName($tmpName = '')\n    {\n        if(!$tmpName) {\n            return '';\n        }\n\n        static $relativeTempFolder = false;\n        if(!$relativeTempFolder){\n            $relativeTempFolder = str_replace(Application::getDocumentRoot(), '', CTempFile::GetAbsoluteRoot());\n        }\n        if (strpos($tmpName, $relativeTempFolder) === false) {\n            $tmpName = $relativeTempFolder . $tmpName;\n        }\n\n        return $tmpName;\n    }\n}"
  },
  {
    "path": "lib/widget/HLIBlockFieldWidget.php",
    "content": "<?php\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\nuse Bitrix\\Main\\Entity\\DataManager;\nuse Bitrix\\Main\\Entity\\EntityError;\nuse Bitrix\\Main\\Entity\\Result;\nuse Bitrix\\Highloadblock as HL;\nuse Bitrix\\Main\\Localization\\Loc;\nuse DigitalWand\\AdminHelper\\Helper\\AdminEditHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminListHelper;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Виджет, отображающий стандартные поля, создаваемые в HL-инфоблоке в админке.\n *\n * Настройки:\n * <ul>\n * <li><b>MODEL</b> - Название модели, из которой будет производиться выборка данных. По-умолчанию - модель текущего\n * хэлпера</li>\n * </ul>\n * Class HLIBlockFieldWidget\n * @package DigitalWand\\AdminHelper\\Widget\n */\nclass HLIBlockFieldWidget extends HelperWidget\n{\n    static protected $userFieldsCache = array();\n    static protected $defaults = array(\n        'USE_BX_API' => true\n    );\n\n    /**\n     * Генерирует HTML для редактирования поля\n     *\n     * @see \\CAdminForm::ShowUserFieldsWithReadyData\n     * @return mixed\n     */\n    protected function getEditHtml()\n    {\n        $info = $this->getUserFieldInfo();\n        if ($info) {\n\n            /** @var \\CAllUserTypeManager $USER_FIELD_MANAGER */\n            global $USER_FIELD_MANAGER;\n            $GLOBALS[$this->getCode()] = isset($GLOBALS[$this->getCode()]) ? $GLOBALS[$this->getCode()] : $this->data[$this->getCode()];\n            $bVarsFromForm = false;\n\n            $info[\"VALUE_ID\"] = intval($this->data['ID']);\n            $info['EDIT_FORM_LABEL'] = $this->getSettings('TITLE');\n\n            if (isset($_REQUEST['def_' . $this->getCode()])) {\n                $info['SETTINGS']['DEFAULT_VALUE'] = $_REQUEST['def_' . $this->getCode()];\n            }\n            print $USER_FIELD_MANAGER->GetEditFormHTML($bVarsFromForm, $GLOBALS[$this->getCode()], $info);\n\n        }\n    }\n\n    /**\n     * Конвертирует данные при сохранении так, как это делали бы пользовательские свойства битрикса.\n     * Выполняет валидацию с помощью CheckFields() пользовательских полей.\n     *\n     * @see Bitrix\\Highloadblock\\DataManager\n     * @see /bitrix/modules/highloadblock/admin/highloadblock_row_edit.php\n     *\n     * @throws \\Bitrix\\Main\\ArgumentException\n     * @throws \\Bitrix\\Main\\SystemException\n     */\n    public function processEditAction()\n    {\n        /** @var \\CAllUserTypeManager $USER_FIELD_MANAGER */\n        global $USER_FIELD_MANAGER;\n        $iblockId = 'HLBLOCK_' . $this->getHLId();\n\n        //Чтобы не терялись старые данные\n        if (!isset($this->data[$this->getCode()]) AND isset($_REQUEST[$this->getCode() . '_old_id'])) {\n            $this->data[$this->getCode()] = $_REQUEST[$this->getCode() . '_old_id'];\n        }\n\n        //Функция работает для всех полей, так что запускаем её только один раз, результат кешируем.\n        static $data = array();\n        if (empty($data)) {\n            $data = $this->data;\n            $USER_FIELD_MANAGER->EditFormAddFields($iblockId, $data);\n        }\n\n        $value = $data[$this->getCode()];\n\n        $entity_data_class = AdminBaseHelper::getHLEntity($this->getSettings('MODEL'));\n\n        $oldData = $this->getOldFieldData($entity_data_class);\n        $fieldsInfo = $USER_FIELD_MANAGER->getUserFieldsWithReadyData($iblockId, $oldData, LANGUAGE_ID, false, 'ID');\n        $fieldInfo = $fieldsInfo[$this->getCode()];\n\n        $className = $fieldInfo['USER_TYPE']['CLASS_NAME'];\n        if (is_callable(array($className, 'CheckFields'))) {\n            $errors = $className::CheckFields($fieldInfo, $value);\n            if (!empty($errors)) {\n                $this->addError($errors);\n                return;\n            }\n        }\n\n        // use save modifiers\n        $field = $entity_data_class::getEntity()->getField($this->getCode());\n        $value = $field->modifyValueBeforeSave($value, $data);\n\n        //Типоспецифичные хаки\n        if ($unserialized = unserialize($value)) {\n            //Список значений прилетает сериализованным\n            $this->data[$this->getCode()] = $unserialized;\n\n        } else if ($className == 'CUserTypeFile' AND !is_array($value)) {\n            //Если не сделать intval, то при сохранении с ранее добавленным файлом будет выскакивать ошибка\n            $this->data[$this->getCode()] = intval($value);\n\n        } else {\n            //Все остальные поля - сохраняем как есть.\n            $this->data[$this->getCode()] = $value;\n        }\n    }\n\n    /**\n     * Битриксу надо получить поля, кторые сохранены в базе для этого пользовательского свойства.\n     * Иначе множественные свойства он затрёт.\n     * Проблема в том, что пользовательские свойства могут браться из связанной сущности.\n     * @param HL\\DataManager $entity_data_class\n     *\n     * @return mixed\n     */\n    protected function getOldFieldData($entity_data_class)\n    {\n        if (is_null($this->data) OR !isset($this->data[$this->helper->pk()])) return false;\n        return $entity_data_class::getByPrimary($this->data[$this->helper->pk()])->fetch();\n    }\n\n    /**\n     * Если запрашивается модель, и если модель явно не указана, то берется модель текущего хэлпера, сохраняется для\n     * последующего использования и возарвщвется пользователю.\n     *\n     * @param string $name\n     * @return array|\\Bitrix\\Main\\Entity\\DataManager|mixed|string\n     */\n    public function getSettings($name = '')\n    {\n        $value = parent::getSettings($name);\n        if (!$value) {\n            if ($name == 'MODEL') {\n                $value = $this->helper->getModel();\n                $this->setSetting($name, $value);\n\n            } else if ($name == 'TITLE') {\n\n                $context = $this->helper->getContext();\n                $info = $this->getUserFieldInfo();\n\n                if (($context == AdminListHelper::OP_ADMIN_VARIABLES_FILTER OR $context == AdminListHelper::OP_CREATE_FILTER_FORM)\n                    AND (isset($info['LIST_FILTER_LABEL']) AND !empty($info['LIST_FILTER_LABEL']))\n                ) {\n                    $value = $info['LIST_FILTER_LABEL'];\n\n                } else if ($context == AdminListHelper::OP_ADMIN_VARIABLES_HEADER\n                    AND isset($info['LIST_COLUMN_LABEL'])\n                    AND !empty($info['LIST_COLUMN_LABEL'])\n                ) {\n                    $value = $info['LIST_COLUMN_LABEL'];\n\n                } else if ($context == AdminEditHelper::OP_SHOW_TAB_ELEMENTS\n                    AND isset($info['EDIT_FORM_LABEL'])\n                    AND !empty($info['EDIT_FORM_LABEL'])\n                ) {\n                    $value = $info['EDIT_FORM_LABEL'];\n\n                } else {\n                    $value = $info['FIELD_NAME'];\n                }\n            }\n        }\n\n        return $value;\n    }\n\n\n    /**\n     * Генерирует HTML для поля в списке\n     * Копипаст из API Битрикса, бессмысленного и беспощадного...\n     *\n     * @see AdminListHelper::addRowCell();\n     *\n     * @param \\CAdminListRow $row\n     * @param array $data - данные текущей строки\n     *\n     * @return mixed\n     */\n    public function generateRow(&$row, $data)\n    {\n        $info = $this->getUserFieldInfo();\n        if ($info) {\n\n            /** @var \\CAllUserTypeManager $USER_FIELD_MANAGER */\n            global $USER_FIELD_MANAGER;\n            $FIELD_NAME = $this->getCode();\n            $GLOBALS[$FIELD_NAME] = isset($GLOBALS[$FIELD_NAME]) ? $GLOBALS[$FIELD_NAME] : $this->data[$this->getCode()];\n\n            $info[\"VALUE_ID\"] = intval($this->data['ID']);\n\n            if (isset($_REQUEST['def_' . $FIELD_NAME])) {\n                $info['SETTINGS']['DEFAULT_VALUE'] = $_REQUEST['def_' . $FIELD_NAME];\n            }\n            $USER_FIELD_MANAGER->AddUserField($info, $data[$this->getCode()], $row);\n\n        }\n    }\n\n    /**\n     * Генерирует HTML для поля фильтрации\n     *\n     * @see AdminListHelper::createFilterForm();\n     * @return mixed\n     */\n    public function showFilterHtml()\n    {\n        $info = $this->getUserFieldInfo();\n        if ($info) {\n            /** @var \\CAllUserTypeManager $USER_FIELD_MANAGER */\n            global $USER_FIELD_MANAGER;\n            $FIELD_NAME = $this->getCode();\n            $GLOBALS[$FIELD_NAME] = isset($GLOBALS[$FIELD_NAME]) ? $GLOBALS[$FIELD_NAME] : $this->data[$this->getCode()];\n\n            $info[\"VALUE_ID\"] = intval($this->data['ID']);\n            $info['LIST_FILTER_LABEL'] = $this->getSettings('TITLE');\n\n            print $USER_FIELD_MANAGER->GetFilterHTML($info, $this->getFilterInputName(), $this->getCurrentFilterValue());\n        }\n    }\n\n    public function getUserFieldInfo()\n    {\n        $id = $this->getHLId();\n        $fields = static::getUserFields($id, $this->data);\n        if (isset($fields[$this->getCode()])) {\n            return $fields[$this->getCode()];\n        }\n        return false;\n    }\n\n    /**\n     * Получаем ID HL-инфоблока по имени его класса\n     * @return mixed\n     */\n    protected function getHLId()\n    {\n        static $id = false;\n\n        if ($id === false) {\n            $model = $this->getSettings('MODEL');\n            $info = AdminBaseHelper::getHLEntityInfo($model);\n            if ($info AND isset($info['ID'])) {\n                $id = $info['ID'];\n            }\n        }\n\n        return $id;\n    }\n\n    static public function getUserFields($iblockId, $data)\n    {\n        /** @var \\CAllUserTypeManager $USER_FIELD_MANAGER */\n        global $USER_FIELD_MANAGER;\n        $iblockId = 'HLBLOCK_' . $iblockId;\n        if (!isset(static::$userFieldsCache[$iblockId][$data['ID']])) {\n            $fields = $USER_FIELD_MANAGER->getUserFieldsWithReadyData($iblockId, $data, LANGUAGE_ID, false, 'ID');\n            self::$userFieldsCache[$iblockId][$data['ID']] = $fields;\n        }\n\n        return self::$userFieldsCache[$iblockId][$data['ID']];\n    }\n\n    /**\n     * Заменяем оригинальную функцию, т.к. текст ошибки приходит от битрикса, причем название поля там почему-то не\n     * проставлено\n     * \n*@param string $messageId\n     */\n    protected function addError($messageId)\n    {\n        if (is_array($messageId)) {\n            foreach ($messageId as $key => $error) {\n                if (isset($error['text'])) {\n                    //FIXME: почему-то битрикс не подхватывает корректное название поля, поэтому запихиваем его сами.\n                    if (isset($error['id']) AND strpos($error['text'], '\"\"')) {\n                        $messageId[$key] = str_replace('\"\"', '\"' . $this->getSettings('TITLE') . '\"', $error['text']);\n\n                    } else {\n                        $messageId[$key] = $error['text'];\n                    }\n                }\n            }\n        }\n\n        $messageId = implode(\"\\n\", $messageId);\n        $this->validationErrors[$this->getCode()] = $messageId;\n    }\n\n}"
  },
  {
    "path": "lib/widget/HelperWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminEditHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminListHelper;\nuse Bitrix\\Main\\Entity\\DataManager;\n\n/**\n * Виджет - класс, отвечающий за внешний вид отдельно взятого поля сущности. Один виджет отвечает за:\n * <ul>\n * <li>Отображение поля на странице редактирования</li>\n * <li>Отображение ячейки поля в таблице списка - при просмотре и редактировании</li>\n * <li>Отображение фильтра по данному полю</li>\n * <li>Валидацию значения поля</li>\n * </ul>\n *\n * Также виджетами осуществляется предварительная обработка данных:\n * <ul>\n * <li>Перед сохранением значения поля в БД</li>\n * <li>После получения значения поля из БД</li>\n * <li>Модификация запроса перед фильтрацией</li>\n * <li>Модификация пуеДшые перед выборкой данных</li>\n * </ul>\n *\n * Для получения минимальной функциональности достаточно переопределить основные методы, отвечающие за отображение\n * виджета в списке и на детальной.\n *\n * Каждый виджет имеет ряд специфических настроек, некоторые из которых обязательны для заполнения. Подробную\n * документацию по настройкам стоит искать в документации к конкретному виджету. Настройки могут быть переданы в\n * виджет как при описании всего интерфейса в файле Interface.php, так и непосредственно во время исполнения,\n * внутри Helper-классов.\n *\n * При указании настроек типа \"да\"/\"нет\", нельзя использовать строковые обозначения \"Y\"/\"N\":\n * для этого есть булевы true и false.\n *\n * Настройки базового класса:\n * <ul>\n * <li><b>HIDE_WHEN_CREATE</b> - скрывает поле в форме редактирования, если создаётся новый элемент, а не открыт\n *     существующий на редактирование.</li>\n * <li><b>TITLE</b> - название поля. Если не задано то возьмется значение title из DataManager::getMap()\n *       через getField($code)->getTitle(). Будет использовано в фильтре, заголовке таблицы и в качестве подписи поля\n *     на\n *     странице редактирования.</li>\n * <li><b>REQUIRED</b> - является ли поле обязательным.</li>\n * <li><b>READONLY</b> - поле нельзя редактировать, предназначено только для чтения</li>\n * <li><b>FILTER</b> - позволяет указать способ фильтрации по полю. В базовом классе возможен только вариант \"BETWEEN\"\n *     или \"><\". И в том и в другом случае это будет означать фильтрацию по диапазону значений. Количество возможных\n *     вариантов этого параметра может быть расширено в наследуемых классах</li>\n * <li><b>UNIQUE</b> - поле должно содержать только уникальные значения</li>\n * <li><b>VIRTUAL</b> - особая настройка, отражается как на поведении виджета, так и на поведении хэлперов. Поле,\n *     объявленное виртуальным, отображается в графическом интерфейче, однако не участвоует в запросах к БД. Опция\n *     может быть полезной при реализации нестандартной логики, когда, к примеру, под именем одного поля могут\n *     выводиться данные из нескольких полей сразу. </li>\n * <li><b>EDIT_IN_LIST</b> - параметр не обрабатывается непосредственно виджетом, однако используется хэлпером.\n *     Указывает, можно ли редактировать данное поле в спискке</li>\n * <li><b>MULTIPLE</b> - bool является ли поле множественным</li>\n * <li><b>MULTIPLE_FIELDS</b> - array поля используемые в хранилище множественных значений и их алиасы</li>\n * <li><b>LIST</b> - отображать ли поле в списке доступных в настройках столбцов таблицы (по-умолчанию true)</li>\n * <li><b>HEADER</b> - является ли столбец отображаемым по-умолчанию, если вывод столбцов таблицы не настроен (по-умолчанию true)</li>\n * </ul>\n *\n * Как сделать виджет множественным?\n * <ul>\n * <li>Реализуйте метод genMultipleEditHTML(). Метод должен выводить множественную форму ввода. Для реализации формы\n * ввода есть JS хелпер HelperWidget::jsHelper()</li>\n * <li>Опишите поля, которые будут переданы связи в EntityManager. Поля описываются в настройке \"MULTIPLE_FIELDS\"\n *     виджета. По умолчанию множественный виджет использует поля ID, ENTITY_ID, VALUE</li>\n * <li>Полученные от виджета данные будут переданы в EntityManager и сохранены как связанные данные</li>\n * </ul>\n * Пример реализации можно увидеть в виджете StringWidget\n *\n * Как использовать множественный виджет?\n * <ul>\n * <li>\n * Создайте таблицу и модель, которая будет хранить данные поля\n * - Таблица обязательно должна иметь поля, которые требует виджет.\n * Обязательные поля виджета по умолчанию описаны в: HelperWidget::$settings['MULTIPLE_FIELDS']\n * Если у виджета нестандартный набор полей, то они хранятся в: SomeWidget::$settings['MULTIPLE_FIELDS']\n * - Если поля, которые требует виджет есть в вашей таблице, но они имеют другие названия,\n * можно настроить виджет для работы с вашими полями.\n * Для этого переопределите настройку MULTIPLE_FIELDS при объявлении поля в интерфейсе следующим способом:\n * ```\n * 'RELATED_LINKS' => array(\n *        'WIDGET' => new StringWidget(),\n *        'TITLE' => 'Ссылки',\n *        'MULTIPLE' => true,\n *        // Обратите внимание, именно тут переопределяются поля виджета\n *        'MULTIPLE_FIELDS' => array(\n *            'ID', // Должны быть прописаны все поля, даже те, которые не нужно переопределять\n *            'ENTITY_ID' => 'NEWS_ID', // ENTITY_ID - поле, которое требует виджет, NEWS_ID - пример поля, которое\n *     будет использоваться вместо ENTITY_ID\n *            'VALUE' => 'LINK', // VALUE - поле, которое требует виджет, LINK - пример поля, которое будет\n *     использоваться вместо VALUE\n *        )\n *    ),\n * ```\n * </li>\n *\n * <li>\n * Далее в основной модели (та, которая указана в AdminBaseHelper::$model) нужно прописать связь с моделью,\n * в которой вы хотите хранить данные поля\n * Пример объявления связи:\n * ```\n * new Entity\\ReferenceField(\n *        'RELATED_LINKS',\n *        'namespace\\NewsLinksTable',\n *        array('=this.ID' => 'ref.NEWS_ID'),\n *          // Условия FIELD и ENTITY не обязательны, подробности смотрите в комментариях к классу @see EntityManager\n *        'ref.FIELD' => new DB\\SqlExpression('?s', 'NEWS_LINKS'),\n *        'ref.ENTITY' => new DB\\SqlExpression('?s', 'news'),\n * ),\n * ```\n * </li>\n *\n * <li>\n * Чтобы виджет работал во множественном режиме, нужно при описании интерфейса поля указать параметр MULTIPLE => true\n * ```\n * 'RELATED_LINKS' => array(\n *        'WIDGET' => new StringWidget(),\n *        'TITLE' => 'Ссылки',\n *        // Включаем режим множественного ввода\n *        'MULTIPLE' => true,\n * )\n * ```\n * </li>\n *\n * <li>\n * Готово :)\n * </li>\n * </ul>\n *\n * О том как сохраняются данные множественных виджетов можно узнать из комментариев \n * класса \\DigitalWand\\AdminHelper\\EntityManager.\n *\n * @see EntityManager\n * @see HelperWidget::getEditHtml()\n * @see HelperWidget::generateRow()\n * @see showFilterHtml::showFilterHTML()\n * @see HelperWidget::setSetting()\n * \n * @author Nik Samokhvalov <nik@samokhvalov.info>\n * @author Dmitriy Baibuhtin <dmitriy.baibuhtin@ya.ru>\n */\nabstract class HelperWidget\n{\n    const LIST_HELPER = 1;\n    const EDIT_HELPER = 2;\n\n    /**\n     * @var string Код поля.\n     */\n    protected $code;\n    /**\n     * @var array $settings Настройки виджета для данной модели.\n     */\n    protected $settings = array(\n        // Поля множественного виджета по умолчанию (array('ОРИГИНАЛЬНОЕ НАЗВАНИЕ', 'ОРИГИНАЛЬНОЕ НАЗВАНИЕ' => 'АЛИАС'))\n        'MULTIPLE_FIELDS' => array('ID', 'VALUE', 'ENTITY_ID')\n    );\n    /**\n     * @var array Настройки \"по-умолчанию\" для модели.\n     */\n    static protected $defaults;\n    /**\n     * @var DataManager Название класса модели.\n     */\n    protected $entityName;\n    /**\n     * @var array Данные модели.\n     */\n    protected $data;\n    /** @var  AdminBaseHelper|AdminListHelper|AdminEditHelper $helper Экземпляр хэлпера, вызывающий данный виджет.\n     */\n    protected $helper;\n    /**\n     * @var bool Статус отображения JS хелпера. Используется для исключения дублирования JS-кода.\n     */\n    protected $jsHelper = false;\n    /**\n     * @var array $validationErrors Ошибки валидации поля.\n     */\n    protected $validationErrors = array();\n    /**\n     * @var string Строка, добавляемая к полю name полей фильтра.\n     */\n    protected $filterFieldPrefix = 'find_';\n\n    /**\n     * Эксемпляр виджета создаётся всего один раз, при описании настроек интерфейса. При создании есть возможность\n     * сразу указать для него необходимые настройки.\n     * \n     * @param array $settings\n     */\n    public function __construct(array $settings = array())\n    {\n        Loc::loadMessages(__FILE__);\n        \n        $this->settings = $settings;\n    }\n\n    /**\n     * Генерирует HTML для редактирования поля.\n     *\n     * @return string\n     * \n     * @api\n     */\n    abstract protected function getEditHtml();\n\n    /**\n     * Генерирует HTML для редактирования поля в мульти-режиме.\n     *\n     * @return string\n     * \n     * @api\n     */\n    protected function getMultipleEditHtml()\n    {\n        return Loc::getMessage('DIGITALWAND_AH_MULTI_NOT_SUPPORT');\n    }\n\n    /**\n     * Оборачивает поле в HTML код, который в большинстве случаев менять не придется. Далее вызывается \n     * кастомизируемая часть.\n     *\n     * @param bool $isPKField Является ли поле первичным ключом модели.\n     *\n     * @see HelperWidget::getEditHtml();\n     */\n    public function showBasicEditField($isPKField)\n    {\n        if ($this->getSettings('HIDE_WHEN_CREATE') AND !isset($this->data[$this->helper->pk()])) {\n            return;\n        }\n\n        // JS хелперы\n        $this->jsHelper();\n\n        if ($this->getSettings('USE_BX_API')) {\n            $this->getEditHtml();\n        } else {\n            print '<tr>';\n            $title = $this->getSettings('TITLE');\n            if ($this->getSettings('REQUIRED') === true) {\n                $title = '<b>' . $title . '</b>';\n            }\n            print '<td width=\"40%\" style=\"vertical-align: top;\">' . $title . ':</td>';\n\n            $field = $this->getValue();\n            \n            if (is_null($field)) {\n                $field = '';\n            }\n\n            $readOnly = $this->getSettings('READONLY');\n\n            if (!$readOnly AND !$isPKField) {\n                if ($this->getSettings('MULTIPLE')) {\n                    $field = $this->getMultipleEditHtml();\n                } else {\n                    $field = $this->getEditHtml();\n                }\n            } else {\n                if ($readOnly) {\n                    if ($this->getSettings('MULTIPLE')) {\n                        $field = $this->getMultipleValueReadonly();\n                    } else {\n                        $field = $this->getValueReadonly();\n                    }\n                }\n            }\n\n            print '<td width=\"60%\">' . $field . '</td>';\n            print '</tr>';\n        }\n    }\n\n    /**\n     * Возвращает значение поля в форме \"только для чтения\" для не множественных свойств.\n     *\n     * @return mixed\n     */\n    protected function getValueReadonly()\n    {\n        return static::prepareToOutput($this->getValue());\n    }\n\n    /**\n     * Возвращает значения множественного поля.\n     * \n     * @return array\n     */\n    protected function getMultipleValue()\n    {\n        $rsEntityData = null;\n        $values = array();\n        if (!empty($this->data[$this->helper->pk()])) {\n            $entityName = $this->entityName;\n            $rsEntityData = $entityName::getList(array(\n                'select' => array('REFERENCE_' => $this->getCode() . '.*'),\n                'filter' => array('=ID' => $this->data[$this->helper->pk()])\n            ));\n\n            if ($rsEntityData) {\n                while ($referenceData = $rsEntityData->fetch()) {\n                    if (empty($referenceData['REFERENCE_' . $this->getMultipleField('ID')])) {\n                        continue;\n                    }\n                    $values[] = $referenceData['REFERENCE_' . $this->getMultipleField('VALUE')];\n                }\n            }\n        } else {\n            if ($this->data[$this->code]) {\n                $values = $this->data[$this->code];\n            }\n        }\n\n        return $values;\n    }\n\n    /**\n     * Возвращает значение поля в форме \"только для чтения\" для множественных свойств.\n     *\n     * @return string\n     */\n    protected function getMultipleValueReadonly()\n    {\n        $values = $this->getMultipleValue();\n\n        foreach ($values as &$value) {\n            $value = static::prepareToOutput($value);\n        }\n\n        return join('<br/>', $values);\n    }\n\n    /**\n     * Обработка строки для безопасного отображения. Если нужно отобразить текст как аттрибут тега, \n     * используйте static::prepareToTag().\n     *\n     * @param string $string\n     * @param bool $hideTags Скрыть теги:\n     * \n     * - true - вырезать теги оставив содержимое. Результат обработки: <b>text</b> = text\n     * \n     * - false - отобразаить теги в виде текста. Результат обработки: <b>text</b> = &lt;b&gt;text&lt;/b&gt;\n     *\n     * @return string\n     */\n    public static function prepareToOutput($string, $hideTags = true)\n    {\n        if ($hideTags) {\n            return preg_replace('/<.+>/mU', '', $string);\n        } else {\n            return htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);\n        }\n    }\n    \n    /**\n     * Подготовка строки для использования в аттрибутах тегов. Например:\n     * ```\n     * <input name=\"test\" value=\"<?= HelperWidget::prepareToTagAttr($value) ?>\"/>\n     * ```\n     * \n     * @param string $string\n     *\n     * @return string\n     */\n    public static function prepareToTagAttr($string)\n    {\n        // Не используйте addcslashes в этом методе, иначе в тегах будут дубли обратных слешей\n        return htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);\n    }\n\n    /**\n     * Подготовка строки для использования в JS.\n     *\n     * @param string $string\n     *\n     * @return string\n     */\n    public static function prepareToJs($string)\n    {\n        $string = htmlspecialchars($string, ENT_QUOTES, SITE_CHARSET);\n        $string = addcslashes($string, \"\\r\\n\\\"\\\\\");\n\n        return $string;\n    }\n\n    /**\n     * Генерирует HTML для поля в списке.\n     *\n     * @param \\CAdminListRow $row\n     * @param array $data Данные текущей строки.\n     *\n     * @return void\n     *\n     * @see AdminListHelper::addRowCell()\n     * \n     * @api\n     */\n    abstract public function generateRow(&$row, $data);\n\n    /**\n     * Генерирует HTML для поля фильтрации.\n     *\n     * @return void\n     *\n     * @see AdminListHelper::createFilterForm()\n     * \n     * @api\n     */\n    abstract public function showFilterHtml();\n\n    /**\n     * Возвращает массив настроек данного виджета, либо значение отдельного параметра, если указано его имя.\n     *\n     * @param string $name Название конкретного параметра.\n     *\n     * @return array|mixed\n     * \n     * @api\n     */\n    public function getSettings($name = '')\n    {\n        if (empty($name)) {\n            return $this->settings;\n        } else {\n            if (isset($this->settings[$name])) {\n                return $this->settings[$name];\n            } else {\n                if (isset(static::$defaults[$name])) {\n                    return static::$defaults[$name];\n                } else {\n                    return false;\n                }\n            }\n        }\n    }\n\n    /**\n     * Передаёт в виджет ссылку на вызывающий его объект.\n     *\n     * @param AdminBaseHelper $helper\n     */\n    public function setHelper(&$helper)\n    {\n        $this->helper = $helper;\n    }\n\n    /**\n     * Возвращает текукщее значение поля фильтрации (спец. символы экранированы).\n     *\n     * @return bool|string\n     */\n    protected function getCurrentFilterValue()\n    {\n        if (isset($GLOBALS[$this->filterFieldPrefix . $this->code])) {\n            return htmlspecialcharsbx($GLOBALS[$this->filterFieldPrefix . $this->code]);\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Проверяет корректность введенных в фильтр значений\n     *\n     * @param string $operationType тип операции\n     * @param mixed $value значение фильтра\n     *\n     * @see AdminListHelper::checkFilter();\n     * @return bool\n     */\n    public function checkFilter($operationType, $value)\n    {\n        return true;\n    }\n\n    /**\n     * Позволяет модифицировать опции, передаваемые в getList, непосредственно перед выборкой.\n     * Если в настройках явно указан способ фильтрации, до добавляет соответствующий префикс в $arFilter.\n     * Если фильтр BETWEEN, то формирует сложную логику фильтрации.\n     *\n     * @param array $filter $arFilter целиком\n     * @param array $select\n     * @param       $sort\n     * @param array $raw $arSelect, $arFilter, $arSort до примененных к ним преобразований.\n     *\n     * @see AdlinListHelper::getData();\n     */\n    public function changeGetListOptions(&$filter, &$select, &$sort, $raw)\n    {\n        if ($this->isFilterBetween()) {\n            $field = $this->getCode();\n            $from = $to = false;\n\n            if (isset($_REQUEST['find_' . $field . '_from'])) {\n                $from = $_REQUEST['find_' . $field . '_from'];\n                if (is_a($this, 'DateWidget')) {\n                    $from = date('Y-m-d H:i:s', strtotime($from));\n                }\n            }\n            if (isset($_REQUEST['find_' . $field . '_to'])) {\n                $to = $_REQUEST['find_' . $field . '_to'];\n                if (is_a($this, 'DateWidget')) {\n                    $to = date('Y-m-d 23:59:59', strtotime($to));\n                } else if (\n                        is_a($this, '\\DigitalWand\\AdminHelper\\Widget\\DateTimeWidget') &&\n                        !preg_match('/\\d{2}:\\d{2}:\\d{2}/', $to)\n                ) {\n                    $to = date('d.m.Y 23:59:59', strtotime($to));\n                }\n            }\n\n            if ($from !== false AND $to !== false) {\n                $filter['><' . $field] = array($from, $to);\n            } else {\n                if ($from !== false) {\n                    $filter['>' . $field] = $from;\n                } else {\n                    if ($to !== false) {\n                        $filter['<' . $field] = $to;\n                    }\n                }\n            }\n        } else {\n            if ($filterPrefix = $this->getSettings('FILTER') AND $filterPrefix !== true AND isset($filter[$this->getCode()])) {\n                $filter[$filterPrefix . $this->getCode()] = $filter[$this->getCode()];\n                unset($filter[$this->getCode()]);\n            }\n        }\n    }\n\n    /**\n     * Проверяет оператор фильтрации.\n     * \n     * @return bool\n     */\n    protected function isFilterBetween()\n    {\n        return $this->getSettings('FILTER') === '><' OR $this->getSettings('FILTER') === 'BETWEEN';\n    }\n\n    /**\n     * Действия, выполняемые над полем в процессе редактирования элемента, до его сохранения.\n     * По-умолчанию выполняется проверка обязательных полей и уникальности.\n     *\n     * @see AdminEditHelper::editAction();\n     * @see AdminListHelper::editAction();\n     */\n    public function processEditAction()\n    {\n        if (!$this->checkRequired()) {\n            $this->addError('DIGITALWAND_AH_REQUIRED_FIELD_ERROR');\n        }\n        if ($this->getSettings('UNIQUE') && !$this->isUnique()) {\n            $this->addError('DIGITALWAND_AH_DUPLICATE_FIELD_ERROR');\n        }\n    }\n\n    /**\n     * В совсем экзотических случаях может потребоваться моджифицировать значение поля уже после его сохраненния в БД -\n     * для последующей обработки каким-либо другим классом.\n     */\n    public function processAfterSaveAction()\n    {\n    }\n\n    /**\n     * Добавляет строку ошибки в массив ошибок.\n     *\n     * @param string $messageId Код сообщения об ошибке из лэнг-файла. Плейсхолдер #FIELD# будет заменён на значение \n     * параметра TITLE.\n     * @param array $replace Данные для замены.\n     *\n     * @see Loc::getMessage()\n     */\n    protected function addError($messageId, $replace = array())\n    {\n        $this->validationErrors[$this->getCode()] = Loc::getMessage(\n            $messageId,\n            array_merge(array('#FIELD#' => $this->getSettings('TITLE')), $replace)\n        );\n    }\n\n    /**\n     * Проверка заполненности обязательных полей.\n     * Не должны быть null или содержать пустую строку.\n     *\n     * @return bool\n     */\n    public function checkRequired()\n    {\n        if ($this->getSettings('REQUIRED') == true) {\n            $value = $this->getValue();\n\n            return !is_null($value) && !empty($value);\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * Выставляет код для данного виджета при инициализации. Перегружает настройки.\n     * \n     * @param string $code\n     */\n    public function setCode($code)\n    {\n        $this->code = $code;\n        $this->loadSettings();\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getCode()\n    {\n        return $this->code;\n    }\n\n    /**\n     * Устанавливает настройки интерфейса для текущего поля.\n     *\n     * @param string $code\n     *\n     * @return bool\n     * \n     * @see AdminBaseHelper::getInterfaceSettings()\n     * @see AdminBaseHelper::setFields()\n     */\n    public function loadSettings($code = null)\n    {\n        $interface = $this->helper->getInterfaceSettings();\n\n        $code = is_null($code) ? $this->code : $code;\n\n        if (!isset($interface['FIELDS'][$code])) {\n            return false;\n        }\n        unset($interface['FIELDS'][$code]['WIDGET']);\n        $this->settings = array_merge($this->settings, $interface['FIELDS'][$code]);\n        $this->setDefaultValue();\n\n        return true;\n    }\n\n    /**\n     * Возвращает название сущности данной модели.\n     * \n     * @return string|DataManager\n     */\n    public function getEntityName()\n    {\n        return $this->entityName;\n    }\n\n    /**\n     * @param string $entityName\n     */\n    public function setEntityName($entityName)\n    {\n        $this->entityName = $entityName;\n        $this->setDefaultValue();\n    }\n\n    /**\n     * Устанавливает значение по-умолчанию для данного поля\n     */\n    public function setDefaultValue()\n    {\n        if (isset($this->settings['DEFAULT']) && is_null($this->getValue())) {\n            $this->setValue($this->settings['DEFAULT']);\n        }\n    }\n\n    /**\n     * Передает ссылку на данные сущности в виджет\n     *\n     * @param $data\n     */\n    public function setData(&$data)\n    {\n        $this->data = &$data;\n        //FIXME: нелепый оверхэд ради того, чтобы можно было централизованно преобразовывать значение при записи\n        $this->setValue($data[$this->getCode()]);\n    }\n\n    /**\n     * Возвращает текущее значение, хранимое в поле виджета\n     * Если такого поля нет, возвращает null\n     *\n     * @return mixed|null\n     */\n    public function getValue()\n    {\n        $code = $this->getCode();\n\n        return isset($this->data[$code]) ? $this->data[$code] : null;\n    }\n\n    /**\n     * Устанавливает значение поля\n     *\n     * @param $value\n     *\n     * @return bool\n     */\n    protected function setValue($value)\n    {\n        $code = $this->getCode();\n        $this->data[$code] = $value;\n\n        return true;\n    }\n\n    /**\n     * Получения названия поля таблицы, в которой хранятся множественные данные этого виджета\n     *\n     * @param string $fieldName Название поля\n     *\n     * @return bool|string\n     */\n    public function getMultipleField($fieldName)\n    {\n        $fields = $this->getSettings('MULTIPLE_FIELDS');\n        if (empty($fields)) {\n            return $fieldName;\n        }\n\n        // Поиск алиаса названия поля\n        if (isset($fields[$fieldName])) {\n            return $fields[$fieldName];\n        }\n\n        // Поиск оригинального названия поля\n        $fieldsFlip = array_flip($fields);\n\n        if (isset($fieldsFlip[$fieldName])) {\n            return $fieldName;\n        }\n\n        return $fieldName;\n    }\n\n    /**\n     * Выставляет значение отдельной настройки\n     *\n     * @param string $name\n     * @param mixed $value\n     */\n    public function setSetting($name, $value)\n    {\n        $this->settings[$name] = $value;\n    }\n\n    /**\n     * Возвращает собранные ошибки валидации\n     * @return array\n     */\n    public function getValidationErrors()\n    {\n        return $this->validationErrors;\n    }\n\n    /**\n     * Возвращает имена для атрибута name полей фильтра.\n     * Если это фильтр BETWEEN, то вернёт массив с вариантами from и to.\n     *\n     * @return array|string\n     */\n    protected function getFilterInputName()\n    {\n        if ($this->isFilterBetween()) {\n            $baseName = $this->filterFieldPrefix . $this->code;;\n            $inputNameFrom = $baseName . '_from';\n            $inputNameTo = $baseName . '_to';\n\n            return array($inputNameFrom, $inputNameTo);\n        } else {\n            return $this->filterFieldPrefix . $this->code;\n        }\n    }\n\n    /**\n     * Возвращает текст для атрибута name инпута редактирования.\n     *\n     * @param null $suffix опциональное дополнение к названию поля\n     *\n     * @return string\n     */\n    protected function getEditInputName($suffix = null)\n    {\n        return 'FIELDS[' . $this->getCode() . $suffix . ']';\n    }\n\n    /**\n     * Уникальный ID для DOM HTML\n     * @return string\n     */\n    protected function getEditInputHtmlId()\n    {\n        $htmlId = end(explode('\\\\', $this->entityName)) . '-' . $this->getCode();\n\n        return strtolower(preg_replace('/[^A-z-]/', '-', $htmlId));\n    }\n\n    /**\n     * Возвращает текст для атрибута name инпута редактирования поля в списке\n     * @return string\n     */\n    protected function getEditableListInputName()\n    {\n        $id = $this->data[$this->helper->pk()];\n\n        return 'FIELDS[' . $id . '][' . $this->getCode() . ']';\n    }\n\n    /**\n     * Определяет тип вызывающего хэлпера, от чего может зависить поведение виджета.\n     *\n     * @return bool|int\n     * @see HelperWidget::EDIT_HELPER\n     * @see HelperWidget::LIST_HELPER\n     */\n    protected function getCurrentViewType()\n    {\n        if (is_a($this->helper, 'DigitalWand\\AdminHelper\\Helper\\AdminListHelper')) {\n            return self::LIST_HELPER;\n        } else {\n            if (is_a($this->helper, 'DigitalWand\\AdminHelper\\Helper\\AdminEditHelper')) {\n                return self::EDIT_HELPER;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Проверяет значение поля на уникальность\n     * @return bool\n     */\n    private function isUnique()\n    {\n        if ($this->getSettings('VIRTUAL')) {\n            return true;\n        }\n\n        $value = $this->getValue();\n        if (empty($value)) {\n            return true;\n        }\n\n        /** @var DataManager $class */\n        $class = $this->entityName;\n        $field = $this->getCode();\n        $idField = 'ID';\n        $id = $this->data[$idField];\n\n        $filter = array(\n            $field => $value,\n        );\n\n        if (!empty($id)) {\n            $filter[\"!=\" . $idField] = $id;\n        }\n\n        $count = $class::getCount($filter);\n\n        if (!$count) {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Проверяет, не является ли текущий запрос попыткой выгрузить данные в Excel\n     * @return bool\n     */\n    protected function isExcelView()\n    {\n        if (isset($_REQUEST['mode']) && $_REQUEST['mode'] == 'excel') {\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @todo Вынести в ресурс (\\CJSCore::Init()).\n     * @todo Описать.\n     */\n    protected function jsHelper()\n    {\n        if ($this->jsHelper == true) {\n            return true;\n        }\n\n        $this->jsHelper = true;\n        \\CJSCore::Init(array(\"jquery\"));\n        ?>\n        <script>\n            /**\n             * Менеджер множественных полей\n             * Позволяет добавлять и удалять любой HTML код с возможность подстановки динамических данных\n             * Инструкция:\n             * - создайте контейнер, где будут хранится отображаться код\n             * - создайте экземпляр MultipleWidgetHelper\n             * Например: var multiple = MultipleWidgetHelper(селектор контейнера, шаблон)\n             * шаблон - это HTML код, который можно будет добавлять и удалять в интерфейсе\n             * В шаблон можно добавлять переменные, их нужно обрамлять фигурными скобками. Например {{entity_id}}\n             * Если в шаблоне несколько полей, переменная {{field_id}} обязательна\n             * Например <input type=\"text\" name=\"image[{{field_id}}][SRC]\"><input type=\"text\" name=\"image[{{field_id}}][DESCRIPTION]\">\n             * Если добавляемые поле не новое, то обязательно передавайте в addField переменную field_id с ID записи,\n             * для новосозданных полей переменная заполнится автоматически\n             */\n            function MultipleWidgetHelper(container, fieldTemplate) {\n                this.$container = $(container);\n                if (this.$container.size() == 0) {\n                    throw 'Главный контейнер полей не найден (' + container + ')';\n                }\n                if (!fieldTemplate) {\n                    throw 'Не передан обязательный параметр fieldTemplate';\n                }\n                this.fieldTemplate = fieldTemplate;\n                this._init();\n            }\n\n            MultipleWidgetHelper.prototype = {\n                /**\n                 * Основной контейнер\n                 */\n                $container: null,\n                /**\n                 * Контейнер полей\n                 */\n                $fieldsContainer: null,\n                /**\n                 * Шаблон поля\n                 */\n                fieldTemplate: null,\n                /**\n                 * Счетчик добавлений полей\n                 */\n                fieldsCounter: 0,\n                /**\n                 * Добавления поля\n                 * @param data object Данные для шаблона в виде ключ: значение\n                 */\n                addField: function (data) {\n                    // console.log('Добавление поля');\n                    this.addFieldHtml(this.fieldTemplate, data);\n                },\n                addFieldHtml: function (fieldTemplate, data) {\n                    this.fieldsCounter++;\n                    this.$fieldsContainer.append(this._generateFieldContent(fieldTemplate, data));\n                },\n                /**\n                 * Удаление поля\n                 * @param field string|object Селектор или jQuery объект\n                 */\n                deleteField: function (field) {\n                    // console.log('Удаление поля');\n                    $(field).remove();\n                    if (this.$fieldsContainer.find('> *').size() == 0) {\n                        this.addField();\n                    }\n                },\n                _init: function () {\n                    this.$container.append('<div class=\"fields-container\"></div>');\n                    this.$fieldsContainer = this.$container.find('.fields-container');\n                    this.$container.append(this._getAddButton());\n\n                    this._trackEvents();\n                },\n                /**\n                 * Генерация контента контейнера поля\n                 * @param data\n                 * @returns {string}\n                 * @private\n                 */\n                _generateFieldContent: function (fieldTemplate, data) {\n                    return '<div class=\"field-container\" style=\"margin-bottom: 5px;\">'\n                        + this._generateFieldTemplate(fieldTemplate, data) + this._getDeleteButton()\n                        + '</div>';\n                },\n                /**\n                 * Генерация шаблона поля\n                 * @param data object Данные для подстановки\n                 * @returns {null}\n                 * @private\n                 */\n                _generateFieldTemplate: function (fieldTemplate, data) {\n                    if (!data) {\n                        data = {};\n                    }\n\n                    if (typeof data.field_id == 'undefined') {\n                        data.field_id = 'new_' + this.fieldsCounter;\n                    }\n\n                    $.each(data, function (key, value) {\n                        // Подставление значений переменных\n                        fieldTemplate = fieldTemplate.replace(new RegExp('\\{\\{' + key + '\\}\\}', ['g']), value);\n                    });\n\n                    // Удаление из шаблона необработанных переменных\n                    fieldTemplate = fieldTemplate.replace(/\\{\\{.+?\\}\\}/g, '');\n\n                    return fieldTemplate;\n                },\n                /**\n                 * Кнопка удаления\n                 * @returns {string}\n                 * @private\n                 */\n                _getDeleteButton: function () {\n                    return '<input type=\"button\" value=\"-\" class=\"delete-field-button\" style=\"margin-left: 5px;\">';\n                },\n                /**\n                 * Кнопка добавления\n                 * @returns {string}\n                 * @private\n                 */\n                _getAddButton: function () {\n                    return '<input type=\"button\" value=\"<?=Loc::getMessage('DIGITALWAND_AH_MULTI_ADD')?>\" class=\"add-field-button\">';\n                },\n                /**\n                 * Отслеживание событий\n                 * @private\n                 */\n                _trackEvents: function () {\n                    var context = this;\n                    // Добавление поля\n                    this.$container.find('.add-field-button').on('click', function () {\n                        context.addField();\n                    });\n                    // Удаление поля\n                    this.$container.on('click', '.delete-field-button', function () {\n                        context.deleteField($(this).parents('.field-container'));\n                    });\n                }\n            };\n        </script>\n        <?\n    }\n}\n"
  },
  {
    "path": "lib/widget/IblockElementWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Iblock\\ElementTable;\nuse Bitrix\\Main\\Loader;\nuse Bitrix\\Main\\Localization\\Loc;\n\n/**\n * Виджет для выбора элемента инфоблока.\n *\n * Доступные опции:\n * <ul>\n * <li> <b>IBLOCK_ID</b> - (int) ID инфоблока\n * <li> <b>INPUT_SIZE</b> - (int) значение атрибута size для input </li>\n * <li> <b>WINDOW_WIDTH</b> - (int) значение width для всплывающего окна выбора элемента </li>\n * <li> <b>WINDOW_HEIGHT</b> - (int) значение height для всплывающего окна выбора элемента </li>\n * </ul>\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n */\nclass IblockElementWidget extends NumberWidget\n{\n    static protected $defaults = array(\n        'FILTER' => '=',\n        'INPUT_SIZE' => 5,\n        'WINDOW_WIDTH' => 600,\n        'WINDOW_HEIGHT' => 500,\n    );\n    \n    public function __construct(array $settings = array())\n    {\n        Loc::loadMessages(__FILE__);\n        Loader::includeModule('iblock');\n        \n        parent::__construct($settings);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getEditHtml()\n    {\n        $iblockId = (int) $this->getSettings('IBLOCK_ID');\n        $inputSize = (int) $this->getSettings('INPUT_SIZE');\n        $windowWidth = (int) $this->getSettings('WINDOW_WIDTH');\n        $windowHeight = (int) $this->getSettings('WINDOW_HEIGHT');\n\n        $name = 'FIELDS';\n        $key = $this->getCode();\n\n        $elementId = $this->getValue();\n\n        if (!empty($elementId)) {\n            $rsElement = ElementTable::getById($elementId);\n\n            if (!$element = $rsElement->fetch()) {\n                $element['NAME'] = Loc::getMessage('IBLOCK_ELEMENT_NOT_FOUND');\n            }\n        } else {\n            $elementId = '';\n        }\n\n        return '<input name=\"' . $this->getEditInputName() . '\"\n                     id=\"' . $name . '[' . $key . ']\"\n                     value=\"' . $elementId . '\"\n                     size=\"' . $inputSize . '\"\n                     type=\"text\">' .\n        '<input type=\"button\"\n                    value=\"...\"\n                    onClick=\"jsUtils.OpenWindow(\\'/bitrix/admin/iblock_element_search.php?lang=' . LANGUAGE_ID\n        . '&amp;IBLOCK_ID=' . $iblockId . '&amp;n=' . $name . '&amp;k=' . $key . '\\', ' . $windowWidth . ', '\n        . $windowHeight . ');\">' . '&nbsp;<span id=\"sp_' . md5($name) . '_' . $key . '\" >'\n        . static::prepareToOutput($element['NAME'])\n        . '</span>';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getValueReadonly()\n    {\n        $elementId = $this->getValue();\n\n        if (!empty($elementId)) {\n            $rsElement = ElementTable::getList(array(\n                'filter' => array(\n                    'ID' => $elementId\n                ),\n                'select' => array(\n                    'ID',\n                    'NAME',\n                    'IBLOCK_ID',\n                    'IBLOCK.IBLOCK_TYPE_ID',\n                )\n            ));\n\n            $element = $rsElement->fetch();\n            \n            return '<a href=\"/bitrix/admin/iblock_element_edit.php?IBLOCK_ID=' . $element['IBLOCK_ID']\n            . '&type=' . $element['IBLOCK_ELEMENT_IBLOCK_IBLOCK_TYPE_ID'] . '&ID='\n            . $elementId . '&lang=ru\">[' . $elementId . '] ' . static::prepareToOutput($element['NAME']) . '</a>';\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function generateRow(&$row, $data)\n    {\n        $elementId = $this->getValue();\n\n        if (!empty($elementId)) {\n            $rsElement = ElementTable::getList(array(\n                'filter' => array(\n                    'ID' => $elementId\n                ),\n                'select' => array(\n                    'ID',\n                    'NAME',\n                    'IBLOCK_ID',\n                    'IBLOCK.IBLOCK_TYPE_ID',\n                )\n            ));\n            \n            $element = $rsElement->fetch();\n            \n            $html = '<a href=\"/bitrix/admin/iblock_element_edit.php?IBLOCK_ID=' . $element['IBLOCK_ID']\n                . '&type=' . $element['IBLOCK_ELEMENT_IBLOCK_IBLOCK_TYPE_ID'] . '&ID='\n                . $elementId . '&lang=ru\">[' . $elementId . '] ' . static::prepareToOutput($element['NAME']) . '</a>';\n        } else {\n            $html = '';\n        }\n\n        $row->AddViewField($this->getCode(), $html);\n    }\n}\n"
  },
  {
    "path": "lib/widget/NumberWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Виджет с числовыми значениями. Точная копия StringWidget, только работает с числами и не ищет по подстроке.\n */\nclass NumberWidget extends StringWidget\n{\n    static protected $defaults = array(\n        'FILTER' => '=',\n        'EDIT_IN_LIST' => true\n    );\n\n    public function checkFilter($operationType, $value)\n    {\n        return $this->isNumber($value);\n    }\n\n    public function checkRequired()\n    {\n        if ($this->getSettings('REQUIRED') == true) {\n            $value = $this->getValue();\n            return !is_null($value) && $value !== '';\n        } else {\n            return true;\n        }\n    }\n\n    public function processEditAction()\n    {\n        if (!$this->checkRequired()) {\n            $this->addError('DIGITALWAND_AH_REQUIRED_FIELD_ERROR');\n\n        } else if (!$this->isNumber($this->getValue())) {\n            $this->addError('VALUE_IS_NOT_NUMERIC');\n        }\n    }\n\n    protected function isNumber($value)\n    {\n        return is_numeric($value) OR is_null($value) OR empty($value);\n    }\n}\n"
  },
  {
    "path": "lib/widget/OrmElementWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\ArgumentTypeException;\nuse Bitrix\\Main\\Localization\\Loc;\nuse DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Виджет выбора записей из ORM.\n *\n * Настройки:\n * - `HELPER` — (string) класс хелпера, из которого будет производиться поиск записией. Должен быть\n * наследником `\\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper`.\n * - `ADDITIONAL_URL_PARAMS` — (array) дополнительные параметры для URL с попапом выбора записи.\n * - `TEMPLATE` — (string) шаблон отображения виджета, может принимать значения select и radio, по-умолчанию — select.\n * - `INPUT_SIZE` — (int) значение атрибута size для input.\n * - `WINDOW_WIDTH` — (int) значение width для всплывающего окна выбора элемента.\n * - `WINDOW_HEIGHT` — (int) значение height для всплывающего окна выбора элемента.\n * - `TITLE_FIELD_NAME` — (string) название поля, из которого выводить имя элемента.\n * - `DELETE_REFERENCED_DATA` — (bool) удалять ли связаный объект при удалении связи? По-умолчанию false.\n *\n * @author Nik Samokhvalov <nik@samokhvalov.info>\n */\nclass OrmElementWidget extends NumberWidget\n{\n    public function processEditAction()\n    {\n        if (!$this->getSettings('MULTIPLE')) {\n            parent::processEditAction();\n        } else {\n            if (!$this->checkRequired()) {\n                $this->addError('DIGITALWAND_AH_REQUIRED_FIELD_ERROR');\n            }\n        }\n    }\n\n    protected static $defaults = array(\n        'FILTER' => '=',\n        'INPUT_SIZE' => 5,\n        'WINDOW_WIDTH' => 600,\n        'WINDOW_HEIGHT' => 500,\n        'TITLE_FIELD_NAME' => 'TITLE',\n        'TEMPLATE' => 'select',\n        'ADDITIONAL_URL_PARAMS' => array(),\n        'DELETE_REFERENCED_DATA' => false\n    );\n\n    /**\n     * @inheritdoc\n     */\n    public function loadSettings($code = null)\n    {\n        $load = parent::loadSettings($code);\n\n        if (!is_subclass_of($this->getSettings('HELPER'), '\\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper'))\n        {\n            throw new ArgumentTypeException('HELPER', '\\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper');\n        }\n\n        if (!is_array($this->getSettings('ADDITIONAL_URL_PARAMS')))\n        {\n            throw new ArgumentTypeException('ADDITIONAL_URL_PARAMS', 'array');\n        }\n\n        return $load;\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function getEditHtml()\n    {\n        if ($this->getSettings('TEMPLATE') == 'radio') {\n            $html = $this->genEditHtmlInputs();\n        } else {\n            $html = $this->getEditHtmlSelect();\n        }\n\n        return $html;\n    }\n\n    /**\n     * Генерирует HTML с выбором элемента во вcплывающем окне, шаблон select.\n     *\n     * @return string\n     */\n    protected function getEditHtmlSelect()\n    {\n        /** @var AdminBaseHelper $linkedHelper */\n        $linkedHelper = $this->getSettings('HELPER');\n        $inputSize = (int) $this->getSettings('INPUT_SIZE');\n        $windowWidth = (int) $this->getSettings('WINDOW_WIDTH');\n        $windowHeight = (int) $this->getSettings('WINDOW_HEIGHT');\n\n        $name = 'FIELDS';\n        $key = $this->getCode();\n\n        $entityData = $this->getOrmElementData();\n\n        if (!empty($entityData)) {\n            $elementId = $entityData[$linkedHelper::pk()];\n            $elementName = $entityData[$this->getSettings('TITLE_FIELD_NAME')] ?\n                $entityData[$this->getSettings('TITLE_FIELD_NAME')] :\n                Loc::getMessage('IBLOCK_ELEMENT_NOT_FOUND');\n        } else {\n            $elementId = '';\n        }\n\n        $popupUrl = $linkedHelper::getUrl(array_merge(\n            array(\n                'popup' => 'Y',\n                'eltitle' => $this->getSettings('TITLE_FIELD_NAME'),\n                'n' => $name,\n                'k' => $key\n            ),\n            $this->getSettings('ADDITIONAL_URL_PARAMS')\n        ));\n\n        return '<input name=\"' . $this->getEditInputName() . '\"\n                     id=\"' . $name . '[' . $key . ']\"\n                     value=\"' . $elementId . '\"\n                     size=\"' . $inputSize . '\"\n                     type=\"text\">' .\n        '<input type=\"button\"\n                    value=\"...\" onClick=\"jsUtils.OpenWindow(\\''. $popupUrl . '\\', ' . $windowWidth . ', '\n        . $windowHeight . ');\">' . '&nbsp;<span id=\"sp_' . md5($name) . '_' . $key . '\" >' .\n        static::prepareToOutput($elementName)\n        . '</span>';\n    }\n\n    /**\n     * Генерирует HTML с выбором элемента в виде радио инпутов.\n     *\n     * @return string\n     */\n    public function genEditHtmlInputs()\n    {\n        $return = '';\n\n        $elementList = $this->getOrmElementList();\n\n        if (!is_null($elementList)) {\n            /** @var \\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper $linkedHelper */\n            $linkedHelper = $this->getSettings('HELPER');\n            foreach ($elementList as $key => $element) {\n                $return .= InputType(\"radio\", $this->getEditInputName(), $element[$linkedHelper::pk()], $this->getValue(), false, $element['TITLE']);\n            }\n        } else {\n            $return = Loc::getMessage('DIGITALWAND_AH_ORM_MISSING_ELEMENTS');\n        }\n\n        return $return;\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function getMultipleEditHtml()\n    {\n        /** @var AdminBaseHelper $linkedHelper */\n        $linkedHelper = $this->getSettings('HELPER');\n        $inputSize = (int)$this->getSettings('INPUT_SIZE');\n        $windowWidth = (int)$this->getSettings('WINDOW_WIDTH');\n        $windowHeight = (int)$this->getSettings('WINDOW_HEIGHT');\n\n        $name = 'FIELDS';\n        $key = $this->getCode();\n\n        $uniqueId = $this->getEditInputHtmlId();\n\n        $entityListData = $this->getOrmElementData();\n\n        $popupUrl = $linkedHelper::getUrl(array_merge(\n            array(\n                'popup' => 'Y',\n                'eltitle' => $this->getSettings('TITLE_FIELD_NAME'),\n                'n' => $name,\n                'k' => '{{field_id}}'\n            ),\n            $this->getSettings('ADDITIONAL_URL_PARAMS')\n        ));\n        $popupUrl = str_replace(urlencode('{{field_id}}'), '{{field_id}}', $popupUrl);\n\n        ob_start();\n        ?>\n\n        <div id=\"<?= $uniqueId ?>-field-container\" class=\"<?= $uniqueId ?>\"></div>\n\n        <script>\n            var multiple = new MultipleWidgetHelper(\n                '#<?= $uniqueId ?>-field-container',\n                '<input name=\"<?=$key?>[{{field_id}}][ID]\"' +\n                'id=\"<?=$name?>[{{field_id}}]\"' +\n                'value=\"{{value}}\"' +\n                'size=\"<?=$inputSize?>\"' +\n                'type=\"text\">' +\n                '<input type=\"button\"' +\n                'value=\"...\"' +\n                'onClick=\"jsUtils.OpenWindow(\\'<?=$popupUrl?>\\', <?=$windowWidth?>, <?=$windowHeight?>);\">' +\n                '&nbsp;<span id=\"sp_<?=md5($name)?>_{{field_id}}\" >{{element_title}}</span>'\n            );\n            <?\n            if (!empty($entityListData))\n            {\n                foreach($entityListData as $referenceData)\n                {\n            $elementId = $referenceData[$linkedHelper::pk()];\n                    $elementName = $referenceData[$this->getSettings('TITLE_FIELD_NAME')] ?\n                            $referenceData[$this->getSettings('TITLE_FIELD_NAME')] :\n                            Loc::getMessage('IBLOCK_ELEMENT_NOT_FOUND');\n\n                    ?>\n            multiple.addField({\n                value: '<?= $elementId ?>',\n                field_id: <?= $elementId ?>,\n                element_title: '<?= static::prepareToJs($elementName) ?>'\n            });\n            <?\n            }\n        }\n        ?>\n            multiple.addField();\n        </script>\n        <?\n        return ob_get_clean();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function getValueReadonly()\n    {\n        $entityData = $this->getOrmElementData();\n\n        if (!empty($entityData)) {\n            $entityName = $entityData[$this->getSettings('TITLE_FIELD_NAME')] ?\n                $entityData[$this->getSettings('TITLE_FIELD_NAME')] :\n                Loc::getMessage('IBLOCK_ELEMENT_NOT_FOUND');\n\n            return '[' . $entityData['ID'] . ']' . static::prepareToOutput($entityName);\n        }\n\n        return '';\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function getMultipleValueReadonly()\n    {\n        $entityListData = $this->getOrmElementData();\n\n        if (!empty($entityListData)) {\n            $multipleData = array();\n\n            foreach ($entityListData as $entityData) {\n                $entityName = $entityData[$this->getSettings('TITLE_FIELD_NAME')] ?\n                    $entityData[$this->getSettings('TITLE_FIELD_NAME')] :\n                    Loc::getMessage('IBLOCK_ELEMENT_NOT_FOUND');\n\n                $multipleData[] = '[' . $entityData['ID'] . ']' . static::prepareToOutput($entityName);\n            }\n\n            return implode('<br />', $multipleData);\n        }\n\n        return '';\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function generateRow(&$row, $data)\n    {\n        if ($this->getSettings('MULTIPLE')) {\n            $strElement = $this->getMultipleValueReadonly();\n        } else {\n            $strElement = $this->getValueReadonly();\n        }\n\n        $row->AddViewField($this->getCode(), $strElement);\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function showFilterHtml()\n    {\n        /** @var AdminBaseHelper $linkedHelper */\n        $linkedHelper = $this->getSettings('HELPER');\n\n        if ($this->getSettings('MULTIPLE')) {\n\n        } else {\n            $inputSize = (int) $this->getSettings('INPUT_SIZE');\n            $windowWidth = (int) $this->getSettings('WINDOW_WIDTH');\n            $windowHeight = (int) $this->getSettings('WINDOW_HEIGHT');\n\n            $name = 'FIND';\n            $key = $this->getCode();\n\n            print '<tr>';\n            print '<td>' . $this->getSettings('TITLE') . '</td>';\n\n            $popupUrl = $linkedHelper::getUrl(array_merge(\n                array(\n                    'popup' => 'Y',\n                    'eltitle' => $this->getSettings('TITLE_FIELD_NAME'),\n                    'n' => $name,\n                    'k' => $key\n                ),\n                $this->getSettings('ADDITIONAL_URL_PARAMS')\n            ));\n\n            $editStr = '<input name=\"' . $this->getFilterInputName() . '\"\n                     id=\"' . $name . '[' . $key . ']\"\n                     value=\"' . $this->getCurrentFilterValue() . '\"\n                     size=\"' . $inputSize . '\"\n                     type=\"text\">' .\n                '<input type=\"button\"\n                    value=\"...\"\n                    onClick=\"jsUtils.OpenWindow(\\'' . $popupUrl . '\\', ' . $windowWidth . ', ' . $windowHeight . ');\">';\n\n            print '<td>' . $editStr . '</td>';\n\n            print '</tr>';\n        }\n    }\n\n    /**\n     * Получает информацию о записях, к которым осуществлена привязка.\n     *\n     * @return array\n     * @throws \\Bitrix\\Main\\ArgumentException\n     */\n    protected function getOrmElementData()\n    {\n        $refInfo = array();\n        $valueList = null;\n\n        /** @var \\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper $linkedHelper */\n        $linkedHelper = $this->getSettings('HELPER');\n        $linkedModel = $linkedHelper::getModel();\n\n        if ($this->getSettings('MULTIPLE')) {\n            $entityName = $this->entityName;\n\n            $rsMultEntity = $entityName::getList(array(\n                'select' => array('REFERENCE_' => $this->getCode() . '.*'),\n                'filter' => array('=' . $this->getCode() . '.' . $this->getMultipleField('ENTITY_ID') => $this->data[$this->helper->pk()])\n            ));\n\n            while ($multEntity = $rsMultEntity->fetch()) {\n                $valueKey = $this->getMultipleField('VALUE');\n                if (isset($multEntity['REFERENCE_' . $valueKey])) {\n                    $valueList[$multEntity['REFERENCE_' . $linkedHelper::pk()]] = $multEntity['REFERENCE_' . $valueKey];\n                }\n            }\n        } else {\n            $value = $this->getValue();\n\n            if (!empty($value)) {\n                $valueList[$value] = $value;\n            }\n        }\n\n        if ($valueList) {\n\n            if ($this->getSettings('MULTIPLE')) {\n                $filter = array();\n                foreach ($valueList as $id => $val){\n                    $filter['ID'][] = $id;\n                }\n            } else {\n                $filter = array($linkedHelper::pk() => $valueList);\n            }\n\n            $rsEntity = $linkedModel::getList(array(\n                'filter' => $filter\n            ));\n\n            while ($entity = $rsEntity->fetch()) {\n\n                if ($this->getSettings('MULTIPLE')) {\n                    if (in_array($entity[$linkedHelper::pk()], array_keys($valueList))) {\n                        unset($valueList[$entity[$linkedHelper::pk()]]);\n                    }\n                    $refInfo[] = $entity;\n                } else {\n                    if (in_array($entity[$linkedHelper::pk()], $valueList)) {\n                        unset($valueList[$entity[$linkedHelper::pk()]]);\n                    }\n                    $refInfo = $entity;\n                    break;\n                }\n            }\n\n            foreach ($valueList as $entityId) {\n                if ($this->getSettings('MULTIPLE')) {\n                    $refInfo[] = array($linkedHelper::pk() => $entityId);\n                } else {\n                    $refInfo = array($linkedHelper::pk() => $entityId);\n                    break;\n                }\n            }\n        }\n\n        return $refInfo;\n    }\n\n    /**\n     * Получает информацию о всех активных элементах для их выбора в виджете.\n     *\n     * @return array\n     *\n     * @throws \\Bitrix\\Main\\ArgumentException\n     */\n    protected function getOrmElementList()\n    {\n        $valueList = null;\n\n        /** @var \\DigitalWand\\AdminHelper\\Helper\\AdminBaseHelper $linkedHelper */\n        $linkedHelper = $this->getSettings('HELPER');\n        $linkedModel = $linkedHelper::getModel();\n\n        $rsEntity = $linkedModel::getList(array(\n            'filter' => array(\n                'ACTIVE' => 1\n            ),\n            'select' => array(\n                $linkedHelper::pk(),\n                'TITLE'\n            )\n        ));\n\n        while ($entity = $rsEntity->fetch()) {\n            $valueList[] = $entity;\n        }\n\n        return $valueList;\n    }\n\n}"
  },
  {
    "path": "lib/widget/StringWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\nuse DigitalWand\\AdminHelper\\Helper\\AdminEditHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminListHelper;\nuse DigitalWand\\AdminHelper\\Helper\\AdminSectionListHelper;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Виджет строки с текстом.\n *\n * Доступные опции:\n * <ul>\n * <li> <b>EDIT_LINK</b> - отображать в виде ссылки на редактирование элемента </li>\n * <li> <b>STYLE</b> - inline-стили для input </li>\n * <li> <b>SIZE</b> - значение атрибута size для input </li>\n * <li> <b>TRANSLIT</b> - true, если поле будет транслитерироваться в символьный код</li>\n * <li> <b>MULTIPLE</b> - поддерживается множественный ввод. В таблице требуется наличие поля VALUE</li>\n * </ul>\n */\nclass StringWidget extends HelperWidget\n{\n    static protected $defaults = array(\n        'FILTER' => '%', //Фильтрация по подстроке, а не по точному соответствию.\n        'EDIT_IN_LIST' => true\n    );\n\n    /**\n     * @inheritdoc\n     */\n    protected function getEditHtml()\n    {\n        $style = $this->getSettings('STYLE');\n        $size = $this->getSettings('SIZE');\n\n        $link = '';\n\n        if ($this->getSettings('TRANSLIT')) {\n\n            //TODO: refactor this!\n            $uniqId = get_class($this->entityName) . '_' . $this->getCode();\n            $nameId = 'name_link_' . $uniqId;\n            $linkedFunctionName = 'set_linked_' . get_class($this->entityName) . '_CODE';//FIXME: hardcode here!!!\n\n            if (isset($this->entityName->{$this->entityName->pk()})) {\n                $pkVal = $this->entityName->{$this->entityName->pk()};\n            } else {\n                $pkVal = '_new_';\n            }\n\n            $nameId .= $pkVal;\n            $linkedFunctionName .= $pkVal;\n\n            $link = '<image id=\"' . $nameId . '\" title=\"' . Loc::getMessage(\"IBSEC_E_LINK_TIP\") . '\" class=\"linked\" src=\"/bitrix/themes/.default/icons/iblock/link.gif\" onclick=\"' . $linkedFunctionName . '()\" />';\n        }\n\n        return '<input type=\"text\"\n                       name=\"' . $this->getEditInputName() . '\"\n                       value=\"' . static::prepareToTagAttr($this->getValue()) . '\"\n                       size=\"' . $size . '\"\n                       style=\"' . $style . '\"/>' . $link;\n    }\n\n    protected function getMultipleEditHtml()\n    {\n        $style = $this->getSettings('STYLE');\n        $size = $this->getSettings('SIZE');\n        $uniqueId = $this->getEditInputHtmlId();\n\n        $rsEntityData = null;\n\n        if (!empty($this->data['ID'])) {\n            $entityName = $this->entityName;\n            $rsEntityData = $entityName::getList(array(\n                'select' => array('REFERENCE_' => $this->getCode() . '.*'),\n                'filter' => array('=ID' => $this->data['ID'])\n            ));\n        }\n\n        ob_start();\n        ?>\n\n        <div id=\"<?= $uniqueId ?>-field-container\" class=\"<?= $uniqueId ?>\">\n        </div>\n\n        <script>\n            var multiple = new MultipleWidgetHelper(\n                '#<?= $uniqueId ?>-field-container',\n                '{{field_original_id}}<input type=\"text\" name=\"<?= $this->getCode()?>[{{field_id}}][<?=$this->getMultipleField('VALUE')?>]\" style=\"<?=$style?>\" size=\"<?=$size?>\" value=\"{{value}}\">'\n            );\n            <?\n            if ($rsEntityData)\n            {\n                while($referenceData = $rsEntityData->fetch())\n                {\n                    if (empty($referenceData['REFERENCE_' . $this->getMultipleField('ID')]))\n                    {\n                        continue;\n                    }\n\n                    ?>\n            multiple.addField({\n                value: '<?= static::prepareToJs($referenceData['REFERENCE_' . $this->getMultipleField('VALUE')]) ?>',\n                field_original_id: '<input type=\"hidden\" name=\"<?= $this->getCode()?>[{{field_id}}][<?= $this->getMultipleField('ID') ?>]\"' +\n                ' value=\"<?= $referenceData['REFERENCE_' . $this->getMultipleField('ID')] ?>\">',\n                field_id: <?= $referenceData['REFERENCE_' . $this->getMultipleField('ID')] ?>\n            });\n            <?\n                           }\n                       }\n                       ?>\n\n            // TODO Добавление созданных полей\n            multiple.addField();\n        </script>\n        <?\n        return ob_get_clean();\n    }\n\n    protected function getMultipleValueReadonly()\n    {\n        $rsEntityData = null;\n        if (!empty($this->data['ID'])) {\n            $entityName = $this->entityName;\n            $rsEntityData = $entityName::getList(array(\n                'select' => array('REFERENCE_' => $this->getCode() . '.*'),\n                'filter' => array('=ID' => $this->data['ID'])\n            ));\n        }\n\n        $result = '';\n        if ($rsEntityData) {\n            while ($referenceData = $rsEntityData->fetch()) {\n                if (empty($referenceData['REFERENCE_VALUE'])) {\n                    continue;\n                }\n\n                $result .= '<div class=\"wrap_text\" style=\"margin-bottom: 5px\">' .\n                    static::prepareToOutput($referenceData['REFERENCE_VALUE']) . '</div>';\n            }\n        }\n\n        return $result;\n    }\n\n    /**\n     * Генерирует HTML для поля в списке\n     * @see AdminListHelper::addRowCell();\n     * @param \\CAdminListRow $row\n     * @param array $data - данные текущей строки\n     */\n    public function generateRow(&$row, $data)\n    {\n        if ($this->getSettings('MULTIPLE')) {\n        } else {\n            if ($this->getSettings('EDIT_LINK') || $this->getSettings('SECTION_LINK')) {\n                $pk = $this->helper->pk();\n\n                if ($this->getSettings('SECTION_LINK')) {\n                    $params = $this->helper->isPopup() ? $_GET : array();\n                    $params['ID'] = $this->data[$pk];\n                    $listHelper = $this->helper->getHelperClass($this->helper->isPopup() ? AdminSectionListHelper::className() : AdminListHelper::className());\n                    $pageUrl = $listHelper::getUrl($params);\n                    $value = '<span class=\"adm-submenu-item-link-icon adm-list-table-icon iblock-section-icon\"></span>';\n                } else {\n                    $editHelper = $this->helper->getHelperClass(AdminEditHelper::className());\n                    $pageUrl = $editHelper::getUrl(array(\n                        'ID' => $this->data[$pk]\n                    ));\n                }\n\n                $value .= '<a href=\"' . $pageUrl . '\">' . static::prepareToOutput($this->getValue()) . '</a>';\n            } else {\n                $value = static::prepareToOutput($this->getValue());\n            }\n\n            if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {\n                $row->AddInputField($this->getCode(), array('style' => 'width:90%'));\n            }\n\n            $row->AddViewField($this->getCode(), $value);\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function showFilterHtml()\n    {\n        if ($this->getSettings('MULTIPLE')) {\n        } else {\n            print '<tr>';\n            print '<td>' . $this->getSettings('TITLE') . '</td>';\n\n            if ($this->isFilterBetween()) {\n                list($from, $to) = $this->getFilterInputName();\n                print '<td>\n            <div class=\"adm-filter-box-sizing\">\n                <span style=\"display: inline-block; left: 11px; top: 5px; position: relative;\">От:</span>\n                <div class=\"adm-input-wrap\" style=\"display: inline-block\">\n                    <input type=\"text\" class=\"adm-input\" name=\"' . $from . '\" value=\"' . $$from . '\">\n                </div>\n                <span style=\"display: inline-block; left: 11px; top: 5px; position: relative;\">До:</span>\n                <div class=\"adm-input-wrap\" style=\"display: inline-block\">\n                    <input type=\"text\" class=\"adm-input\" name=\"' . $to . '\" value=\"' . $$to . '\">\n                </div>\n            </div>\n            </td> ';\n            } else {\n                print '<td><input type=\"text\" name=\"' . $this->getFilterInputName() . '\" size=\"47\" value=\"' . $this->getCurrentFilterValue() . '\"></td>';\n            }\n\n            print '</tr>';\n        }\n    }\n}"
  },
  {
    "path": "lib/widget/TextAreaWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\nuse Bitrix\\Main\\Localization\\Loc;\n\nLoc::loadMessages(__FILE__);\n\n/**\n * Выводит textarea для редактирования длинных строк.\n * Урезает длинные строки при отображении в списке\n *\n * Доступные опции:\n * <ul>\n * <li><b>COLS</b> - ширина</li>\n * <li><b>ROWS</b> - высота</li>\n * </ul>\n */\nclass TextAreaWidget extends StringWidget\n{\n    /**\n     * количество отображаемых символов в режиме списка.\n     */\n    const LIST_TEXT_SIZE = 150;\n\n    static protected $defaults = array(\n        'COLS' => 65,\n        'ROWS' => 5,\n        'EDIT_IN_LIST' => false\n    );\n\n    /**\n     * @inheritdoc\n     */\n    protected function getEditHtml()\n    {\n        $cols = $this->getSettings('COLS');\n        $rows = $this->getSettings('ROWS');\n\n        return '<textarea cols=\"' . $cols . '\" rows=\"' . $rows . '\" name=\"' . $this->getEditInputName() . '\">'\n        . static::prepareToOutput($this->getValue(), false) . '</textarea>';\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function generateRow(&$row, $data)\n    {\n        $text = $this->getValue();\n\n        if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {\n            $row->AddInputField($this->getCode(), array('style' => 'width:90%'));\n        } else {\n            if (strlen($text) > self::LIST_TEXT_SIZE && !$this->isExcelView()) {\n                $pos = false;\n                $pos = $pos === false ? stripos($text, \" \", self::LIST_TEXT_SIZE) : $pos;\n                $pos = $pos === false ? stripos($text, \"\\n\", self::LIST_TEXT_SIZE) : $pos;\n                $pos = $pos === false ? stripos($text, \"</\", self::LIST_TEXT_SIZE) : $pos;\n                $pos = $pos === false ? 300 : $pos;\n                $text = substr($text, 0, $pos) . \" ...\";\n            }\n\n            $text = static::prepareToOutput($text);\n\n            $row->AddViewField($this->code, $text);\n        }\n    }\n}"
  },
  {
    "path": "lib/widget/UrlWidget.php",
    "content": "<?php\r\n\r\nnamespace DigitalWand\\AdminHelper\\Widget;\r\n\r\nuse Bitrix\\Main\\Localization\\Loc;\r\n\r\nLoc::loadMessages(__FILE__);\r\n\r\n/**\r\n * Виджет текстового поля для ввода гиперссылки.\r\n *\r\n * Доступные опции:\r\n * <ul>\r\n * <li> PROTOCOL_REQUIRED - ссылка должна иметь протокол</li>\r\n * <li> STYLE - inline-стили </li>\r\n * <li> SIZE - значение атрибута size для input </li>\r\n * <li> MAX_URL_LEN - длина отображаемого URL</li>\r\n * </ul>\r\n *\r\n * @author Nik Samokhvalov <nik@samokhvalov.info>\r\n */\r\nclass UrlWidget extends StringWidget\r\n{\r\n    static protected $defaults = array(\r\n        'MAX_URL_LEN' => 256,\r\n        'PROTOCOL_REQUIRED' => false,\r\n    );\r\n\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function generateRow(&$row, $data)\r\n    {\r\n        $value = $this->getValue();\r\n\r\n        if ($this->getSettings('EDIT_IN_LIST') AND !$this->getSettings('READONLY')) {\r\n            $row->AddInputField($this->getCode(), array('style' => 'width:90%'));\r\n        }\r\n\r\n        $row->AddViewField($this->getCode(), $value);\r\n    }\r\n\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function getValue()\r\n    {\r\n        $code = $this->getCode();\r\n        $value = isset($this->data[$code]) ? $this->data[$code] : null;\r\n\r\n        if ($value !== null) {\r\n            $urlText = static::prepareToOutput($value);\r\n            $urlText = preg_replace('/^javascript:/i', '', $urlText);\r\n\r\n            if (strlen($urlText) > $this->getSettings('MAX_URL_LEN')) {\r\n                $urlText = substr($urlText, 0, $this->getSettings('MAX_URL_LEN'));\r\n            }\r\n\r\n            if (($this->getSettings('READONLY') && $this->getCurrentViewType() == static::EDIT_HELPER) || $this->getCurrentViewType() == static::LIST_HELPER) {\r\n                $value = '<a href=\"' . $value . '\" target=\"_blank\">' . $urlText . '</a>';\r\n            } else {\r\n                $value = $urlText;\r\n            }\r\n        }\r\n\r\n        return $value;\r\n    }\r\n    \r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    protected function getValueReadonly()\r\n    {\r\n        return $this->getValue();\r\n    }\r\n\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function processEditAction()\r\n    {\r\n        $value = $this->getValue();\r\n\r\n        if (\r\n            $this->getSettings('PROTOCOL_REQUIRED')\r\n            && !empty($value)\r\n            && preg_match('/^https?:\\/\\//', $value) == 0\r\n        ) {\r\n\r\n            $this->addError('PROTOCOL_REQUIRED');\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib/widget/UserWidget.php",
    "content": "<?php\r\n\r\nnamespace DigitalWand\\AdminHelper\\Widget;\r\n\r\nuse Bitrix\\Main\\UserTable;\r\n\r\n/**\r\n * Виджет для вывода пользователя.\r\n *\r\n * Доступные опции:\r\n * <ul>\r\n * <li> STYLE - inline-стили\r\n * <li> SIZE - значение атрибута size для input\r\n * </ul>\r\n *\r\n * @author Nik Samokhvalov <nik@samokhvalov.info>\r\n */\r\nclass UserWidget extends NumberWidget\r\n{\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function getEditHtml()\r\n    {\r\n        $style = $this->getSettings('STYLE');\r\n        $size = $this->getSettings('SIZE');\r\n\r\n        $userId = $this->getValue();\r\n\r\n        $htmlUser = '';\r\n\r\n        if (!empty($userId) && $userId != 0) {\r\n            $rsUser = UserTable::getById($userId);\r\n            $user = $rsUser->fetch();\r\n\r\n            $htmlUser = '[<a href=\"user_edit.php?lang=ru&ID=' . $user['ID'] . '\">' . $user['ID'] . '</a>] ('\r\n                . $user['EMAIL'] . ') ' . $user['NAME'] . '&nbsp;' . $user['LAST_NAME'];\r\n        }\r\n\r\n        return '<input type=\"text\"\r\n                       name=\"' . $this->getEditInputName() . '\"\r\n                       value=\"' . static::prepareToTagAttr($this->getValue()) . '\"\r\n                       size=\"' . $size . '\"\r\n                       style=\"' . $style . '\"/>' . $htmlUser;\r\n    }\r\n\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function getValueReadonly()\r\n    {\r\n        $userId = $this->getValue();\r\n        $htmlUser = '';\r\n\r\n        if (!empty($userId) && $userId != 0) {\r\n            $rsUser = UserTable::getById($userId);\r\n            $user = $rsUser->fetch();\r\n\r\n            $htmlUser = '[<a href=\"user_edit.php?lang=ru&ID=' . $user['ID'] . '\">' . $user['ID'] . '</a>]';\r\n\r\n            if ($user['EMAIL']) {\r\n                $htmlUser .= ' (' . $user['EMAIL'] . ')';\r\n            }\r\n\r\n            $htmlUser .= ' ' . static::prepareToOutput($user['NAME'])\r\n                . '&nbsp;' . static::prepareToOutput($user['LAST_NAME']);\r\n        }\r\n\r\n        return $htmlUser;\r\n    }\r\n\r\n    /**\r\n     * @inheritdoc\r\n     */\r\n    public function generateRow(&$row, $data)\r\n    {\r\n        $userId = $this->getValue();\r\n        $strUser = '';\r\n\r\n        if (!empty($userId) && $userId != 0) {\r\n            $rsUser = UserTable::getById($userId);\r\n            $user = $rsUser->fetch();\r\n\r\n            $strUser = '[<a href=\"user_edit.php?lang=ru&ID=' . $user['ID'] . '\">' . $user['ID'] . '</a>]';\r\n\r\n            if ($user['EMAIL']) {\r\n                $strUser .= ' (' . $user['EMAIL'] . ')';\r\n            }\r\n\r\n            $strUser .= ' ' . static::prepareToOutput($user['NAME'])\r\n                . '&nbsp;' . static::prepareToOutput($user['LAST_NAME']);\r\n        }\r\n\r\n        if ($strUser) {\r\n            $row->AddViewField($this->getCode(), $strUser);\r\n        } else {\r\n            $row->AddViewField($this->getCode(), '');\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "lib/widget/VisualEditorWidget.php",
    "content": "<?php\n\nnamespace DigitalWand\\AdminHelper\\Widget;\n\n/**\n * Визуальный редактор.\n *\n * В отличии от виджета TextAreaWidget, кроме поля указанного в интерфейсе раздела (AdminInterface::fields()),\n * обязательно поле {НАЗВАНИЕ ПОЛЯ}_TEXT_TYPE, в котором будет хранится тип контента (text/html).\n */\nclass VisualEditorWidget extends TextAreaWidget\n{\n    /**\n     * @const string Текст. Тип содержимого редактора\n     */\n    const CONTENT_TYPE_TEXT = 'text';\n    /**\n     * @const string HTML-текст. Тип содержимого редактора\n     */\n    const CONTENT_TYPE_HTML = 'html';\n\n    protected static $defaults = array(\n        'WIDTH' => '100%',\n        'HEIGHT' => 450,\n        'EDITORS' => array(\n            'EDITOR'\n        ),\n        'DEFAULT_EDITOR' => 'EDITOR',\n        'LIGHT_EDITOR_MODE' => 'N',\n        'EDITOR_TOOLBAR_CONFIG_SET' => 'FULL', // SIMPLE\n        'EDITOR_TOOLBAR_CONFIG' => false,\n    );\n\n    /**\n     * @inheritdoc\n     */\n    protected function getEditHtml()\n    {\n        if (\\CModule::IncludeModule('fileman')) {\n            ob_start();\n            $codeType = $this->getContentTypeCode();\n            /** @var string $className Имя класса без неймспейса */\n            $className = $this->getEntityShortName();\n            $entityClass = $this->entityName;\n            $modelPk = $entityClass::getEntity()->getPrimary();\n            $id = isset($this->data[$modelPk]) ? $this->data[$modelPk] : false;\n            $bxCode = $this->code . '_' . $className;\n            $bxCodeType = $codeType . '_' . $className;\n\n            if ($this->forceMultiple) {\n                if ($id) {\n                    $bxCode .= '_' . $id;\n                    $bxCodeType .= '_' . $id;\n                } else {\n                    $bxCode .= '_new_';\n                    $bxCodeType .= '_new_';\n                }\n            }\n\n            // TODO Избавиться от данного костыля\n            if ($_REQUEST[$bxCode]) {\n                $this->data[$this->code] = $_REQUEST[$bxCode];\n            }\n\n            $editorToolbarSets = array(\n                'FULL' => array(\n                    'Bold', 'Italic', 'Underline', 'Strike', 'RemoveFormat',\n                    'CreateLink', 'DeleteLink', 'Image', 'Video',\n                    'BackColor', 'ForeColor',\n                    'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyFull',\n                    'InsertOrderedList', 'InsertUnorderedList', 'Outdent', 'Indent',\n                    'StyleList', 'HeaderList',\n                    'FontList', 'FontSizeList'\n                ),\n                'SIMPLE' => array(\n                    'Bold', 'Italic', 'Underline', 'Strike', 'RemoveFormat',\n                    'CreateLink', 'DeleteLink',\n                    'Video',\n                    'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyFull',\n                    'InsertOrderedList', 'InsertUnorderedList', 'Outdent', 'Indent',\n                    'FontList', 'FontSizeList',\n                )\n            );\n\n            if ($this->getSettings('LIGHT_EDITOR_MODE') == 'Y') {\n                // Облегченная версия редактора\n                global $APPLICATION;\n                \n                $editorToolbarConfig = $this->getSettings('EDITOR_TOOLBAR_CONFIG');\n                \n                if (!is_array($editorToolbarConfig)) {\n                    $editorToolbarSet = $this->getSettings('EDITOR_TOOLBAR_CONFIG_SET');\n                    if (isset($editorToolbarSets[$editorToolbarSet])) {\n                        $editorToolbarConfig = $editorToolbarSets[$editorToolbarSet];\n                    } else {\n                        $editorToolbarConfig = $editorToolbarSets['FULL'];\n                    }\n                }\n                \n                $APPLICATION->IncludeComponent('bitrix:fileman.light_editor', '', array(\n                        'CONTENT' => $this->data[$this->code],\n                        'INPUT_NAME' => $bxCode,\n                        'INPUT_ID' => $bxCode,\n                        'WIDTH' => $this->getSettings('WIDTH'),\n                        'HEIGHT' => $this->getSettings('HEIGHT'),\n                        'RESIZABLE' => 'N',\n                        'AUTO_RESIZE' => 'N',\n                        'VIDEO_ALLOW_VIDEO' => 'Y',\n                        'VIDEO_MAX_WIDTH' => $this->getSettings('WIDTH'),\n                        'VIDEO_MAX_HEIGHT' => $this->getSettings('HEIGHT'),\n                        'VIDEO_BUFFER' => '20',\n                        'VIDEO_LOGO' => '',\n                        'VIDEO_WMODE' => 'transparent',\n                        'VIDEO_WINDOWLESS' => 'Y',\n                        'VIDEO_SKIN' => '/bitrix/components/bitrix/player/mediaplayer/skins/bitrix.swf',\n                        'USE_FILE_DIALOGS' => 'Y',\n                        'ID' => 'LIGHT_EDITOR_' . $bxCode,\n                        'JS_OBJ_NAME' => $bxCode,\n                        'TOOLBAR_CONFIG' => $editorToolbarConfig\n                    )\n                );\n            } else {\n                // Полная версия редактора\n                \\CFileMan::AddHTMLEditorFrame(\n                    $bxCode,\n                    $this->data[$this->code],\n                    $bxCodeType,\n                    $this->data[$codeType],\n                    array(\n                        'width' => $this->getSettings('WIDTH'),\n                        'height' => $this->getSettings('HEIGHT'),\n                    )\n                );\n                \n                $defaultEditors = array(\n                    static::CONTENT_TYPE_TEXT => static::CONTENT_TYPE_TEXT,\n                    static::CONTENT_TYPE_HTML => static::CONTENT_TYPE_HTML,\n                    'editor' => 'editor'\n                );\n                $editors = $this->getSettings('EDITORS');\n                $defaultEditor = strtolower($this->getSettings('DEFAULT_EDITOR'));\n                $contentType = $this->getContentType();\n                $defaultEditor = isset($contentType) && $contentType == static::CONTENT_TYPE_TEXT ? static::CONTENT_TYPE_TEXT : $defaultEditor;\n                $defaultEditor = isset($contentType) && $contentType == static::CONTENT_TYPE_HTML ? \"editor\" : $defaultEditor;\n\n                if (count($editors) > 1) {\n                    foreach ($editors as &$editor) {\n                        $editor = strtolower($editor);\n                        if (isset($defaultEditors[$editor])) {\n                            unset($defaultEditors[$editor]);\n                        }\n                    }\n                }\n\n                $script = '<script type=\"text/javascript\">';\n                $script .= '$(document).ready(function() {';\n\n                foreach ($defaultEditors as $editor) {\n                    $script .= '$(\"#bxed_' . $bxCode . '_' . $editor . '\").parent().hide();';\n                }\n\n                $script .= '$(\"#bxed_' . $bxCode . '_' . $defaultEditor . '\").click();';\n                $script .= 'setTimeout(function() {$(\"#bxed_' . $bxCode . '_' . $defaultEditor . '\").click(); }, 500);';\n                $script .= \"});\";\n                $script .= '</script>';\n\n                echo $script;\n            }\n            $html = ob_get_clean();\n\n            return $html;\n        } else {\n            return parent::getEditHtml();\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function showBasicEditField($isPKField)\n    {\n        if (!\\CModule::IncludeModule('fileman')) {\n            parent::showBasicEditField($isPKField);\n        } else {\n            $title = $this->getSettings('TITLE');\n            if ($this->getSettings('REQUIRED') === true) {\n                $title = '<b>' . $title . '</b>';\n            }\n            print '<tr class=\"heading\"><td colspan=\"2\">' . $title . '</td></tr>';\n            print '<tr><td colspan=\"2\">';\n            $readOnly = $this->getSettings('READONLY');\n            if (!$readOnly) {\n                print $this->getEditHtml();\n            } else {\n                print $this->getValueReadonly();\n            }\n            print '</td></tr>';\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function processEditAction()\n    {\n        $entityClass = $this->entityName;\n        $modelPk = $entityClass::getEntity()->getPrimary();\n        $className = $this->getEntityShortName();\n        $currentView = $this->getCurrentViewType();\n\n        switch ($currentView) {\n            case HelperWidget::EDIT_HELPER:\n                $id = isset($this->data[$modelPk]) ? $this->data[$modelPk] : false;\n                $codeType = $this->getContentTypeCode();\n                $bxCode = $this->getCode() . '_' . $className;\n                $bxCodeType = $codeType . '_' . $className;\n                \n                if ($this->forceMultiple AND $id) {\n                    $bxCode .= '_' . $id;\n                    $bxCodeType .= '_' . $id;\n                }\n                \n                if (!$_REQUEST[$bxCode] && $this->getSettings('REQUIRED') == true) {\n                    $this->addError('DIGITALWAND_AH_REQUIRED_FIELD_ERROR');\n                }\n                \n                $this->data[$this->code] = $_REQUEST[$bxCode];\n                $this->data[$codeType] = $_REQUEST[$bxCodeType];\n                break;\n            case HelperWidget::LIST_HELPER:\n            default:\n                parent::processEditAction();\n                break;\n        }\n    }\n\n    /**\n     * @inheritdoc\n     */\n    protected function getValueReadonly()\n    {\n        return $this->getContentType() == static::CONTENT_TYPE_HTML ? $this->data[$this->code] : parent::getValueReadOnly();\n    }\n\n    /**\n     * @inheritdoc\n     */\n    public function generateRow(&$row, $data)\n    {\n        $text = trim(strip_tags($data[$this->code]));\n\n        if (strlen($text) > self::LIST_TEXT_SIZE && !$this->isExcelView()) {\n            $pos = false;\n            $pos = $pos === false ? stripos($text, \" \", self::LIST_TEXT_SIZE) : $pos;\n            $pos = $pos === false ? stripos($text, \"\\n\", self::LIST_TEXT_SIZE) : $pos;\n            $pos = $pos === false ? stripos($text, \"</\", self::LIST_TEXT_SIZE) : $pos;\n            $pos = $pos === false ? 300 : $pos;\n            $text = substr($text, 0, $pos) . \" ...\";\n        }\n\n        $text = static::prepareToOutput($text);\n        $row->AddViewField($this->code, $text);\n    }\n\n    /**\n     * Тип текста (text/html). По умолчанию html.\n     * \n     * @return string\n     */\n    public function getContentType()\n    {\n        $contentType = $this->data[$this->getContentTypeCode()];\n\n        return empty($contentType) ? static::CONTENT_TYPE_HTML : $contentType;\n    }\n\n    /**\n     * Поле, в котором хранится тип текста.\n     * \n     * @return string\n     */\n    public function getContentTypeCode()\n    {\n        return $this->code . '_TEXT_TYPE';\n    }\n\n    /**\n     * Название класса без неймспейса.\n     *\n     * @return string\n     */\n    protected function getEntityShortName()\n    {\n        return end(explode('\\\\', $this->entityName));\n    }\n}"
  }
]