├── .gitignore ├── LICENSE ├── README.md ├── admin ├── applyError.php ├── changeversion.php ├── cli.php ├── controller.php ├── createScenario.php ├── detail.php ├── diagnostic.php ├── entitiesVersions.php ├── log.php ├── main.php ├── menu.php └── newChangesList.php ├── data ├── apply_scheme.png ├── change_analyze.png ├── change_event.png ├── classes_scheme.png ├── cli_apply.png ├── cli_help.png ├── cli_list.png ├── cli_rollback.png ├── create_dump.png ├── create_scenario.png ├── create_scenario_was_success.png ├── development_scheme.png ├── export.png ├── import.png ├── init_module_params.png ├── item_changes_list.png ├── main.png ├── main_with_scenario.png ├── scenarioTemplate.tpl ├── scenario_code.png ├── settings_page.png ├── update_scenario.png ├── updatelog.png ├── versions_demo.png └── versions_developer.png ├── description.ru ├── docs ├── auto.md ├── cli.md ├── scripts.md ├── setup.md ├── start.md └── update.md ├── include.php ├── install ├── admin │ └── ws_migrations.php ├── db │ ├── install.sql │ └── uninstall.sql ├── form.php ├── index.php ├── tools │ └── ws_migrations.php ├── uninstall.php ├── upload │ └── ws.migrations │ │ └── .htaccess └── version.php ├── lang ├── en │ ├── admin.php │ ├── handlers.php │ ├── info.php │ ├── menu.php │ ├── processes.php │ ├── setup.php │ ├── tests.php │ └── uninstall.php └── ru │ ├── admin.php │ ├── handlers.php │ ├── info.php │ ├── menu.php │ ├── processes.php │ ├── setup.php │ ├── tests.php │ └── uninstall.php ├── lib ├── applyresult.php ├── builder │ ├── agentbuilder.php │ ├── builderexception.php │ ├── entity │ │ ├── agent.php │ │ ├── base.php │ │ ├── enumvariant.php │ │ ├── eventmessage.php │ │ ├── eventtype.php │ │ ├── form.php │ │ ├── formanswer.php │ │ ├── formfield.php │ │ ├── formstatus.php │ │ ├── highloadblock.php │ │ ├── iblock.php │ │ ├── iblocksection.php │ │ ├── iblocktype.php │ │ ├── property.php │ │ └── userfield.php │ ├── eventsbuilder.php │ ├── formbuilder.php │ ├── highloadblockbuilder.php │ └── iblockbuilder.php ├── callback.php ├── changedatacollector │ ├── collector.php │ └── collectorfix.php ├── console │ ├── command │ │ ├── applycommand.php │ │ ├── basecommand.php │ │ ├── createscenariocommand.php │ │ ├── helpcommand.php │ │ ├── lastcommand.php │ │ ├── listcommand.php │ │ └── rollbackcommand.php │ ├── console.php │ ├── consoleexception.php │ ├── formatter │ │ └── output.php │ ├── runtimecounter.php │ └── runtimefixcounter.php ├── diagnostic │ ├── diagnosticresult.php │ ├── diagnostictester.php │ └── errormessage.php ├── entities │ ├── appliedchangeslog.php │ ├── appliedchangeslogmodel.php │ ├── baseentity.php │ ├── dbversionreferences.php │ ├── eventmessage.php │ ├── setuplog.php │ └── setuplogmodel.php ├── factories │ └── datetimefactory.php ├── localization.php ├── module.php ├── moduleoptions.php ├── options.php ├── platformversion.php ├── processes │ ├── addprocess.php │ ├── baseprocess.php │ ├── deleteprocess.php │ └── updateprocess.php ├── reference │ ├── referencecontroller.php │ └── referenceitem.php ├── scriptscenario.php ├── subjecthandlers │ ├── basesubjecthandler.php │ ├── iblockhandler.php │ ├── iblockpropertyhandler.php │ └── iblocksectionhandler.php └── tests │ ├── abstractcase.php │ ├── cases │ ├── agentbuildercase.php │ ├── errorexception.php │ ├── eventsbuildercase.php │ ├── fixtestcase.php │ ├── formbuildercase.php │ ├── highloadblockbuildercase.php │ ├── iblockbuildercase.php │ ├── installtestcase.php │ ├── rollbacktestcase.php │ └── updatetestcase.php │ ├── fixtures │ ├── add_collection.json │ ├── delete_iblock.json │ ├── delete_property.json │ ├── delete_section.json │ └── update_collection.json │ ├── result.php │ └── starter.php ├── options.php ├── prolog.php └── updater.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 WorkSolutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Модуль миграций 2 | =============== 3 | 4 | Модуль для CMS "1С-Битрикс", помогает реализовать быстрые изменения схемы данных проекта. 5 | 6 | ### Предупреждение об ограниченной поддержке 7 | 8 | __Данный модуль больше не имеет активной поддержки, рекомендуем вам ознакомиться с [новым модулем миграций](https://github.com/worksolutions/bitrix-reduce-migrations)__ 9 | 10 | ### Введение 11 | 12 | Как известно, исходный код плотно взаимодействует со структурой данных в проекте. С появлением новых функциональных требований исходный код изменяется, 13 | он требует новые поля для хранения даных, либо определяет новые сущности, которые отражаются в отдельных таблицах, 14 | а в проектах основанных на CMS "1С-Битрикс" данные в основном отражены в инфоблоках. 15 | 16 | Основная структура хранения данных проекта, написанного на Битриксе, отличается от общепринятой. 17 | Сущности хранятся не в отдельных таблицах, а абстрагированы в виде инфоблоков в разных местах, поэтому стандартные 18 | инструменты работы с данными, в таких проектах, не всегда справляются со своими задачами. 19 | 20 | Модуль миграций основан на исходном коде ядра CMS и поэтому позволяет в наиболее удобной форме осуществлять синхронизацию. Идея состоит в том, 21 | что проект имеет специальный каталог для хранения файлов, описывающих преобразование данных; этот каталог обновляется вместе с исходным кодом проекта. 22 | При обновлении исходного кода можно обновить базу данных проекта, чтобы она соответствовала новому функционалу. 23 | 24 | Модуль имеет два типа синхронизации: 25 | 26 | 1. Автоматическая - синхронизируются базовые данные инфоблоков, свойств, секций 27 | 2. Сценарии миграции - этап обновления реализуется разработчиком согласно требуемого алгоритма 28 | 29 | ### Разделы описания 30 | 31 | * [Установка и настройка](docs/setup.md) 32 | * [Начало работы](docs/start.md) 33 | * [Обновление площадки](docs/update.md) 34 | * [Интерфейс командной строки](docs/cli.md) 35 | * [Как происходит автоматический учет изменений](docs/auto.md) 36 | * [Скрипты миграций](docs/scripts.md) 37 | -------------------------------------------------------------------------------- /admin/applyError.php: -------------------------------------------------------------------------------- 1 | array('=id' => $id))); 7 | if (!$model) { 8 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_js.php"); 9 | ShowError($localization->message('error.modelNotExists', array( 10 | ':id:' => $id 11 | ))); 12 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin_js.php"); 13 | } 14 | /** @var CMain $APPLICATION */ 15 | $errorData = \WS\Migrations\jsonToArray($model->description) ?: array('message' => $model->description); 16 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_js.php"); 17 | ?> 18 | 19 | 20 | 23 | 26 | 27 | 30 | 31 | 34 | 37 | 38 | 41 | 44 | 45 | 48 | 66 | 67 | 70 |
21 | message('message')?> 22 | 24 | 25 |
32 | message('data')?> 33 | 35 | 36 |
46 | message('trace')?> 47 | 49 | 65 |
71 | 86 | getRequest(); 6 | 7 | /** @var \WS\Migrations\PlatformVersion $platformVersion */ 8 | $platformVersion = \WS\Migrations\Module::getInstance()->getPlatformVersion(); 9 | if ($request->isPost()) { 10 | $post = $request->getPostList()->toArray(); 11 | $post = \Bitrix\Main\Text\Encoding::convertEncodingArray($post, "UTF-8", $context->getCulture()->getCharset()); 12 | if ($post['changeversion']) { 13 | \WS\Migrations\Module::getInstance()->runRefreshVersion(); 14 | } 15 | if ($post['ownersetup']) { 16 | $platformVersion->setOwner($post['ownersetup']['owner']); 17 | $options = \WS\Migrations\Module::getInstance()->getOptions(); 18 | exit(); 19 | } 20 | } 21 | /** @var $localization \WS\Migrations\Localization */ 22 | $localization; 23 | $APPLICATION->SetTitle($localization->getDataByPath('pageTitle')); 24 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php"); 25 | ?>
"edit1", 30 | "TAB" => $localization->getDataByPath('title'), 31 | "ICON" => "iblock", 32 | "TITLE" => $localization->getDataByPath('title'), 33 | ), 34 | array( 35 | "DIV" => "edit2", 36 | "TAB" => $localization->getDataByPath('otherVersions.tab'), 37 | "ICON" => "iblock", 38 | "TITLE" => $localization->getDataByPath('otherVersions.tab') 39 | ) 40 | )); 41 | $module = \WS\Migrations\Module::getInstance(); 42 | $form->BeginPrologContent(); 43 | CAdminMessage::ShowNote($localization->getDataByPath('description')); 44 | $form->EndPrologContent(); 45 | $form->Begin(array( 46 | 'FORM_ACTION' => $APPLICATION->GetCurUri() 47 | )); 48 | 49 | $form->BeginNextFormTab(); 50 | $form->BeginCustomField('version', 'vv'); 51 | $color = "green"; 52 | !$platformVersion->isValid() && $color = "red"; 53 | ?> 54 | 55 | getDataByPath('version')?>: 56 | getValue()?> 57 | 58 | 59 | getDataByPath('owner')?>: 60 | getOwner()?> [getDataByPath('setup')?>] 61 | 62 | 63 | 64 | EndCustomField('version'); 66 | $form->BeginNextFormTab(); 67 | $form->BeginCustomField('owner', 'ww'); 68 | foreach ($module->getOptions()->getOtherVersions() as $version => $owner) { 69 | ?> 70 | 71 | : 72 | 73 | 74 | EndCustomField('owner'); 77 | $form->Buttons(); 78 | $form->Show(); 79 | CJSCore::Init(array('jquery')); 80 | $jsParams = array( 81 | 'owner' => array( 82 | 'label' => $localization->getDataByPath('owner'), 83 | 'value' => $platformVersion->getOwner() ?: '' 84 | ), 85 | 'dialog' => array( 86 | 'title' => $localization->getDataByPath('dialog.title') 87 | ) 88 | ); 89 | ?> 90 |
91 | 129 | -------------------------------------------------------------------------------- /admin/cli.php: -------------------------------------------------------------------------------- 1 | Authorize(1); 19 | 20 | $module = \WS\Migrations\Module::getInstance(); 21 | $console = new Console($argv); 22 | 23 | $getShowProgress = function () use ($console) { 24 | $counter = new \WS\Migrations\Console\RuntimeCounter(); 25 | return function ($data, $type) use ($console, $counter) { 26 | if ($type == 'setCount') { 27 | $counter->migrationCount = $data; 28 | $counter->start = microtime(true); 29 | } 30 | if ($type == 'start') { 31 | $counter->migrationNumber++; 32 | $console->printLine("{$data['name']}({$counter->migrationNumber}/$counter->migrationCount)", Console::OUTPUT_PROGRESS); 33 | } 34 | if ($type == 'end') { 35 | /**@var \WS\Migrations\Entities\AppliedChangesLogModel $log */ 36 | $log = $data['log']; 37 | $time = round($data['time'], 2); 38 | $message = ''; 39 | if (!empty($data['error'])) { 40 | $message .= $data['error'] . '. '; 41 | } 42 | $overallTime = round(microtime(true) - $counter->start, 2); 43 | $message .= "$time sec ($overallTime sec)"; 44 | $console->printLine($message, $log->success ? Console::OUTPUT_SUCCESS: Console::OUTPUT_ERROR); 45 | } 46 | }; 47 | }; 48 | try { 49 | $console->printLine(""); 50 | $command = $console->getCommand(); 51 | $command->execute($getShowProgress()); 52 | $module->commitDutyChanges(); 53 | } catch (\WS\Migrations\Console\ConsoleException $e) { 54 | $console->printLine($e->getMessage()); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /admin/controller.php: -------------------------------------------------------------------------------- 1 | isAdmin()) { 7 | return ; 8 | } 9 | 10 | CModule::IncludeModule('ws.migrations'); 11 | CModule::IncludeModule('iblock'); 12 | 13 | $request = $_REQUEST; 14 | $action = $request['q']; 15 | $fAction = function ($file) use ($action) { 16 | global 17 | $USER, $DB, $APPLICATION, $adminPage, $adminMenu, $adminChain; 18 | $localization = \WS\Migrations\Module::getInstance()->getLocalization('admin')->fork($action); 19 | include $file; 20 | }; 21 | 22 | $actionFile = __DIR__.DIRECTORY_SEPARATOR.$request['q'].'.php'; 23 | if (file_exists($actionFile)) { 24 | $fAction($actionFile); 25 | } else { 26 | /* @var $APPLICATION CMain */ 27 | $APPLICATION->ThrowException("Action `$actionFile` not exists"); 28 | } 29 | require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_admin_after.php"); 30 | ?> -------------------------------------------------------------------------------- /admin/createScenario.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/admin/createScenario.php -------------------------------------------------------------------------------- /admin/diagnostic.php: -------------------------------------------------------------------------------- 1 | getRequest(); 6 | 7 | $tester = \WS\Migrations\Module::getInstance()->useDiagnostic(); 8 | if ($request->isPost()) { 9 | $post = $request->getPostList()->toArray(); 10 | $run = (bool)$post['run']; 11 | $run && $tester->run(); 12 | } 13 | $lastResult = $tester->getLastResult(); 14 | /** @var $localization \WS\Migrations\Localization */ 15 | $localization; 16 | 17 | $APPLICATION->SetTitle($localization->getDataByPath('title')); 18 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php"); 19 | ?>
"edit1", 23 | "TAB" => $localization->getDataByPath('title'), 24 | "ICON" => "iblock", 25 | "TITLE" => $localization->getDataByPath('title'), 26 | ) 27 | )); 28 | $form->SetShowSettings(false); 29 | $module = \WS\Migrations\Module::getInstance(); 30 | $form->BeginPrologContent(); 31 | CAdminMessage::ShowNote($localization->getDataByPath('description')); 32 | $form->EndPrologContent(); 33 | $form->Begin(array( 34 | 'FORM_ACTION' => $APPLICATION->GetCurUri() 35 | )); 36 | 37 | $form->BeginNextFormTab(); 38 | $form->BeginCustomField('version', 'vv'); 39 | ?> 40 | 41 | getDataByPath('last.result')?>: 42 | isSuccess() ? $localization->message('last.success') : $localization->message('last.fail')?> [getTime()?>] 43 | 44 | isSuccess()): 46 | ?> 47 | 48 | 49 | getDataByPath('last.description')?>: 50 | 51 | Fetch(); 62 | $iblockId = $arProperty['IBLOCK_ID']; 63 | $arIblock = CIBlock::GetArrayByID($iblockId); 64 | $type = $arIblock['IBLOCK_TYPE_ID']; 65 | $urlTemplate = '/bitrix/admin/iblock_edit.php?type='.$type.'&ID='.$iblockId . '&lang=' . LANGUAGE_ID; 66 | break; 67 | case 'iblockPropertyListValues': 68 | $arValue = \Bitrix\Iblock\PropertyEnumerationTable::getList(array('filter' => array('=ID' => $id)))->Fetch(); 69 | $arProperty = CIBlockProperty::GetByID($arValue['PROPERTY_ID'])->Fetch(); 70 | $iblockId = $arProperty['IBLOCK_ID']; 71 | $arIblock = CIBlock::GetArrayByID($iblockId); 72 | $type = $arIblock['IBLOCK_TYPE_ID']; 73 | $urlTemplate = '/bitrix/admin/iblock_edit.php?type='.$type.'&ID='.$iblockId . '&lang=' . LANGUAGE_ID; 74 | break; 75 | case 'iblockSection': 76 | $arSection = CIBlockSection::GetByID($id)->Fetch(); 77 | $iblockId = $arSection['IBLOCK_ID']; 78 | $urlTemplate = '/bitrix/admin/iblock_section_edit.php?IBLOCK_ID='.$iblockId.'&ID='.$id . '&lang=' . LANGUAGE_ID; 79 | break; 80 | } 81 | return $urlTemplate; 82 | }; 83 | $strings = array(); 84 | foreach ($lastResult->getMessages() as $message) { 85 | if ($message->getType() == \WS\Migrations\Diagnostic\ErrorMessage::TYPE_ITEM_HAS_NOT_REFERENCE) { 86 | $url = $fCreateUrlFromEntity($message->getGroup(), $message->getItem()); 87 | if ($url) { 88 | $strings[] = ''.$message->getText().''; 89 | continue; 90 | } 91 | } 92 | $strings[] = $message->getText(); 93 | } 94 | echo implode('
', $strings); 95 | ?> 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | EndCustomField('version'); 108 | $form->BeginNextFormTab(); 109 | $form->Buttons(array('btnSave' => false, 'btnApply' => false)); 110 | $form->sButtonsContent = ''; 111 | 112 | $form->Show(); 113 | ?>
-------------------------------------------------------------------------------- /admin/entitiesVersions.php: -------------------------------------------------------------------------------- 1 | SetTitle($localization->getDataByPath('title')); 10 | $sTableID = "ws_migrations_versions_table"; 11 | $oSort = new CAdminSorting($sTableID, "date", "asc"); 12 | $lAdmin = new CAdminList($sTableID, $oSort); 13 | 14 | $arHeaders = array( 15 | array("id" => "reference", "content" => $localization->getDataByPath('fields.reference'), "default"=>true), 16 | array("id" => "versions", "content" => $localization->getDataByPath('fields.versions'), "default" => true), 17 | array("id" => "destination", "content" => $localization->getDataByPath('fields.destination'), "default" => true), 18 | ); 19 | $platformVersion = $module->getPlatformVersion(); 20 | $lAdmin->AddHeaders($arHeaders); 21 | 22 | $rsData = \WS\Migrations\Entities\DbVersionReferencesTable::getList(array('limit' => 500)); 23 | $arData = array(); 24 | while ($iData = $rsData->fetch()) { 25 | !$arData[$iData['REFERENCE']] && $arData[$iData['REFERENCE']] = array( 26 | 'reference' => $iData['REFERENCE'], 27 | 'versions' => array() 28 | ); 29 | 30 | $arData[$iData['REFERENCE']]['versions'][] = array( 31 | 'id' => $iData['ITEM_ID'], 32 | 'version' => $iData['DB_VERSION'] 33 | ); 34 | 35 | $iData['DB_VERSION'] == $platformVersion->getValue() && $arData[$iData['REFERENCE']]['data'] = array( 36 | 'id' => $iData['ITEM_ID'], 37 | 'subject' => $iData['GROUP'] 38 | ); 39 | } 40 | $rsData = new CAdminResult($arData, $sTableID); 41 | $rsData->NavStart(); 42 | 43 | $lAdmin->NavText($rsData->GetNavPrint($localization->getDataByPath('messages.pages'))); 44 | 45 | $versionsList = $platformVersion->getMapVersions(); 46 | 47 | $fCreateUrlFromEntity = function ($type, $id) { 48 | $urlTemplate = ''; 49 | switch ($type) { 50 | case 'iblock': 51 | $arIblock = CIBlock::GetArrayByID($id); 52 | $type = $arIblock['IBLOCK_TYPE_ID']; 53 | $urlTemplate = '/bitrix/admin/iblock_edit.php?type='.$type.'&ID='.$id. '&lang=' . LANGUAGE_ID; 54 | break; 55 | case 'iblockProperty': 56 | $arProperty = CIBlockProperty::GetByID($id)->Fetch(); 57 | $iblockId = $arProperty['IBLOCK_ID']; 58 | $arIblock = CIBlock::GetArrayByID($iblockId); 59 | $type = $arIblock['IBLOCK_TYPE_ID']; 60 | $urlTemplate = '/bitrix/admin/iblock_edit.php?type='.$type.'&ID='.$iblockId. '&lang=' . LANGUAGE_ID; 61 | break; 62 | case 'iblockSection': 63 | $arSection = CIBlockSection::GetByID($id)->Fetch(); 64 | $iblockId = $arSection['IBLOCK_ID']; 65 | $urlTemplate = '/bitrix/admin/iblock_section_edit.php?IBLOCK_ID='.$iblockId.'&ID='.$id. '&lang=' . LANGUAGE_ID; 66 | break; 67 | } 68 | return $urlTemplate; 69 | }; 70 | 71 | $fGetVersionsHtml = function ($versions) use (& $versionsList) { 72 | 73 | $strings = array_map(function ($version) use ($versionsList) { 74 | return "({$version['id']}) - ". ($versionsList[$version['version']] ?: $version['version']); 75 | }, $versions); 76 | 77 | return implode('
', $strings); 78 | }; 79 | 80 | while ($rowData = $rsData->NavNext()) { 81 | $row = $lAdmin->AddRow($rowData['reference']); 82 | $row->AddField('reference', $rowData['reference']); 83 | $row->AddViewField('versions', $fGetVersionsHtml($rowData['versions'])); 84 | $row->AddViewField('destination', 85 | '[' 86 | .$rowData['data']['id'].'] '.$localization->message('subjects.'.$rowData['data']['subject']) 87 | .'' 88 | ); 89 | } 90 | 91 | if ($_REQUEST["mode"] == "list") { 92 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_js.php"); 93 | } else { 94 | require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php"); 95 | } 96 | 97 | $lAdmin->CheckListMode(); 98 | $lAdmin->DisplayList(); 99 | 100 | if ($_REQUEST["mode"] == "list") { 101 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin_js.php"); 102 | } else { 103 | require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin.php"); 104 | } 105 | -------------------------------------------------------------------------------- /admin/log.php: -------------------------------------------------------------------------------- 1 | SetTitle($localization->getDataByPath('title')); 13 | $sTableID = "ws_migrations_log_table"; 14 | $oSort = new CAdminSorting($sTableID, "date", "asc"); 15 | $lAdmin = new CAdminList($sTableID, $oSort); 16 | 17 | $arHeaders = array( 18 | array("id" => "updateDate", "content" => $localization->getDataByPath('fields.updateDate'), "default"=>true), 19 | array("id" => "description", "content" => $localization->getDataByPath('fields.description'), "default" => true), 20 | array("id" => "source", "content" => $localization->getDataByPath('fields.source'), "default" => true), 21 | array("id" => "dispatcher", "content" => $localization->getDataByPath('fields.dispatcher'), "default" => true) 22 | ); 23 | $lAdmin->AddHeaders($arHeaders); 24 | 25 | $models = AppliedChangesLogModel::find(array( 26 | 'limit' => 500, 27 | 'order' => array( 28 | 'groupLabel' => 'desc' 29 | ) 30 | )); 31 | 32 | $rowsData = array(); 33 | $versions = $module->getPlatformVersion()->getMapVersions(); 34 | array_walk($models, function (AppliedChangesLogModel $model) use (& $rowsData, $versions) { 35 | if (in_array($model->description, array('Insert reference', 'References updates'))) { 36 | return; 37 | } 38 | $row = & $rowsData[$model->groupLabel]; 39 | if(!$row) { 40 | $row = array( 41 | 'label' => $model->groupLabel, 42 | 'updateDate' => $model->date->format('d.m.Y H:i:s'), 43 | 'source' => $versions[$model->source] ?: $model->source, 44 | 'dispatcher' => $model->getSetupLog() ? $model->getSetupLog()->shortUserInfo() : '' 45 | ); 46 | } 47 | $row['description'] = $row['description'] ? implode("
", array($row['description'], $model->description)) : $model->description; 48 | if (!$model->success) { 49 | $row['error'][] = array( 50 | 'data' => \WS\Migrations\jsonToArray($model->description) ?: array('message' => $model->description), 51 | 'id' => $model->id 52 | ); 53 | } 54 | }); 55 | 56 | $rsData = new CAdminResult(null, $sTableID); 57 | $rsData->InitFromArray($rowsData); 58 | $rsData->NavStart(); 59 | 60 | $lAdmin->NavText($rsData->GetNavPrint($localization->getDataByPath('messages.pages'))); 61 | while ($rowData = $rsData->NavNext()) { 62 | $row = $lAdmin->AddRow($rowData['label'], $rowData); 63 | $description = $rowData['description']; 64 | if ($rowData['error']) { 65 | $description = array(); 66 | foreach ($rowData['error'] as $rowErrorData) { 67 | $description[] = ''.$rowErrorData['data']['message'].''; 68 | } 69 | $description = implode('
', $description); 70 | } 71 | 72 | $row->AddViewField('description', $description); 73 | $row->AddActions(array( 74 | array( 75 | "ICON" => "view", 76 | "TEXT" => $localization->message('messages.view'), 77 | "ACTION" => $lAdmin->ActionRedirect("ws_migrations.php?q=detail&label=".$rowData['label'].'&type=applied'. '&lang=' . LANGUAGE_ID), 78 | "DEFAULT" => true 79 | ) 80 | )); 81 | } 82 | if ($_REQUEST["mode"] == "list") { 83 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_js.php"); 84 | } else { 85 | require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php"); 86 | } 87 | 88 | $lAdmin->CheckListMode(); 89 | $lAdmin->DisplayList(); 90 | ?> 91 | 107 | isAdmin()) { 4 | return array(); 5 | } 6 | $loc = \WS\Migrations\Module::getInstance()->getLocalization('menu'); 7 | $inputUri = '/bitrix/admin/ws_migrations.php?lang=' . LANGUAGE_ID . '&q='; 8 | return array( 9 | array( 10 | 'parent_menu' => 'global_menu_settings', 11 | 'sort' => 500, 12 | 'text' => $loc->getDataByPath('title'), 13 | 'title' => $loc->getDataByPath('title'), 14 | 'module_id' => 'ws.migrations', 15 | 'icon' => '', 16 | 'items_id' => 'ws_migrations_menu', 17 | 'items' => array( 18 | array( 19 | 'text' => $loc->getDataByPath('apply'), 20 | 'url' => $inputUri.'main', 21 | ), 22 | array( 23 | 'text' => $loc->getDataByPath('automigrations'), 24 | 'items_id' => 'ws_migrations_menu_auto', 25 | 'items' => array( 26 | array( 27 | 'text' => $loc->getDataByPath('changeversion'), 28 | 'url' => $inputUri.'changeversion', 29 | ), 30 | array( 31 | 'text' => $loc->getDataByPath('entitiesVersions'), 32 | 'url' => $inputUri.'entitiesVersions' 33 | ), 34 | ), 35 | ), 36 | array( 37 | 'text' => $loc->getDataByPath('createScenario'), 38 | 'url' => $inputUri.'createScenario' 39 | ), 40 | array( 41 | 'text' => $loc->getDataByPath('diagnostic'), 42 | 'url' => $inputUri.'diagnostic' 43 | ), 44 | array( 45 | 'text' => $loc->getDataByPath('log'), 46 | 'url' => $inputUri.'log', 47 | 'more_url' => array($inputUri.'detail') 48 | ) 49 | ) 50 | ) 51 | ); 52 | -------------------------------------------------------------------------------- /admin/newChangesList.php: -------------------------------------------------------------------------------- 1 | "date", "content" => $localization->getDataByPath('fields.date'), "default"=>true), 15 | array("id" => "description", "content" => $localization->getDataByPath('fields.description'), "default" => true), 16 | array("id" => "source", "content" => $localization->getDataByPath('fields.source'), "default" => true), 17 | ); 18 | $lAdmin->AddHeaders($arHeaders); 19 | 20 | /** @var CollectorFix[] $fixes */ 21 | $fixes = $module->getNotAppliedFixes(); 22 | 23 | $rowsData = array(); 24 | array_walk($fixes, function (CollectorFix $fix) use (& $rowsData, $localization) { 25 | if (in_array($fix->getName(), array('Insert reference', 'References updates', 'Reference fix'))) { 26 | return; 27 | } 28 | $row = & $rowsData[$fix->getLabel()]; 29 | if(!$row) { 30 | $time = str_replace(".json", "", $fix->getLabel()); 31 | $row = array( 32 | 'label' => $fix->getLabel(), 33 | 'date' => FormatDate("Q " . $localization->message("message.ago"), $time), 34 | 'source' => $fix->getOwner() ?: $fix->getDbVersion() 35 | ); 36 | } 37 | $row['description'] = $row['description'] ? implode("
", array($row['description'], $fix->getName())) : $fix->getName(); 38 | }); 39 | 40 | $rsData = new CAdminResult(null, $sTableID); 41 | $rsData->InitFromArray($rowsData); 42 | $rsData->NavStart(500); 43 | 44 | while ($rowData = $rsData->NavNext()) { 45 | $row = $lAdmin->AddRow($rowData['label'], $rowData); 46 | $row->AddViewField('description', $rowData['description']); 47 | $row->AddActions(array( 48 | array( 49 | "ICON" => "view", 50 | "TEXT" => $localization->message('message.view'), 51 | "ACTION" => $lAdmin->ActionRedirect("ws_migrations.php?q=detail&label=".$rowData['label'].'&type=new'. '&lang=' . LANGUAGE_ID), 52 | "DEFAULT" => true 53 | ) 54 | )); 55 | } 56 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_js.php"); 57 | 58 | $lAdmin->CheckListMode(); 59 | $lAdmin->DisplayList(); 60 | 61 | require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin_js.php"); 62 | -------------------------------------------------------------------------------- /data/apply_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/apply_scheme.png -------------------------------------------------------------------------------- /data/change_analyze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/change_analyze.png -------------------------------------------------------------------------------- /data/change_event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/change_event.png -------------------------------------------------------------------------------- /data/classes_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/classes_scheme.png -------------------------------------------------------------------------------- /data/cli_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/cli_apply.png -------------------------------------------------------------------------------- /data/cli_help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/cli_help.png -------------------------------------------------------------------------------- /data/cli_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/cli_list.png -------------------------------------------------------------------------------- /data/cli_rollback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/cli_rollback.png -------------------------------------------------------------------------------- /data/create_dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/create_dump.png -------------------------------------------------------------------------------- /data/create_scenario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/create_scenario.png -------------------------------------------------------------------------------- /data/create_scenario_was_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/create_scenario_was_success.png -------------------------------------------------------------------------------- /data/development_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/development_scheme.png -------------------------------------------------------------------------------- /data/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/export.png -------------------------------------------------------------------------------- /data/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/import.png -------------------------------------------------------------------------------- /data/init_module_params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/init_module_params.png -------------------------------------------------------------------------------- /data/item_changes_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/item_changes_list.png -------------------------------------------------------------------------------- /data/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/main.png -------------------------------------------------------------------------------- /data/main_with_scenario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/data/main_with_scenario.png -------------------------------------------------------------------------------- /data/scenarioTemplate.tpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /install/db/install.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `ws_migrations_apply_changes_log` ( 2 | `ID` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | `SETUP_LOG_ID` INT (11), 4 | `GROUP_LABEL` VARCHAR (128), 5 | `SOURCE` VARCHAR(50), 6 | `DATE` DATETIME NOT NULL, 7 | `PROCESS` VARCHAR (128) NOT NULL, 8 | `SUBJECT` VARCHAR (128) DEFAULT NULL, 9 | `UPDATE_DATA` MEDIUMTEXT NOT NULL, 10 | `ORIGINAL_DATA` MEDIUMTEXT DEFAULT NULL, 11 | `SUCCESS` INT (1), 12 | `DESCRIPTION` TEXT NOT NULL 13 | ); 14 | CREATE TABLE IF NOT EXISTS `ws_migrations_setups_log` ( 15 | `ID` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 16 | `USER_ID` INT, 17 | `DATE` DATETIME NOT NULL 18 | ); 19 | 20 | CREATE TABLE IF NOT EXISTS `ws_migrations_db_version_references` ( 21 | `ID` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 22 | `REFERENCE` VARCHAR (40), 23 | `DB_VERSION` VARCHAR(64), 24 | `GROUP` VARCHAR (64), 25 | `ITEM_ID` VARCHAR (32) 26 | ); 27 | -------------------------------------------------------------------------------- /install/db/uninstall.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE if exists ws_migrations_apply_changes_log; 2 | DROP TABLE if exists ws_migrations_setups_log; 3 | DROP TABLE if exists ws_migrations_db_version_references; -------------------------------------------------------------------------------- /install/form.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/install/form.php -------------------------------------------------------------------------------- /install/tools/ws_migrations.php: -------------------------------------------------------------------------------- 1 | "1.6.3", 4 | "VERSION_DATE" => "2016-05-10 11:00:00" 5 | ); 6 | ?> 7 | -------------------------------------------------------------------------------- /lang/en/admin.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'title' => 'Migrations management', 5 | 'list' => array( 6 | 'auto' => 'New auto migrations', 7 | 'scenarios' => 'New scenarios' 8 | ), 9 | 'version' => 'The current version of the database', 10 | 'change_link' => 'change version', 11 | 'errorList' => 'Unsuccessful applied migrations', 12 | 'appliedList' => 'Successful applied migrations', 13 | 'btnRollback' => 'Undo last change', 14 | 'btnApply' => 'Apply', 15 | 'lastSetup' => array( 16 | 'sectionName' => 'Last update :time: - :user:' 17 | ), 18 | 'common' => array( 19 | 'listEmpty' => 'List is empty', 20 | 'reference-fix' => 'References synchronizing', 21 | 'pageEmpty' => 'Data for update don`t exists yet' 22 | ), 23 | 'newChangesDetail' => 'Changes list', 24 | 'newChangesTitle' => 'New changes', 25 | 'errorWindow' => 'Error info', 26 | 'diagnostic' => 'Errors diagnosis, the use of the migration is possible only after the correction', 27 | 'platformVersion' => array( 28 | 'ok' => 'Platform version', 29 | 'error' => 'Incorrect platform version' 30 | ) 31 | ), 32 | 'changeversion' => array( 33 | 'pageTitle' => 'Platform versions', 34 | 'title' => 'Current platform version', 35 | 'version' => 'HASH', 36 | 'setup' => 'setup', 37 | 'owner' => 'Signature', 38 | 'button_change' => 'Change HASH', 39 | 'description' => "Each project area has a unique identifier (HASH) to synchronize data.", 40 | 'dialog' => array( 41 | 'title' => 'Set the name of the project owner\'s version' 42 | ), 43 | 'otherVersions' => array( 44 | 'tab' => 'Other versions of the project' 45 | ) 46 | ), 47 | 'newChangesList' => array( 48 | 'fields' => array( 49 | 'date' => 'Date create', 50 | 'description' => 'Description', 51 | 'source' => 'Source', 52 | ), 53 | 'message' => array( 54 | "ago" => 'back', 55 | 'view' => 'detail' 56 | ) 57 | ), 58 | 'applyError' => array( 59 | 'message' => 'Message', 60 | 'data' => 'Data', 61 | 'trace' => 'Call stack', 62 | 'error' => array( 63 | 'modelNotExists' => 'Data for record id =: id: does not exist' 64 | ) 65 | ), 66 | 'entitiesVersions' => array( 67 | 'title' => 'References list', 68 | 'fields' => array( 69 | 'reference' => 'HASH', 70 | 'versions' => 'Other versions Ids', 71 | 'destination' => 'Reference' 72 | ), 73 | 'messages' => array( 74 | 'pages' => 'Pages' 75 | ), 76 | 'subjects' => array( 77 | 'iblock' => 'Information block', 78 | 'iblockProperty' => 'Information block property', 79 | 'iblockSection' => 'Information block section', 80 | ) 81 | ), 82 | 'createScenario' => array( 83 | 'title' => 'The script scenario', 84 | 'field' => array( 85 | 'name' => 'Title', 86 | 'description' => 'Description' 87 | ), 88 | 'path-to-file' => 'Class file migration is #path#', 89 | 'save-file-error' => 'An error occured save file', 90 | 'button' => array( 91 | 'create' => 'Create migration scenario' 92 | ) 93 | ), 94 | 'diagnostic' => array( 95 | 'title' => 'Platform diagnostic', 96 | 'description' => 'Diagnostics status, problem-solving tips', 97 | 'last' => array( 98 | 'description' => 'Description', 99 | 'result' => 'Result', 100 | 'success' => 'Success', 101 | 'fail' => 'Fail' 102 | ), 103 | 'run' => 'Run diagnostic', 104 | ), 105 | 'log' => array( 106 | 'title' => 'Updates log', 107 | 'fields' => array( 108 | 'updateDate' => 'Date', 109 | 'description' => 'Update features', 110 | 'source' => 'Source', 111 | 'dispatcher' => 'Update by' 112 | ), 113 | 'messages' => array( 114 | 'InsertReference' => 'Insert other reference', 115 | 'view' => 'Analysis of changes', 116 | 'pages' => 'Pages', 117 | 'actualization' => 'Actualization sources', 118 | 'descriptionMoreLink' => 'detail', 119 | 'errorWindow' => 'Error information' 120 | ) 121 | ), 122 | 'detail' => array( 123 | 'title' => '#date. #source. Update by - #deployer', 124 | 'tabs' => array( 125 | 'diff' => 'Features', 126 | 'final' => 'Update result', 127 | 'merge' => 'Data before update' 128 | ), 129 | 'message' => array( 130 | 'nobody' => 'The site is not updated', 131 | 'show' => 'show data', 132 | 'hide' => 'hide data', 133 | ), 134 | 'serviceLabels' => array( 135 | '~reference' => 'HASH', 136 | '~property_list_values' => 'VALUES', 137 | 'Reference fix' => 'Register links with the essence of the platform', 138 | 'Insert reference' => 'The new entity reference', 139 | 'reference' => 'HASH', 140 | 'group' => 'Group entity ( the handler )', 141 | 'dbVersion' => 'Platform version' 142 | ) 143 | ), 144 | 'cli' => array( 145 | 'common' => array( 146 | 'reference-fix' => 'References synchronizing' 147 | ), 148 | ), 149 | ); -------------------------------------------------------------------------------- /lang/en/handlers.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'name' => 'Information block' 5 | ), 6 | 'iblockProperty' => array( 7 | 'name' => 'The property information block', 8 | 'errors' => array( 9 | 'iblockNotExists' => 'Information block with id `:id:` does not exist' 10 | ) 11 | ), 12 | 'iblockSection' => array( 13 | 'name' => 'Section information block' 14 | ) 15 | ); -------------------------------------------------------------------------------- /lang/en/info.php: -------------------------------------------------------------------------------- 1 | 'Data migrations', 4 | 'description' => 'Allows you to synchronize data from different copies of site', 5 | 'partner' => array( 6 | 'name' => 'WorkSolutions', 7 | 'url' => 'http://www.worksolutions.ru' 8 | ) 9 | ); -------------------------------------------------------------------------------- /lang/en/menu.php: -------------------------------------------------------------------------------- 1 | 'Data migrations', 4 | 'changeversion' => 'Platform versions', 5 | 'apply' => 'Update', 6 | 'automigrations' => 'Auto migrations', 7 | 'log' => 'Log', 8 | 'entitiesVersions' => 'References', 9 | 'createScenario' => 'Scenario', 10 | 'diagnostic' => 'Diagnostic' 11 | ); -------------------------------------------------------------------------------- /lang/en/processes.php: -------------------------------------------------------------------------------- 1 | 'Addition', 4 | 'update' => 'Update', 5 | 'delete' => 'Remove' 6 | ); -------------------------------------------------------------------------------- /lang/en/setup.php: -------------------------------------------------------------------------------- 1 | 'Installing the Data migrations module', 4 | 'tab' => 'Settings', 5 | 'description' => 'You must specify the directory location of the migration file. relative to the root directory of the project. ($ SERVER [\'DOCUMENT_ROOT\']) ', 6 | 'fields' => array( 7 | 'catalog' => 'Directory path', 8 | 'useAutotests' => 'Use auto-tests (for developers)', 9 | 'apply' => 'Set Up' 10 | ), 11 | 'errors' => array( 12 | 'notCreateDir' => 'Unable to create directory', 13 | 'catalogFieldEmpty' => 'Catalog field is empty' 14 | ), 15 | 'section' => array( 16 | 'disableHandlers' => 'Use synchronization' 17 | ) 18 | ); -------------------------------------------------------------------------------- /lang/en/uninstall.php: -------------------------------------------------------------------------------- 1 | 'Deleting data migration module', 4 | 'tab' => 'Delete options', 5 | 'description' => "When you delete data , the project will not be able to work with the current migrations.\n" 6 | ."You will need to initialize the mechanism of migration once again , starting with the current version .\n" 7 | ."Information to remove : tables migration , customization , migration catalog (if used versioning system" 8 | ."should register changes )", 9 | 'fields' => array( 10 | 'removeAll' => 'Erase migration data' 11 | ) 12 | ); -------------------------------------------------------------------------------- /lang/ru/admin.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/admin.php -------------------------------------------------------------------------------- /lang/ru/handlers.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/handlers.php -------------------------------------------------------------------------------- /lang/ru/info.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/info.php -------------------------------------------------------------------------------- /lang/ru/menu.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/menu.php -------------------------------------------------------------------------------- /lang/ru/processes.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/processes.php -------------------------------------------------------------------------------- /lang/ru/setup.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/setup.php -------------------------------------------------------------------------------- /lang/ru/uninstall.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lang/ru/uninstall.php -------------------------------------------------------------------------------- /lib/applyresult.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations; 7 | 8 | 9 | class ApplyResult { 10 | private $_success; 11 | private $_message; 12 | private $_id; 13 | 14 | /** 15 | * @param string $value 16 | * @return $this 17 | */ 18 | public function setMessage($value) { 19 | $this->_message = $value; 20 | return $this; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getMessage() { 27 | return $this->_message; 28 | } 29 | 30 | /** 31 | * @return boolean 32 | */ 33 | public function isSuccess() { 34 | return $this->_success; 35 | } 36 | 37 | /** 38 | * @param boolean $value 39 | * @return $this 40 | */ 41 | public function setSuccess($value) { 42 | $this->_success = $value; 43 | return $this; 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function getId() { 50 | return $this->_id; 51 | } 52 | 53 | /** 54 | * @param mixed $id 55 | * @return $this 56 | */ 57 | public function setId($id) { 58 | $this->_id = $id; 59 | return $this; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /lib/builder/agentbuilder.php: -------------------------------------------------------------------------------- 1 | agent = null; 15 | } 16 | 17 | /** 18 | * @param $callback 19 | * @return Agent 20 | * @throws BuilderException 21 | */ 22 | public function addAgent($callback) { 23 | if ($this->agent) { 24 | throw new BuilderException('reset builder data for continue'); 25 | } 26 | $this->agent = new Agent($callback); 27 | return $this->agent; 28 | } 29 | 30 | /** 31 | * @param $callback 32 | * @return Agent 33 | * @throws BuilderException 34 | */ 35 | public function getAgent($callback) { 36 | if ($this->agent) { 37 | throw new BuilderException('reset builder data for continue'); 38 | } 39 | $data = $this->findAgent($callback); 40 | $this->agent = new Agent($callback, $data); 41 | return $this->agent; 42 | } 43 | 44 | /** 45 | * @throws BuilderException 46 | */ 47 | public function commit() { 48 | global $DB, $APPLICATION; 49 | $DB->StartTransaction(); 50 | try { 51 | $agent = $this->agent; 52 | if ($agent->getId() > 0) { 53 | $res = \CAgent::Update($agent->getId(), $agent->getSaveData()); 54 | if (!$res) { 55 | throw new BuilderException("Agent wasn't updated"); 56 | } 57 | } else { 58 | $res = \CAgent::AddAgent( 59 | $agent->callback, 60 | $agent->module, 61 | $agent->isPeriod, 62 | $agent->interval, 63 | '', 64 | $agent->active, 65 | $agent->nextExec, 66 | $agent->sort, 67 | $agent->userId 68 | ); 69 | if (!$res) { 70 | throw new BuilderException("Agent wasn't created: " . $APPLICATION->GetException()->GetString()); 71 | } 72 | $agent->setId($res); 73 | } 74 | 75 | } catch (BuilderException $e) { 76 | $DB->Rollback(); 77 | throw new BuilderException($e->getMessage()); 78 | } 79 | $DB->Commit(); 80 | } 81 | 82 | /** 83 | * @return Agent 84 | */ 85 | public function getCurrentAgent() { 86 | return $this->agent; 87 | } 88 | 89 | /** 90 | * @param $callback 91 | * @return array 92 | * @throws BuilderException 93 | */ 94 | private function findAgent($callback) { 95 | $agent = \CAgent::GetList(null, array( 96 | 'NAME' => $callback, 97 | ))->Fetch(); 98 | if (empty($agent)) { 99 | throw new BuilderException("Agent {$callback} not found"); 100 | } 101 | return $agent; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /lib/builder/builderexception.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 23 | $this->setDefault(); 24 | $this->setSaveData($data); 25 | } 26 | 27 | public function getMap() { 28 | return array( 29 | 'id' => 'ID', 30 | 'sort' => 'SORT', 31 | 'active' => 'ACTIVE', 32 | 'module' => 'MODULE_ID', 33 | 'callback' => 'NAME', 34 | 'userId' => 'USER_ID', 35 | 'isPeriod' => 'IS_PERIOD', 36 | 'interval' => 'AGENT_INTERVAL', 37 | 'nextExec' => 'NEXT_EXEC', 38 | ); 39 | } 40 | 41 | /** 42 | * @param int $id 43 | * @return Agent 44 | */ 45 | public function setId($id) { 46 | $this->id = $id; 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param int $sort 52 | * @return Agent 53 | */ 54 | public function setSort($sort) { 55 | $this->sort = $sort; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return int 61 | */ 62 | public function getId() { 63 | return $this->id; 64 | } 65 | 66 | private function setDefault() { 67 | $this 68 | ->setSort(100) 69 | ->setActive(true) 70 | ->setInterval(86400) 71 | ->setIsPeriod(false) 72 | ->setNextExec(new DateTime()); 73 | } 74 | 75 | /** 76 | * @param int $userId 77 | * @return Agent 78 | */ 79 | public function setUserId($userId) { 80 | $this->userId = $userId; 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param int $interval 86 | * @return Agent 87 | */ 88 | public function setInterval($interval) { 89 | $this->interval = $interval; 90 | return $this; 91 | } 92 | 93 | /** 94 | * @param bool $active 95 | * @return Agent 96 | */ 97 | public function setActive($active) { 98 | $this->active = $active ? 'Y' : 'N'; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @param string $module 104 | * @return Agent 105 | */ 106 | public function setModule($module) { 107 | $this->module = $module; 108 | return $this; 109 | } 110 | 111 | /** 112 | * @param string $callback 113 | * @return Agent 114 | */ 115 | public function setCallback($callback) { 116 | $this->callback = $callback; 117 | return $this; 118 | } 119 | 120 | /** 121 | * @param bool $isPeriod 122 | * @return Agent 123 | */ 124 | public function setIsPeriod($isPeriod) { 125 | $this->isPeriod = $isPeriod ? 'Y' : 'N'; 126 | return $this; 127 | } 128 | 129 | /** 130 | * @param DateTime $nextExec 131 | * @return Agent 132 | */ 133 | public function setNextExec($nextExec) { 134 | $this->nextExec = $nextExec->format('d.m.Y H:i:s'); 135 | return $this; 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /lib/builder/entity/base.php: -------------------------------------------------------------------------------- 1 | data[$prop]; 12 | } 13 | 14 | public function __set($prop, $value) { 15 | $this->data[$prop] = $value; 16 | return $value; 17 | } 18 | 19 | public function getSaveData() { 20 | $map = $this->getMap(); 21 | $fields = array(); 22 | foreach ($this->data as $key => $value) { 23 | if (!isset($map[$key])) { 24 | $fields[$key] = $value; 25 | continue; 26 | } 27 | $fields[$map[$key]] = $value; 28 | } 29 | return $fields; 30 | } 31 | 32 | public function setSaveData($data) { 33 | $map = array_flip($this->getMap()); 34 | foreach ($data as $key => $val) { 35 | if (!isset($map[$key])) { 36 | $this->{$key} = $val; 37 | continue; 38 | } 39 | $this->{$map[$key]} = $val; 40 | } 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public abstract function getMap(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/builder/entity/enumvariant.php: -------------------------------------------------------------------------------- 1 | value = $value; 19 | $this->setSaveData($data); 20 | } 21 | 22 | public function getMap() { 23 | return array( 24 | 'id' => 'ID', 25 | 'value' => 'VALUE', 26 | 'xmlId' => 'XML_ID', 27 | 'sort' => 'SORT', 28 | 'default' => 'DEF', 29 | 'del' => 'DEL', 30 | ); 31 | } 32 | 33 | /** 34 | * @param int $id 35 | * @return IblockType 36 | */ 37 | public function setId($id) { 38 | $this->id = $id; 39 | return $this; 40 | } 41 | 42 | /** 43 | * @return int 44 | */ 45 | public function getId() { 46 | return $this->id; 47 | } 48 | 49 | /** 50 | * @param string $value 51 | * @return EnumVariant 52 | */ 53 | public function setValue($value) { 54 | $this->value = $value; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @param string $xmlId 60 | * @return EnumVariant 61 | */ 62 | public function setXmlId($xmlId) { 63 | $this->xmlId = $xmlId; 64 | return $this; 65 | } 66 | 67 | /** 68 | * @param int $sort 69 | * @return EnumVariant 70 | */ 71 | public function setSort($sort) { 72 | $this->sort = $sort; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param bool $default 78 | * @return EnumVariant 79 | */ 80 | public function setDefault($default) { 81 | $this->default = $default ? 'Y' : 'N'; 82 | return $this; 83 | } 84 | } -------------------------------------------------------------------------------- /lib/builder/entity/eventmessage.php: -------------------------------------------------------------------------------- 1 | setEmailFrom($from) 27 | ->setEmailTo($to) 28 | ->setSiteId($siteId); 29 | 30 | $this->setSaveData($data); 31 | $this->dateUpdate = new DateTime(); 32 | } 33 | 34 | public function getMap() { 35 | return array( 36 | 'id' => 'ID', 37 | 'siteId' => 'LID', 38 | 'active' => 'ACTIVE', 39 | 'emailFrom' => 'EMAIL_FROM', 40 | 'emailTo' => 'EMAIL_TO', 41 | 'subject' => 'SUBJECT', 42 | 'message' => 'MESSAGE', 43 | 'bodyType' => 'BODY_TYPE', 44 | 'bcc' => 'BCC', 45 | 'dateUpdate' => 'TIMESTAMP_X', 46 | ); 47 | } 48 | 49 | /** 50 | * @param int $id 51 | * @return EventMessage 52 | */ 53 | public function setId($id) { 54 | $this->id = $id; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return int 60 | */ 61 | public function getId() { 62 | return $this->id; 63 | } 64 | 65 | /** 66 | * @param string $siteId 67 | * @return EventMessage 68 | */ 69 | public function setSiteId($siteId) { 70 | $this->siteId = $siteId; 71 | return $this; 72 | } 73 | 74 | /** 75 | * @param bool $active 76 | * @return EventMessage 77 | */ 78 | public function setActive($active) { 79 | $this->active = $active ? 'Y' : 'N'; 80 | return $this; 81 | } 82 | 83 | /** 84 | * @param string $emailFrom 85 | * @return EventMessage 86 | */ 87 | public function setEmailFrom($emailFrom) { 88 | $this->emailFrom = $emailFrom; 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param string $emailTo 94 | * @return EventMessage 95 | */ 96 | public function setEmailTo($emailTo) { 97 | $this->emailTo = $emailTo; 98 | return $this; 99 | } 100 | 101 | /** 102 | * @param string $subject 103 | * @return EventMessage 104 | */ 105 | public function setSubject($subject) { 106 | $this->subject = $subject; 107 | return $this; 108 | } 109 | 110 | /** 111 | * @param string $message 112 | * @return EventMessage 113 | */ 114 | public function setMessage($message) { 115 | $this->message = $message; 116 | return $this; 117 | } 118 | 119 | /** 120 | * @param string $bodyType 121 | * @return EventMessage 122 | */ 123 | public function setBodyType($bodyType) { 124 | $this->bodyType = $bodyType; 125 | return $this; 126 | } 127 | 128 | /** 129 | * @param string $bcc 130 | * @return EventMessage 131 | */ 132 | public function setBcc($bcc) { 133 | $this->bcc = $bcc; 134 | return $this; 135 | } 136 | 137 | public function remove() { 138 | $this->forRemove = true; 139 | } 140 | 141 | public function isRemoved() { 142 | return $this->forRemove; 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /lib/builder/entity/eventtype.php: -------------------------------------------------------------------------------- 1 | eventName = $eventName; 19 | $this->lid = $lid; 20 | $this->setSaveData($data); 21 | } 22 | 23 | public function getMap() { 24 | return array( 25 | 'id' => 'ID', 26 | 'sort' => 'SORT', 27 | 'eventName' => 'EVENT_NAME', 28 | 'lid' => 'LID', 29 | 'name' => 'NAME', 30 | 'description' => 'DESCRIPTION', 31 | ); 32 | } 33 | 34 | /** 35 | * @param int $id 36 | * @return EventType 37 | */ 38 | public function setId($id) { 39 | $this->id = $id; 40 | return $this; 41 | } 42 | 43 | /** 44 | * @return int 45 | */ 46 | public function getId() { 47 | return $this->id; 48 | } 49 | 50 | /** 51 | * @param int $sort 52 | * @return EventType 53 | */ 54 | public function setSort($sort) { 55 | $this->sort = $sort; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @param string $eventName 61 | * @return EventType 62 | */ 63 | public function setEventName($eventName) { 64 | $this->eventName = $eventName; 65 | return $this; 66 | } 67 | 68 | /** 69 | * @param string $lid 70 | * @return EventType 71 | */ 72 | public function setLid($lid) { 73 | $this->lid = $lid; 74 | return $this; 75 | } 76 | 77 | /** 78 | * @param string $name 79 | * @return EventType 80 | */ 81 | public function setName($name) { 82 | $this->name = $name; 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param string $description 88 | * @return EventType 89 | */ 90 | public function setDescription($description) { 91 | $this->description = $description; 92 | return $this; 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /lib/builder/entity/formanswer.php: -------------------------------------------------------------------------------- 1 | message = $message; 32 | $this->setSaveData($data); 33 | } 34 | 35 | public function getMap() { 36 | return array( 37 | 'id' => 'ID', 38 | 'sort' => 'C_SORT', 39 | 'message' => 'MESSAGE', 40 | 'value' => 'VALUE', 41 | 'active' => 'ACTIVE', 42 | 'fieldType' => 'FIELD_TYPE', 43 | 'fieldWidth' => 'FIELD_WIDTH', 44 | 'fieldHeight' => 'FIELD_HEIGHT', 45 | 'fieldParam' => 'FIELD_PARAM', 46 | 'del' => 'DEL', 47 | ); 48 | } 49 | 50 | /** 51 | * @param int $id 52 | * @return FormAnswer 53 | */ 54 | public function setId($id) { 55 | $this->id = $id; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return int 61 | */ 62 | public function getId() { 63 | return $this->id; 64 | } 65 | 66 | /** 67 | * @param int $sort 68 | * @return FormAnswer 69 | */ 70 | public function setSort($sort) { 71 | $this->sort = $sort; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @param bool $active 77 | * @return FormAnswer 78 | */ 79 | public function setActive($active) { 80 | $this->active = $active ? 'Y' : 'N'; 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param string $message 86 | * @return FormAnswer 87 | */ 88 | public function setMessage($message) { 89 | $this->message = $message; 90 | return $this; 91 | } 92 | 93 | /** 94 | * @param string $value 95 | * @return FormAnswer 96 | */ 97 | public function setValue($value) { 98 | $this->value = $value; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @param string $fieldType 104 | * @return FormAnswer 105 | */ 106 | public function setFieldType($fieldType) { 107 | $this->fieldType = $fieldType; 108 | return $this; 109 | } 110 | 111 | /** 112 | * @param string $fieldWidth 113 | * @return FormAnswer 114 | */ 115 | public function setFieldWidth($fieldWidth) { 116 | $this->fieldWidth = $fieldWidth; 117 | return $this; 118 | } 119 | 120 | /** 121 | * @param string $fieldHeight 122 | * @return FormAnswer 123 | */ 124 | public function setFieldHeight($fieldHeight) { 125 | $this->fieldHeight = $fieldHeight; 126 | return $this; 127 | } 128 | 129 | /** 130 | * @param string $fieldParam 131 | * @return FormAnswer 132 | */ 133 | public function setFieldParam($fieldParam) { 134 | $this->fieldParam = $fieldParam; 135 | return $this; 136 | } 137 | 138 | public function needDelete() { 139 | return $this->del == 'Y'; 140 | } 141 | 142 | 143 | } -------------------------------------------------------------------------------- /lib/builder/entity/formstatus.php: -------------------------------------------------------------------------------- 1 | title = $title; 27 | $this->setSaveData($data); 28 | $this->dateUpdate = new DateTime(); 29 | } 30 | 31 | public function getMap() { 32 | return array( 33 | 'id' => 'ID', 34 | 'sort' => 'C_SORT', 35 | 'dateUpdate' => 'TIMESTAMP_X', 36 | 'active' => 'ACTIVE', 37 | 'title' => 'TITLE', 38 | 'description' => 'DESCRIPTION', 39 | 'isDefault' => 'DEFAULT_VALUE', 40 | 'css' => 'CSS', 41 | 'handlerOut' => 'HANDLER_OUT', 42 | 'handlerIn' => 'HANDLER_IN', 43 | 'arGroupCanView' => 'arPERMISSION_VIEW', 44 | 'arGroupCanMove' => 'arPERMISSION_MOVE', 45 | 'arGroupCanEdit' => 'arPERMISSION_EDIT', 46 | 'arGroupCanDelete' => 'arPERMISSION_DELETE', 47 | ); 48 | } 49 | 50 | /** 51 | * @param int $id 52 | * @return FormStatus 53 | */ 54 | public function setId($id) { 55 | $this->id = $id; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return int 61 | */ 62 | public function getId() { 63 | return $this->id; 64 | } 65 | 66 | /** 67 | * @param int $sort 68 | * @return FormStatus 69 | */ 70 | public function setSort($sort) { 71 | $this->sort = $sort; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @param bool $active 77 | * @return FormStatus 78 | */ 79 | public function setActive($active) { 80 | $this->active = $active ? 'Y' : 'N'; 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param string $title 86 | * @return FormStatus 87 | */ 88 | public function setTitle($title) { 89 | $this->title = $title; 90 | return $this; 91 | } 92 | 93 | /** 94 | * @param string $description 95 | * @return FormStatus 96 | */ 97 | public function setDescription($description) { 98 | $this->description = $description; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @param bool $isDefault 104 | * @return FormStatus 105 | */ 106 | public function setIsDefault($isDefault) { 107 | $this->isDefault = $isDefault ? 'Y' : 'N'; 108 | return $this; 109 | } 110 | 111 | /** 112 | * @param string $css 113 | * @return FormStatus 114 | */ 115 | public function setCss($css) { 116 | $this->css = $css; 117 | return $this; 118 | } 119 | 120 | /** 121 | * @param string $handlerOut 122 | * @return FormStatus 123 | */ 124 | public function setHandlerOut($handlerOut) { 125 | $this->handlerOut = $handlerOut; 126 | return $this; 127 | } 128 | 129 | /** 130 | * @param string $handlerIn 131 | * @return FormStatus 132 | */ 133 | public function setHandlerIn($handlerIn) { 134 | $this->handlerIn = $handlerIn; 135 | return $this; 136 | } 137 | 138 | /** 139 | * @param array $arGroupCanView 140 | * @return FormStatus 141 | */ 142 | public function setArGroupCanView($arGroupCanView) { 143 | $this->arGroupCanView = $arGroupCanView; 144 | return $this; 145 | } 146 | 147 | /** 148 | * @param array $arGroupCanMove 149 | * @return FormStatus 150 | */ 151 | public function setArGroupCanMove($arGroupCanMove) { 152 | $this->arGroupCanMove = $arGroupCanMove; 153 | return $this; 154 | } 155 | 156 | /** 157 | * @param array $arGroupCanEdit 158 | * @return FormStatus 159 | */ 160 | public function setArGroupCanEdit($arGroupCanEdit) { 161 | $this->arGroupCanEdit = $arGroupCanEdit; 162 | return $this; 163 | } 164 | 165 | /** 166 | * @param array $arGroupCanDelete 167 | * @return FormStatus 168 | */ 169 | public function setArGroupCanDelete($arGroupCanDelete) { 170 | $this->arGroupCanDelete = $arGroupCanDelete; 171 | return $this; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /lib/builder/entity/highloadblock.php: -------------------------------------------------------------------------------- 1 | name = $name; 16 | $this->tableName = $tableName; 17 | $this->id = $id; 18 | } 19 | 20 | public function getMap() { 21 | return array( 22 | 'id' => 'ID', 23 | 'name' => 'NAME', 24 | 'tableName' => 'TABLE_NAME', 25 | ); 26 | } 27 | 28 | /** 29 | * @param int $id 30 | * @return IblockType 31 | */ 32 | public function setId($id) { 33 | $this->id = $id; 34 | return $this; 35 | } 36 | 37 | /** 38 | * @return int 39 | */ 40 | public function getId() { 41 | return $this->id; 42 | } 43 | 44 | /** 45 | * @param string $name 46 | * @return HighLoadBlock 47 | */ 48 | public function setName($name) { 49 | $this->name = $name; 50 | return $this; 51 | } 52 | 53 | /** 54 | * @param string $tableName 55 | * @return HighLoadBlock 56 | */ 57 | public function setTableName($tableName) { 58 | $this->tableName = $tableName; 59 | return $this; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /lib/builder/entity/iblocksection.php: -------------------------------------------------------------------------------- 1 | name = $name; 30 | $this->childs = array(); 31 | $this->setSaveData($data); 32 | } 33 | 34 | public function getMap() { 35 | return array( 36 | 'id' => 'ID', 37 | 'iblockId' => 'IBLOCK_ID', 38 | 'code' => 'CODE', 39 | 'xmlId' => 'XML_ID', 40 | 'sort' => 'SORT', 41 | 'name' => 'NAME', 42 | 'active' => 'ACTIVE', 43 | 'picture' => 'PICTURE', 44 | 'description' => 'DESCRIPTION', 45 | 'descriptionType' => 'DESCRIPTION_TYPE', 46 | 'detailPicture' => 'DETAIL_PICTURE', 47 | ); 48 | } 49 | 50 | /** 51 | * @param int $id 52 | * @return EventType 53 | */ 54 | public function setId($id) { 55 | $this->id = $id; 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return int 61 | */ 62 | public function getId() { 63 | return $this->id; 64 | } 65 | 66 | /** 67 | * @param int $sort 68 | * @return EventType 69 | */ 70 | public function setSort($sort) { 71 | $this->sort = $sort; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @param string $eventName 77 | * @return EventType 78 | */ 79 | public function setEventName($eventName) { 80 | $this->eventName = $eventName; 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param string $lid 86 | * @return EventType 87 | */ 88 | public function setLid($lid) { 89 | $this->lid = $lid; 90 | return $this; 91 | } 92 | 93 | /** 94 | * @param string $name 95 | * @return EventType 96 | */ 97 | public function setName($name) { 98 | $this->name = $name; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @param string $description 104 | * @return EventType 105 | */ 106 | public function setDescription($description) { 107 | $this->description = $description; 108 | return $this; 109 | } 110 | 111 | /** 112 | * @param $name 113 | * @return IblockSection 114 | */ 115 | public function getChild($name) { 116 | return $this->findChild($name); 117 | } 118 | 119 | /** 120 | * @param $name 121 | * @param bool $data 122 | * @return IblockSection 123 | */ 124 | public function addChild($name, $data = false) { 125 | $child = new IblockSection($name, $data); 126 | $this->children[] = $child; 127 | return $child; 128 | } 129 | 130 | private function findChild($name) { 131 | foreach ($this->children as $child) { 132 | if ($child->name != $name) { 133 | continue; 134 | } 135 | return $child; 136 | } 137 | $child = $this->getChildFromDB($name); 138 | $this->children[] = $child; 139 | return $child; 140 | } 141 | /** 142 | * @return IblockSection[] 143 | */ 144 | public function getChildren() { 145 | return $this->children; 146 | } 147 | 148 | /** 149 | * @param $name 150 | * @return IblockSection 151 | * @throws BuilderException 152 | */ 153 | private function getChildFromDB($name) { 154 | if (!$this->id) { 155 | throw new BuilderException("Save section for continue"); 156 | } 157 | $item = \CIBlockSection::GetList(null, array( 158 | 'IBLOCK_ID' => $this->iblockId, 159 | 'SECTION_ID' => $this->id, 160 | '=NAME' => $name 161 | ))->Fetch(); 162 | if (empty($item)) { 163 | throw new BuilderException("Iblock section with name '{$name}' not found"); 164 | } 165 | return new IblockSection($name, $item); 166 | } 167 | } -------------------------------------------------------------------------------- /lib/builder/entity/iblocktype.php: -------------------------------------------------------------------------------- 1 | id = $type; 25 | $this->iblockTypeId = $data['ID']; 26 | $this->setSaveData($data); 27 | } 28 | 29 | public function getMap() { 30 | return array( 31 | 'id' => 'ID', 32 | 'sort' => 'SORT', 33 | 'sections' => 'SECTIONS', 34 | 'inRss' => 'IN_RSS', 35 | 'lang' => 'LANG', 36 | ); 37 | } 38 | 39 | /** 40 | * @param int $id 41 | * @return IblockType 42 | */ 43 | public function setId($id) { 44 | $this->id = $id; 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param int $sort 50 | * @return IblockType 51 | */ 52 | public function setSort($sort) { 53 | $this->sort = $sort; 54 | return $this; 55 | } 56 | 57 | /** 58 | * @return int 59 | */ 60 | public function getId() { 61 | return $this->id; 62 | } 63 | 64 | /** 65 | * @param string $sections 66 | * @return IblockType 67 | */ 68 | public function setSections($sections) { 69 | $this->sections = $sections; 70 | return $this; 71 | } 72 | 73 | /** 74 | * @param array $lang ['en' => ['NAME'=>'Catalog', 'SECTION_NAME'=>'Sections', 'ELEMENT_NAME'=>'Products']] 75 | * @return IblockType 76 | */ 77 | public function setLang($lang) { 78 | $this->lang = $lang; 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param bool $inRss 84 | * @return IblockType 85 | */ 86 | public function setInRss($inRss) { 87 | $this->inRss = $inRss ? 'Y' : 'N'; 88 | return $this; 89 | } 90 | } -------------------------------------------------------------------------------- /lib/builder/eventsbuilder.php: -------------------------------------------------------------------------------- 1 | eventType = null; 19 | $this->newMessages = array(); 20 | $this->exitsMessages = array(); 21 | } 22 | 23 | /** 24 | * @param $type 25 | * @param $lid 26 | * @return EventType 27 | * @throws BuilderException 28 | */ 29 | public function addEventType($type, $lid) { 30 | if ($this->eventType) { 31 | throw new BuilderException('EventType already set'); 32 | } 33 | $this->eventType = new EventType($type, $lid); 34 | return $this->eventType; 35 | } 36 | 37 | /** 38 | * @param $type 39 | * @param $lid 40 | * @return EventType 41 | * @throws BuilderException 42 | */ 43 | public function getEventType($type, $lid) { 44 | if ($this->eventType) { 45 | throw new BuilderException('EventType already set'); 46 | } 47 | $this->eventType = new EventType($type, $lid, $this->findEventType($type, $lid)); 48 | return $this->eventType; 49 | } 50 | 51 | /** 52 | * @param $from 53 | * @param $to 54 | * @param $siteId 55 | * @return EventMessage 56 | */ 57 | public function addEventMessage($from, $to, $siteId) { 58 | $message = new EventMessage($from, $to, $siteId); 59 | $this->newMessages[] = $message; 60 | return $message; 61 | } 62 | 63 | /** 64 | * @return Entity\EventMessage[] 65 | * @throws BuilderException 66 | */ 67 | public function getEventMessages() { 68 | foreach ($this->findMessages() as $data) { 69 | $this->exitsMessages[] = new EventMessage(false, false, false, $data); 70 | } 71 | return $this->exitsMessages; 72 | } 73 | 74 | /** 75 | * @return EventType 76 | */ 77 | public function getCurrentEventType() { 78 | return $this->eventType; 79 | } 80 | 81 | /** 82 | * @throws BuilderException 83 | */ 84 | public function commit() { 85 | global $DB; 86 | $DB->StartTransaction(); 87 | try { 88 | $this->commitEventType(); 89 | $this->commitNewEventMessages(); 90 | $this->commitExistsEventMessages(); 91 | } catch (\Exception $e) { 92 | $DB->Rollback(); 93 | throw new BuilderException($e->getMessage()); 94 | } 95 | $DB->Commit(); 96 | } 97 | 98 | /** 99 | * @param $type 100 | * @param $lid 101 | * @return array 102 | * @throws BuilderException 103 | */ 104 | private function findEventType($type, $lid) { 105 | $data = \CEventType::GetList(array( 106 | 'TYPE_ID' => $type, 107 | 'LID' => $lid 108 | ))->Fetch(); 109 | if (empty($data)) { 110 | throw new BuilderException("EventType '{$type}' not found for lid '{$lid}'"); 111 | } 112 | return $data; 113 | } 114 | 115 | /** 116 | * @throws BuilderException 117 | */ 118 | private function commitEventType() { 119 | global $APPLICATION; 120 | if (!$this->eventType) { 121 | throw new BuilderException("EventType doesn't set"); 122 | } 123 | $gw = new \CEventType(); 124 | if ($this->eventType->getId() > 0) { 125 | $gw->Update(['ID' => $this->eventType->getId()], $this->eventType->getSaveData()); 126 | } else { 127 | $res = $gw->Add($this->eventType->getSaveData()); 128 | if (!$res) { 129 | throw new BuilderException('EventType add failed with error: ' . $APPLICATION->GetException()->GetString()); 130 | } 131 | $this->eventType->setId($res); 132 | } 133 | } 134 | 135 | /** 136 | * @throws BuilderException 137 | */ 138 | private function commitNewEventMessages() { 139 | global $APPLICATION; 140 | if (!$this->getCurrentEventType()->getId()) { 141 | throw new BuilderException("EventType doesn't set"); 142 | } 143 | $gw = new \CEventMessage(); 144 | foreach ($this->newMessages as $message) { 145 | $id = $gw->Add(array_merge( 146 | $message->getSaveData(), 147 | array('EVENT_NAME' => $this->getCurrentEventType()->eventName) 148 | )); 149 | if (!$id) { 150 | throw new BuilderException("EventMessage add failed with error: " . $APPLICATION->GetException()->GetString()); 151 | } 152 | $message->setId($id); 153 | } 154 | } 155 | 156 | /** 157 | * @throws BuilderException 158 | */ 159 | private function commitExistsEventMessages() { 160 | global $APPLICATION; 161 | if (!$this->getCurrentEventType()->getId()) { 162 | throw new BuilderException("EventType doesn't set"); 163 | } 164 | $gw = new \CEventMessage(); 165 | foreach ($this->exitsMessages as $message) { 166 | if (!$message->isRemoved()) { 167 | continue; 168 | } 169 | if (!$gw->Delete($message->getId())) { 170 | throw new BuilderException("EventType wasn't deleted: ". $APPLICATION->GetException()->GetString()); 171 | } 172 | } 173 | foreach ($this->exitsMessages as $message) { 174 | if ($message->isRemoved()) { 175 | continue; 176 | } 177 | if (!$gw->Update($message->getId(), $message->getSaveData())) { 178 | throw new BuilderException("EventType wasn't updated: ". $APPLICATION->GetException()->GetString()); 179 | } 180 | } 181 | } 182 | 183 | /** 184 | * @return array 185 | * @throws BuilderException 186 | * @throws \Bitrix\Main\ArgumentException 187 | */ 188 | private function findMessages() { 189 | if (!$this->getCurrentEventType()->getId()) { 190 | throw new BuilderException("EventType doesn't set"); 191 | } 192 | $res = EventMessageTable::getList(array( 193 | 'filter' => array( 194 | 'EVENT_NAME' => $this->getCurrentEventType()->eventName 195 | ) 196 | )); 197 | return $res->fetchAll(); 198 | } 199 | 200 | 201 | } 202 | -------------------------------------------------------------------------------- /lib/builder/highloadblockbuilder.php: -------------------------------------------------------------------------------- 1 | highLoadBlock = null; 22 | $this->fields = array(); 23 | } 24 | 25 | /** 26 | * @param $name 27 | * @param $tableName 28 | * @return HighLoadBlock 29 | * @throws BuilderException 30 | */ 31 | public function addHLBlock($name, $tableName) { 32 | if ($this->highLoadBlock) { 33 | throw new BuilderException('reset builder data for continue'); 34 | } 35 | $this->highLoadBlock = new HighLoadBlock($name, $tableName); 36 | return $this->highLoadBlock; 37 | } 38 | 39 | /** 40 | * @param $tableName 41 | * @return HighLoadBlock 42 | * @throws BuilderException 43 | */ 44 | public function getHLBlock($tableName) { 45 | if ($this->highLoadBlock) { 46 | throw new BuilderException('reset builder data for continue'); 47 | } 48 | $block = $this->findTable($tableName); 49 | $this->highLoadBlock = new HighLoadBlock($block['NAME'], $tableName, $block['ID']); 50 | return $this->highLoadBlock; 51 | } 52 | 53 | /** 54 | * @param $code 55 | * @return UserField 56 | */ 57 | public function addField($code) { 58 | $field = new UserField($code); 59 | $this->fields[] = $field; 60 | return $field; 61 | } 62 | 63 | /** 64 | * @param $code 65 | * @return UserField 66 | * @throws BuilderException 67 | */ 68 | public function getField($code) { 69 | $data = $this->findField($code); 70 | $field = new UserField($code, $data); 71 | $this->fields[] = $field; 72 | return $field; 73 | } 74 | 75 | /** 76 | * @throws BuilderException 77 | */ 78 | public function commit() { 79 | global $DB; 80 | $DB->StartTransaction(); 81 | try { 82 | $this->commitHighLoadBlock(); 83 | $this->commitFields(); 84 | } catch (BuilderException $e) { 85 | $DB->Rollback(); 86 | throw new BuilderException($e->getMessage()); 87 | } 88 | $DB->Commit(); 89 | } 90 | 91 | /** 92 | * @param $tableName 93 | * @return array|false 94 | * @throws BuilderException 95 | * @throws \Bitrix\Main\ArgumentException 96 | */ 97 | public function findTable($tableName) { 98 | $hbRes = HighloadBlockTable::getList(array( 99 | 'filter' => array( 100 | 'TABLE_NAME' => $tableName 101 | ) 102 | )); 103 | if (!($table = $hbRes->fetch())){ 104 | throw new BuilderException('Cant find block by table name `'.$tableName.'` '); 105 | } 106 | return $table; 107 | } 108 | 109 | /** 110 | * @return HighLoadBlock 111 | */ 112 | public function getCurrentHighLoadBlock() { 113 | return $this->highLoadBlock; 114 | } 115 | 116 | /** 117 | * @throws BuilderException 118 | * @throws \Bitrix\Main\SystemException 119 | */ 120 | private function commitHighLoadBlock() { 121 | if (!$this->highLoadBlock->getId()) { 122 | $hbRes = HighloadBlockTable::add($this->highLoadBlock->getSaveData()); 123 | } else { 124 | $hbRes = HighloadBlockTable::update( 125 | $this->highLoadBlock->getId(), 126 | $this->highLoadBlock->getSaveData() 127 | ); 128 | } 129 | if (!$hbRes->isSuccess()) { 130 | throw new BuilderException($this->highLoadBlock->tableName . ' ' . implode(', ', $hbRes->getErrorMessages())); 131 | } 132 | $this->highLoadBlock->setId($hbRes->getId()); 133 | } 134 | 135 | /** 136 | * @throws BuilderException 137 | */ 138 | private function commitFields() { 139 | global $APPLICATION; 140 | if (!$this->getCurrentHighLoadBlock()->getId()) { 141 | throw new BuilderException('Set highLoadBlock before'); 142 | } 143 | $gw = new \CUserTypeEntity(); 144 | foreach ($this->fields as $field) { 145 | if ($field->getId() > 0) { 146 | $res = $gw->Update($field->getId(), $field->getSaveData()); 147 | } else { 148 | $res = $gw->Add(array_merge($field->getSaveData(), array( 149 | 'ENTITY_ID' => 'HLBLOCK_' . $this->getCurrentHighLoadBlock()->getId() 150 | ))); 151 | if ($res) { 152 | $field->setId($res); 153 | } 154 | } 155 | 156 | if (!$res) { 157 | throw new BuilderException($APPLICATION->GetException()->GetString()); 158 | } 159 | 160 | $this->commitEnum($field); 161 | } 162 | } 163 | 164 | /** 165 | * @param UserField $field 166 | * @throws BuilderException 167 | */ 168 | private function commitEnum($field) { 169 | global $APPLICATION; 170 | $obEnum = new \CUserFieldEnum; 171 | $values = array(); 172 | foreach ($field->getEnumVariants() as $key => $variant) { 173 | $key = 'n' . $key; 174 | if ($variant->getId() > 0) { 175 | $key = $variant->getId(); 176 | } 177 | $values[$key] = $variant->getSaveData(); 178 | } 179 | if (empty($values)) { 180 | return; 181 | } 182 | if (!$obEnum->SetEnumValues($field->getId(), $values)) { 183 | throw new BuilderException($APPLICATION->GetException()->GetString()); 184 | } 185 | } 186 | 187 | /** 188 | * @param $code 189 | * @return array 190 | * @throws BuilderException 191 | */ 192 | private function findField($code) { 193 | if (!$this->highLoadBlock) { 194 | throw new BuilderException('set higloadBlock for continue'); 195 | } 196 | $field = \CUserTypeEntity::GetList(null, array( 197 | 'FIELD_NAME' => $code, 198 | 'ENTITY_ID' => "HLBLOCK_" . $this->getCurrentHighLoadBlock()->getId(), 199 | ))->Fetch(); 200 | 201 | if (empty($field)) { 202 | throw new BuilderException('Field for update not found'); 203 | } 204 | return $field; 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /lib/callback.php: -------------------------------------------------------------------------------- 1 | _function = $f; 31 | if (isset($args) && is_array($args)) { 32 | call_user_func_array(array($this, 'bind'), $args); 33 | } 34 | $this->_number = self::$_count ++; 35 | } 36 | 37 | public function bind() { 38 | if ( ! empty($this->_args)) { 39 | throw new \Exception("Args setup before"); 40 | } 41 | $this->_args = func_get_args(); 42 | } 43 | 44 | /** 45 | * Параметры для вызова 46 | * 47 | * @param mixed $param1 48 | * @param mixed $param1 49 | * 50 | * @return mixed 51 | */ 52 | public function __invoke() { 53 | $args = array_merge($this->_args, func_get_args() ? : array()); 54 | return call_user_func_array($this->_function, $args); 55 | } 56 | 57 | public function setCallByToString($bool) { 58 | $this->_callByToString = (bool) $bool; 59 | } 60 | 61 | public function __toString() { 62 | if ($this->_callByToString) { 63 | return $this(); 64 | } 65 | return 'CallBackObject_' . $this->_number; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /lib/changedatacollector/collector.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lib/changedatacollector/collector.php -------------------------------------------------------------------------------- /lib/changedatacollector/collectorfix.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\ChangeDataCollector; 7 | 8 | 9 | use WS\Migrations\Processes\BaseProcess; 10 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 11 | 12 | class CollectorFix { 13 | private $_subject; 14 | private $_process; 15 | private $_data; 16 | private $_label; 17 | private $_name; 18 | private $_originalData; 19 | private $_owner; 20 | 21 | private $_isUses = false; 22 | 23 | private $_dbVersion; 24 | 25 | public function __construct($label) { 26 | $this->_label = $label; 27 | } 28 | 29 | /** 30 | * @return $this 31 | */ 32 | public function take() { 33 | $this->_isUses = true; 34 | return $this; 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function isUses() { 41 | return $this->_isUses; 42 | } 43 | 44 | 45 | /** 46 | * @return BaseProcess 47 | */ 48 | public function getProcess() { 49 | return $this->_process; 50 | } 51 | 52 | 53 | /** 54 | * @return BaseSubjectHandler 55 | */ 56 | public function getSubject() { 57 | return $this->_subject; 58 | } 59 | 60 | /** 61 | * @return mixed 62 | */ 63 | public function getUpdateData() { 64 | return $this->_data; 65 | } 66 | 67 | /** 68 | * @param mixed $subject 69 | * @return $this 70 | */ 71 | public function setSubject($subject) { 72 | $this->_subject = $subject; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param mixed $process 78 | * @return $this 79 | */ 80 | public function setProcess($process) { 81 | $this->_process = $process; 82 | return $this; 83 | } 84 | 85 | /** 86 | * @param mixed $data 87 | * @return $this 88 | */ 89 | public function setUpdateData($data) { 90 | $this->take(); 91 | $this->_data = $data; 92 | return $this; 93 | } 94 | 95 | public function setOriginalData($data) { 96 | $this->_originalData = $data; 97 | return $this; 98 | } 99 | 100 | public function getOriginalData() { 101 | return $this->_originalData; 102 | } 103 | 104 | public function getLabel() { 105 | return $this->_label; 106 | } 107 | 108 | public function setName($value) { 109 | $this->_name = $value; 110 | return $this; 111 | } 112 | 113 | public function getName() { 114 | return $this->_name; 115 | } 116 | 117 | /** 118 | * @param $value 119 | * @return $this 120 | */ 121 | public function setDbVersion($value) { 122 | $this->_dbVersion = $value; 123 | return $this; 124 | } 125 | 126 | /** 127 | * @return string 128 | */ 129 | public function getDbVersion() { 130 | return $this->_dbVersion ?: $this->_data['dbVersion']; 131 | } 132 | 133 | /** 134 | * @return string 135 | */ 136 | public function getOwner() { 137 | return $this->_owner; 138 | } 139 | 140 | /** 141 | * @param string $owner 142 | * @return $this 143 | */ 144 | public function setOwner($owner) { 145 | $this->_owner = $owner; 146 | return $this; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/console/command/applycommand.php: -------------------------------------------------------------------------------- 1 | force = in_array('-f', $params); 14 | } 15 | 16 | public function execute($callback = false) { 17 | if (!$this->force) { 18 | $this->console 19 | ->printLine("Are you sure? (yes|no):"); 20 | $answer = $this->console->readLine(); 21 | if ($answer != 'yes') { 22 | exit(); 23 | } 24 | } 25 | $diagnosticTester = $this->module 26 | ->useDiagnostic(); 27 | 28 | if (!$diagnosticTester->run()) { 29 | $this->console 30 | ->printLine("Diagnostic is not valid"); 31 | exit(); 32 | } 33 | $this->console 34 | ->printLine("Applying new fixes started....", Console::OUTPUT_PROGRESS); 35 | 36 | $time = microtime(true); 37 | 38 | $count = (int)$this->module 39 | ->applyNewFixes($callback); 40 | 41 | $interval = round(microtime(true) - $time, 2); 42 | 43 | $this->console 44 | ->printLine("Apply action finished! $count items, time $interval sec", Console::OUTPUT_PROGRESS); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /lib/console/command/basecommand.php: -------------------------------------------------------------------------------- 1 | console = $console; 16 | $this->initParams($params); 17 | $this->module = Module::getInstance(); 18 | } 19 | 20 | /** 21 | * @return string 22 | */ 23 | static public function className() { 24 | return get_called_class(); 25 | } 26 | protected function initParams($params) {} 27 | 28 | abstract public function execute($callback = false); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/console/command/createscenariocommand.php: -------------------------------------------------------------------------------- 1 | name = false; 22 | $this->description = false; 23 | if ($index = array_search('-n', $params)) { 24 | isset($params[$index + 1]) && !$this->isParam($params[$index + 1]) && $this->name = trim($params[$index + 1]); 25 | } 26 | if ($index = array_search('-d', $params)) { 27 | isset($params[$index + 1]) && !$this->isParam($params[$index + 1]) && $this->description = trim($params[$index + 1]); 28 | } 29 | } 30 | 31 | private function getName() { 32 | if ($this->name) { 33 | return $this->name; 34 | } 35 | $this->console 36 | ->printLine('Enter name:'); 37 | $name = $this->console 38 | ->readLine(); 39 | while (!strlen(trim($name))) { 40 | $this->console 41 | ->printLine("Name mustn't be empty. Enter name:"); 42 | $name = $this->console 43 | ->readLine(); 44 | } 45 | return $name; 46 | } 47 | 48 | private function getDescription() { 49 | if ($this->description) { 50 | return $this->description; 51 | } 52 | $this->console 53 | ->printLine('Enter description:'); 54 | return $this->console 55 | ->readLine(); 56 | } 57 | public function execute($callback = false) { 58 | $this->name = $this->getName(); 59 | $this->description = $this->getDescription(); 60 | 61 | $templateContent = file_get_contents(__DIR__.'/../../../data/scenarioTemplate.tpl'); 62 | 63 | $arReplace = array( 64 | '#class_name#' => $className = 'ws_m_' . time(). '_' . \CUtil::translit($this->name, LANGUAGE_ID), 65 | '#name#' => $this->name, 66 | '#description#' => $this->description, 67 | '#db_version#' => $this->module->getPlatformVersion()->getValue(), 68 | '#owner#' => $this->module->getPlatformVersion()->getOwner() 69 | ); 70 | $classContent = str_replace(array_keys($arReplace), array_values($arReplace), $templateContent); 71 | $fileName = $className . '.php'; 72 | try { 73 | $fileName = $this->module->putScriptClass($fileName, $classContent); 74 | } catch (\Exception $e) { 75 | $this->console->printLine("An error occurred saving file", Console::OUTPUT_ERROR); 76 | return; 77 | } 78 | $this->console->printLine($fileName, Console::OUTPUT_SUCCESS); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /lib/console/command/helpcommand.php: -------------------------------------------------------------------------------- 1 | console 9 | ->printLine("Action list:") 10 | ->printLine("* list - List of new migrations") 11 | ->printLine("* apply - Apply new migrations, \n -f Without approve") 12 | ->printLine("* rollback - Rollback last applied migrations") 13 | ->printLine("* createScenario - Create new migration scenario, \n -n 'Name' - scenario name \n -d 'Description' - scenario description") 14 | ->printLine("* last - Show last applied migrations list") 15 | ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/console/command/lastcommand.php: -------------------------------------------------------------------------------- 1 | getLastSetupLog(); 11 | if (!$lastSetupLog) { 12 | $this->console 13 | ->printLine("Nothing to show."); 14 | return; 15 | } 16 | $appliedFixes = array(); 17 | $errorFixes = array(); 18 | 19 | foreach ($lastSetupLog->getAppliedLogs() as $appliedLog) { 20 | !$appliedLog->success && $errorFixes[] = $appliedLog; 21 | $appliedLog->success && $appliedFixes[$appliedLog->description]++; 22 | } 23 | foreach ($appliedFixes as $fixName => $fixCount) { 24 | $this->console 25 | ->printLine($fixName . ($fixCount > 1 ? "[$fixCount]" : ""), Console::OUTPUT_SUCCESS); 26 | } 27 | /** @var \WS\Migrations\Entities\AppliedChangesLogModel $errorApply */ 28 | foreach ($errorFixes as $errorApply) { 29 | $errorData = \WS\Migrations\jsonToArray($errorApply->description) ?: $errorApply->description; 30 | if (is_scalar($errorData)) { 31 | $this->console 32 | ->printLine($errorData, Console::OUTPUT_ERROR); 33 | } 34 | if (is_array($errorData)) { 35 | $this->console 36 | ->printLine($errorData['message'], Console::OUTPUT_ERROR); 37 | } 38 | 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/console/command/listcommand.php: -------------------------------------------------------------------------------- 1 | registeredFixes = array(); 13 | $this->localization = \WS\Migrations\Module::getInstance()->getLocalization('admin')->fork('cli'); 14 | } 15 | 16 | public function execute($callback = false) { 17 | $has = false; 18 | foreach ($this->module->getNotAppliedFixes() as $notAppliedFix) { 19 | $this->registerFix($notAppliedFix->getName()); 20 | $has = true; 21 | } 22 | foreach ($this->module->getNotAppliedScenarios() as $notAppliedScenario) { 23 | $this->registerFix($notAppliedScenario::name()); 24 | $has = true; 25 | } 26 | !$has && $this->console->printLine("Nothing to apply"); 27 | $has && $this->printRegisteredFixes(); 28 | } 29 | 30 | private function registerFix($name) { 31 | if ($name == 'Reference fix') { 32 | $name = $this->localization->message('common.reference-fix'); 33 | } 34 | $this->registeredFixes[$name]++; 35 | } 36 | 37 | private function printRegisteredFixes() { 38 | foreach ($this->registeredFixes as $name => $count) { 39 | $count = $count > 1 ? '['.$count.']' : ''; 40 | $this->console->printLine($name.' '.$count); 41 | } 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /lib/console/command/rollbackcommand.php: -------------------------------------------------------------------------------- 1 | console 11 | ->printLine("Are you sure? (yes|no):"); 12 | 13 | $answer = $this->console 14 | ->readLine(); 15 | 16 | if ($answer != 'yes') { 17 | return; 18 | } 19 | $this->console 20 | ->printLine("Rollback action started...", Console::OUTPUT_PROGRESS); 21 | $start = microtime(true); 22 | $this->module 23 | ->rollbackLastChanges($callback); 24 | 25 | $interval = round(microtime(true) - $start, 2); 26 | 27 | $this->console 28 | ->printLine("Rollback action finished! Time $interval sec", Console::OUTPUT_PROGRESS); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/console/console.php: -------------------------------------------------------------------------------- 1 | ConvertCharsetArray($args, "UTF-8", LANG_CHARSET); 35 | $this->out = fopen('php://stdout', 'w'); 36 | array_shift($args); 37 | $this->params = $args; 38 | $this->action = isset($this->params[0]) ? $this->params[0] : '--help'; 39 | $this->successOutput = new Output('green'); 40 | $this->errorOutput = new Output('red'); 41 | $this->progressOutput = new Output('yellow'); 42 | $this->defaultOutput = new Output(); 43 | } 44 | 45 | /** 46 | * @param $str 47 | * @param $type 48 | * @return Console 49 | */ 50 | public function printLine($str, $type = false) { 51 | global $APPLICATION; 52 | $str = $APPLICATION->ConvertCharset($str, LANG_CHARSET, "UTF-8"); 53 | if ($type) { 54 | $str = $this->getOutput($type)->colorize($str); 55 | } 56 | fwrite($this->out, $str . "\n"); 57 | return $this; 58 | } 59 | 60 | public function readLine() { 61 | return trim(fgets(STDIN)); 62 | } 63 | 64 | /** 65 | * @return BaseCommand 66 | * @throws ConsoleException 67 | */ 68 | public function getCommand() { 69 | $commands = array( 70 | '--help' => HelpCommand::className(), 71 | 'list' => ListCommand::className(), 72 | 'apply' => ApplyCommand::className(), 73 | 'rollback' => RollbackCommand::className(), 74 | 'last' => LastCommand::className(), 75 | 'createScenario' => CreateScenarioCommand::className(), 76 | ); 77 | if (!$commands[$this->action]) { 78 | throw new ConsoleException("Action `{$this->action}` is not supported"); 79 | } 80 | return new $commands[$this->action]($this, $this->params); 81 | } 82 | 83 | /** 84 | * @param $type 85 | * @return Output 86 | */ 87 | private function getOutput($type) { 88 | switch ($type) { 89 | case 'success': 90 | return $this->successOutput; 91 | break; 92 | case 'error': 93 | return $this->errorOutput; 94 | break; 95 | case 'progress': 96 | return $this->progressOutput; 97 | break; 98 | default:; 99 | } 100 | return $this->defaultOutput; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /lib/console/consoleexception.php: -------------------------------------------------------------------------------- 1 | textColors(); 13 | $this->color = isset($colors[$color]) ? $colors[$color] : $colors['default']; 14 | } 15 | 16 | public function textColors () { 17 | return array( 18 | 'black' => 30, 19 | 'red' => 31, 20 | 'green' => 32, 21 | 'yellow' => 33, 22 | 'blue' => 34, 23 | 'magenta' => 35, 24 | 'cyan' => 36, 25 | 'white' => 37, 26 | 'default' => 0 27 | ); 28 | } 29 | 30 | public function colorize($text) { 31 | return chr(27) . "[{$this->color}m" . $text . chr(27) . "[0m"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lib/console/runtimecounter.php: -------------------------------------------------------------------------------- 1 | start = microtime(true); 11 | $this->migrationCount = 0; 12 | $this->migrationNumbe = 0; 13 | } 14 | } -------------------------------------------------------------------------------- /lib/console/runtimefixcounter.php: -------------------------------------------------------------------------------- 1 | time = 0; 18 | $this->activeFixName = ''; 19 | $this->fixNames = array(); 20 | $this->fixNumber = 0; 21 | $this->migrationCount = 0; 22 | } 23 | 24 | public function setFixNameByFixes($fixes) { 25 | /** @var CollectorFix $fix */ 26 | foreach ($fixes as $fix) { 27 | if ($this->activeFixName != $fix->getName()) { 28 | $this->fixNumber++; 29 | $this->activeFixName = $fix->getName(); 30 | } 31 | $this->fixNames[$this->activeFixName . $this->fixNumber]++; 32 | } 33 | } 34 | 35 | /** 36 | * @param AppliedChangesLogModel[] $list 37 | */ 38 | public function setFixNamesByLogs($list) { 39 | foreach ($list as $log) { 40 | if (!$log->success) { 41 | continue; 42 | } 43 | $processName = $log->description; 44 | if ($processName == Module::SPECIAL_PROCESS_SCENARIO) { 45 | $this->migrationCount++; 46 | continue; 47 | } 48 | if ($this->activeFixName != $log->description) { 49 | $this->migrationCount++; 50 | $this->fixNumber++; 51 | $this->activeFixName = $log->description; 52 | } 53 | $this->fixNames[$this->activeFixName . $this->fixNumber]++; 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /lib/diagnostic/diagnosticresult.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Diagnostic; 7 | 8 | /** 9 | * Class DiagnosticResult 10 | * 11 | * @package WS\Migrations\Diagnostic 12 | */ 13 | class DiagnosticResult { 14 | 15 | /** 16 | * @var bool 17 | */ 18 | private $success; 19 | 20 | /** 21 | * @var ErrorMessage[] 22 | */ 23 | private $messages; 24 | 25 | /** 26 | * @var string 27 | */ 28 | private $time; 29 | 30 | /** 31 | * @var bool 32 | */ 33 | private $isNull = false; 34 | 35 | /** 36 | * @param bool $success 37 | * @param ErrorMessage[] $messages 38 | * @param string $time 39 | * @throws \Exception 40 | */ 41 | public function __construct($success, array $messages, $time = '') { 42 | $this->success = $success; 43 | $this->messages = $messages; 44 | foreach ($this->messages as $message) { 45 | if (! $message instanceof ErrorMessage) { 46 | throw new \Exception('Message must be as object'); 47 | } 48 | } 49 | $this->time = $time; 50 | } 51 | 52 | /** 53 | * @return DiagnosticResult 54 | */ 55 | public static function createNull() { 56 | $object = new static(true, array(), '-'); 57 | $object->isNull = true; 58 | return $object; 59 | } 60 | 61 | /** 62 | * @return bool 63 | */ 64 | public function isNull() { 65 | return $this->isNull; 66 | } 67 | 68 | /** 69 | * @return bool 70 | */ 71 | public function isSuccess() { 72 | return $this->success; 73 | } 74 | 75 | /** 76 | * @return ErrorMessage[] 77 | */ 78 | public function getMessages() { 79 | return $this->messages; 80 | } 81 | 82 | public function getMessagesText() { 83 | return array_map(function (ErrorMessage $message) { 84 | return $message->getText(); 85 | }, $this->getMessages()); 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getTime() { 92 | return $this->time; 93 | } 94 | } -------------------------------------------------------------------------------- /lib/diagnostic/diagnostictester.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Diagnostic; 7 | 8 | use WS\Migrations\Module; 9 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 10 | 11 | class DiagnosticTester { 12 | 13 | const LOG_TYPE = 'WS_MIGRATIONS_DIAGNOSTIC'; 14 | 15 | /** 16 | * @var BaseSubjectHandler[] 17 | */ 18 | private $handlers; 19 | /** 20 | * @var Module 21 | */ 22 | private $module; 23 | 24 | /** 25 | * @var bool 26 | */ 27 | private $lastRun; 28 | 29 | /** 30 | * @param BaseSubjectHandler[] $handlers 31 | * @param Module $module 32 | */ 33 | public function __construct(array $handlers, Module $module) { 34 | $this->handlers = $handlers; 35 | $this->module = $module; 36 | } 37 | 38 | /** 39 | * @return bool 40 | */ 41 | public function run() { 42 | $success = true; 43 | $messages = array(); 44 | if (!$this->module->getPlatformVersion()->isValid()) { 45 | $messages[] = new ErrorMessage('module', '', '', 'Module has not valid version'); 46 | } 47 | foreach ($this->handlers as $handler) { 48 | $handlerResult = $handler->diagnostic(); 49 | if (!$handlerResult->isSuccess()) { 50 | $success = false; 51 | $messages = array_merge($messages, $handlerResult->getMessages()); 52 | } 53 | } 54 | 55 | $this->lastRun = $success; 56 | $jsonData = json_encode(array( 57 | 'success' => $success, 58 | 'messages' => array_map(function (ErrorMessage $message) { 59 | return $message->toArray(); 60 | }, $messages) 61 | )); 62 | \CEventLog::Log('INFO', self::LOG_TYPE, 'ws.migrations', null, $jsonData); 63 | return $success; 64 | } 65 | 66 | /** 67 | * @return bool 68 | */ 69 | public function hasRun() { 70 | return $this->lastRun !== null; 71 | } 72 | 73 | /** 74 | * @return bool 75 | * @throws \Exception 76 | */ 77 | public function isSuccessRunResult() { 78 | if (!$this->hasRun()) { 79 | throw new \Exception("Run is not launched"); 80 | } 81 | return $this->lastRun; 82 | } 83 | 84 | /** 85 | * @return DiagnosticResult 86 | */ 87 | public function getLastResult() { 88 | $arLog = \CEventLog::GetList(array('ID' => 'DESC'), array( 89 | 'AUDIT_TYPE_ID' => self::LOG_TYPE 90 | ), 91 | array( 92 | 'nPageSize' => 1 93 | ) 94 | )->Fetch(); 95 | 96 | if (!$arLog) { 97 | return DiagnosticResult::createNull(); 98 | } 99 | $arLogData = $arLog ? json_decode($arLog['DESCRIPTION'], true) : array(); 100 | $res = new DiagnosticResult( 101 | $arLogData['success'] ?: false, 102 | array_map( 103 | function ($messageData) { 104 | return ErrorMessage::unpack($messageData); 105 | }, 106 | $arLogData['messages'] ?: array() 107 | ), 108 | $arLog['TIMESTAMP_X'] 109 | ); 110 | return $res; 111 | } 112 | } -------------------------------------------------------------------------------- /lib/diagnostic/errormessage.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Diagnostic; 7 | 8 | /** 9 | * Class ErrorMessage is container for errors handle 10 | * 11 | * @package WS\Migrations\Diagnostic 12 | */ 13 | class ErrorMessage { 14 | 15 | const TYPE_ITEM_HAS_NOT_REFERENCE = 'item-has-not-reference'; 16 | const TYPE_REFERENCE_WITHOUT_ITEM = 'reference-without-item'; 17 | 18 | private $group; 19 | private $item; 20 | private $type; 21 | private $text; 22 | 23 | /** 24 | * @param string $group 25 | * @param string $item 26 | * @param string $type 27 | * @param string $text 28 | */ 29 | public function __construct($group, $item, $type, $text) { 30 | $this->group = $group; 31 | $this->item = $item; 32 | $this->type = $type; 33 | $this->text = $text; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getGroup() { 40 | return $this->group; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getItem() { 47 | return $this->item; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getType() { 54 | return $this->type; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getText() { 61 | return $this->text; 62 | } 63 | 64 | /** 65 | * @param $data 66 | * @return ErrorMessage 67 | */ 68 | static public function unpack($data) { 69 | return new static( 70 | $data['group'], 71 | $data['item'], 72 | $data['type'], 73 | $data['text'] 74 | ); 75 | } 76 | 77 | /** 78 | * @return array 79 | */ 80 | public function toArray() { 81 | return array( 82 | 'group' => $this->group, 83 | 'item' => $this->item, 84 | 'type' => $this->type, 85 | 'text' => $this->text 86 | ); 87 | } 88 | } -------------------------------------------------------------------------------- /lib/entities/appliedchangeslog.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | 9 | use Bitrix\Main\Entity\DataManager; 10 | use Bitrix\Main; 11 | 12 | class AppliedChangesLogTable extends DataManager { 13 | public static function className() { 14 | return get_called_class(); 15 | } 16 | 17 | public static function getFilePath() { 18 | // fuck ))) 19 | return __FILE__; 20 | } 21 | 22 | public static function getTableName() { 23 | return 'ws_migrations_apply_changes_log'; 24 | } 25 | 26 | public static function getMap() { 27 | return array( 28 | 'ID' => array( 29 | 'data_type' => 'integer', 30 | 'primary' => true, 31 | 'autocomplete' => true 32 | ), 33 | 'SETUP_LOG_ID' => array( 34 | 'data_type' => 'integer' 35 | ), 36 | 'GROUP_LABEL' => array( 37 | 'data_type' => 'string', 38 | 'required' => true, 39 | ), 40 | 'DATE' => array( 41 | 'data_type' => 'datetime', 42 | 'required' => true, 43 | ), 44 | 'PROCESS' => array( 45 | 'data_type' => 'string', 46 | 'required' => true, 47 | ), 48 | 'SUBJECT' => array( 49 | 'data_type' => 'string', 50 | 'required' => true, 51 | ), 52 | 'UPDATE_DATA' => array( 53 | 'data_type' => 'string', 54 | 'required' => true, 55 | ), 56 | 'ORIGINAL_DATA' => array( 57 | 'data_type' => 'string', 58 | 'required' => true, 59 | ), 60 | 'SUCCESS' => array( 61 | 'data_type' => 'boolean', 62 | 'values' => array(false,true) 63 | ), 64 | 'DESCRIPTION' => array( 65 | 'data_type' => 'string', 66 | 'required' => true, 67 | ), 68 | 'SOURCE' => array( 69 | 'data_type' => 'string' 70 | ) 71 | ); 72 | } 73 | } -------------------------------------------------------------------------------- /lib/entities/appliedchangeslogmodel.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | use Bitrix\Main\Type\DateTime; 9 | use WS\Migrations\factories\DateTimeFactory; 10 | 11 | class AppliedChangesLogModel extends BaseEntity { 12 | public 13 | $id, $groupLabel, $date, $success, 14 | $processName, $subjectName, $source, $updateData, 15 | $originalData, $description, $setupLogId; 16 | 17 | private $_setupLog; 18 | 19 | public function __construct() { 20 | $this->date = DateTimeFactory::createBase(); 21 | } 22 | 23 | static protected function modifyFromDb($data) { 24 | $result = array(); 25 | foreach ($data as $name => $value) { 26 | if ($name == 'date') { 27 | if ($value instanceof DateTime) { 28 | $timestamp = $value->getTimestamp(); 29 | $value = DateTimeFactory::createBase(); 30 | $value->setTimestamp($timestamp); 31 | } else { 32 | $value = DateTimeFactory::createBase($value); 33 | } 34 | } 35 | if (in_array($name, array('originalData', 'updateData'))) { 36 | $value = \WS\Migrations\jsonToArray($value); 37 | } 38 | $result[$name] = $value; 39 | } 40 | return $result; 41 | } 42 | 43 | static protected function modifyToDb($data) { 44 | $result = array(); 45 | foreach ($data as $name => $value) { 46 | if ($name == 'date' && $value instanceof \DateTime) { 47 | $value = DateTimeFactory::createBitrix($value); 48 | } 49 | if (in_array($name, array('originalData', 'updateData'))) { 50 | $value = \WS\Migrations\arrayToJson($value); 51 | } 52 | $result[$name] = $value; 53 | } 54 | return $result; 55 | } 56 | 57 | static protected function map() { 58 | return array( 59 | 'id' => 'ID', 60 | 'setupLogId' => 'SETUP_LOG_ID', 61 | 'groupLabel' => 'GROUP_LABEL', 62 | 'date' => 'DATE', 63 | 'processName' => 'PROCESS', 64 | 'subjectName' => 'SUBJECT', 65 | 'source' => 'SOURCE', 66 | 'updateData' => 'UPDATE_DATA', 67 | 'originalData' => 'ORIGINAL_DATA', 68 | 'success' => 'SUCCESS', 69 | 'description' => 'DESCRIPTION' 70 | ); 71 | } 72 | 73 | /** 74 | * @return SetupLogModel 75 | */ 76 | public function getSetupLog() { 77 | if (!$this->_setupLog) { 78 | $this->_setupLog = SetupLogModel::findOne(array( 79 | 'filter' => array('=id' => $this->setupLogId) 80 | ) 81 | ); 82 | } 83 | return $this->_setupLog; 84 | } 85 | 86 | public function setSetupLog(SetupLogModel $model = null) { 87 | $this->_setupLog = $model; 88 | $model->id && $this->setupLogId = $model->id; 89 | return $this; 90 | } 91 | 92 | static protected function gatewayClass() { 93 | return AppliedChangesLogTable::className(); 94 | } 95 | } -------------------------------------------------------------------------------- /lib/entities/baseentity.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | 9 | abstract class BaseEntity { 10 | public $id; 11 | 12 | private $_isNew = true; 13 | 14 | private $_errors = array(); 15 | 16 | static private $_oneRequestsCache = array(); 17 | /** 18 | * @param $props 19 | * @return $this 20 | */ 21 | static public function create($props) { 22 | /** @var $model BaseEntity */ 23 | $model = new static; 24 | foreach ($props as $name => $value) { 25 | $model->{$name} = $value; 26 | } 27 | $model->_isNew = false; 28 | return $model; 29 | } 30 | 31 | /** 32 | * @param $fields 33 | * @return $this 34 | */ 35 | static private function _createByRow($fields) { 36 | $props = array(); 37 | $fieldsToProps = array_flip(static::map()); 38 | foreach ($fields as $name => $value) { 39 | $name = $fieldsToProps[$name]; 40 | $props[$name] = $value; 41 | } 42 | $props = static::modifyFromDb($props); 43 | return self::create($props); 44 | } 45 | 46 | private function _getRawFields() { 47 | $result = array(); 48 | $data = array(); 49 | foreach (static::map() as $property => $field) { 50 | $data[$property] = $this->{$property}; 51 | } 52 | $data = static::modifyToDb($data); 53 | foreach (static::map() as $property => $field) { 54 | $result[$field] = $data[$property]; 55 | } 56 | 57 | return $result; 58 | } 59 | 60 | /** 61 | * @param array $params 62 | * @return AppliedChangesLogModel[] 63 | */ 64 | static public function find($params = array()) { 65 | $modelToDb = static::map(); 66 | $fReplaceList = function ($list) use ($modelToDb) { 67 | return array_map(function ($item) use ($modelToDb) { 68 | return $modelToDb[$item]; 69 | }, $list); 70 | }; 71 | 72 | if ($params['select']) { 73 | $params['select'] = $fReplaceList($params['select']); 74 | } 75 | if ($params['group']) { 76 | $pGroup = array(); 77 | foreach ($params['group'] as $field => $value) { 78 | $pGroup[$modelToDb[$field]] = $value; 79 | } 80 | $params['group'] = $pGroup; 81 | } 82 | if ($params['order']) { 83 | $pOrder = array(); 84 | foreach ($params['order'] as $field => $value) { 85 | $pOrder[$modelToDb[$field]] = $value; 86 | } 87 | $params['order'] = $pOrder; 88 | } 89 | 90 | if ($params['filter']) { 91 | $pFilter = array(); 92 | foreach ($params['filter'] as $field => $value) { 93 | $field = preg_replace_callback("/\w+/", function ($matches) use ($modelToDb) { 94 | return $modelToDb[$matches[0]]; 95 | }, $field); 96 | $pFilter[$field] = $value; 97 | } 98 | $params['filter'] = $pFilter; 99 | } 100 | $dbResult = static::callGatewayMethod('getList', $params); 101 | $rows = $dbResult->fetchAll(); 102 | $items = array(); 103 | foreach ($rows as $row) { 104 | $items[] = self::_createByRow($row); 105 | } 106 | return $items; 107 | } 108 | 109 | /** 110 | * @param array $params 111 | * @return $this 112 | */ 113 | static public function findOne($params = array()) { 114 | $cacheKey = md5(get_called_class().serialize($params)); 115 | if (!self::$_oneRequestsCache[$cacheKey]) { 116 | $params['limit'] = 1; 117 | $items = self::find($params); 118 | self::$_oneRequestsCache[$cacheKey] = $items[0]; 119 | } 120 | return self::$_oneRequestsCache[$cacheKey]; 121 | } 122 | 123 | /** 124 | * @param $name 125 | * @return mixed 126 | * @internal param $p1 127 | * @internal param $p2 128 | * @internal param $p3 129 | * 130 | */ 131 | static public function callGatewayMethod($name) { 132 | $params = func_get_args(); 133 | $name = array_shift($params); 134 | return call_user_func_array(array(static::gatewayClass(), $name), $params); 135 | } 136 | 137 | public function delete() { 138 | $res = static::callGatewayMethod('delete', $this->id); 139 | return !(bool)$res->getErrors(); 140 | } 141 | 142 | public function insert() { 143 | $res = static::callGatewayMethod('add', $this->_getRawFields()); 144 | $this->id = $res->getId(); 145 | $this->_errors = $res->getErrors() ?: array(); 146 | $this->_isNew = false; 147 | return !(bool)$res->getErrors(); 148 | } 149 | 150 | public function update() { 151 | $res = static::callGatewayMethod('update', $this->id, $this->_getRawFields()); 152 | $this->_errors = $res->getErrors() ?: array(); 153 | return !(bool)$res->getErrors(); 154 | } 155 | 156 | public function save() { 157 | return $this->_isNew ? $this->insert() : $this->update(); 158 | } 159 | 160 | public function getErrors() { 161 | return $this->_errors; 162 | } 163 | 164 | abstract static protected function map(); 165 | 166 | abstract static protected function gatewayClass(); 167 | 168 | static protected function modifyFromDb($data) { 169 | return $data; 170 | } 171 | 172 | static protected function modifyToDb($data) { 173 | return $data; 174 | } 175 | } -------------------------------------------------------------------------------- /lib/entities/dbversionreferences.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | 9 | use Bitrix\Main\Entity\DataManager; 10 | 11 | class DbVersionReferencesTable extends DataManager { 12 | 13 | public static function getTableName() { 14 | return 'ws_migrations_db_version_references'; 15 | } 16 | 17 | public static function className() { 18 | return get_called_class(); 19 | } 20 | 21 | public static function getFilePath() { 22 | // fuck ))) 23 | return __FILE__; 24 | } 25 | 26 | public static function getMap() { 27 | return array( 28 | 'ID' => array( 29 | 'data_type' => 'integer', 30 | 'primary' => true, 31 | 'autocomplete' => true 32 | ), 33 | 'REFERENCE' => array( 34 | 'data_type' => 'string', 35 | 'required' => true 36 | ), 37 | 'DB_VERSION' => array( 38 | 'data_type' => 'string', 39 | 'required' => true 40 | ), 41 | 'GROUP' => array( 42 | 'data_type' => 'string', 43 | 'required' => true 44 | ), 45 | 'ITEM_ID' => array( 46 | 'data_type' => 'string', 47 | 'required' => true 48 | ) 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/entities/setuplog.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | 9 | use Bitrix\Main\Entity\DataManager; 10 | use Bitrix\Main; 11 | 12 | class SetupLogTable extends DataManager { 13 | 14 | public static function className() { 15 | return get_called_class(); 16 | } 17 | 18 | public static function getTableName() { 19 | return 'ws_migrations_setups_log'; 20 | } 21 | 22 | /** 23 | * fuck )) 24 | * @return string|void 25 | */ 26 | public static function getFilePath() { 27 | return __FILE__; 28 | } 29 | 30 | public static function getMap() { 31 | return array( 32 | 'ID' => array( 33 | 'data_type' => 'integer', 34 | 'primary' => true, 35 | 'autocomplete' => true 36 | ), 37 | 'DATE' => array( 38 | 'data_type' => 'datetime', 39 | 'required' => true, 40 | ), 41 | 'USER_ID' => array( 42 | 'data_type' => 'integer' 43 | ) 44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /lib/entities/setuplogmodel.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Entities; 7 | 8 | 9 | use Bitrix\Main\Type\DateTime; 10 | use Bitrix\Main\UserTable; 11 | use WS\Migrations\factories\DateTimeFactory; 12 | 13 | class SetupLogModel extends BaseEntity { 14 | public 15 | $id, $userId; 16 | /** 17 | * @var \DateTime 18 | */ 19 | public $date; 20 | 21 | private $_userData = false; 22 | 23 | public function __construct() { 24 | $this->date = DateTimeFactory::createBase(); 25 | } 26 | 27 | static protected function map() { 28 | return array( 29 | 'id' => 'ID', 30 | 'date' => 'DATE', 31 | 'userId' => 'USER_ID' 32 | ); 33 | } 34 | 35 | static protected function gatewayClass() { 36 | return SetupLogTable::className(); 37 | } 38 | 39 | static protected function modifyFromDb($data) { 40 | if ($data['date'] instanceof DateTime) { 41 | $timestamp = $data['date']->getTimestamp(); 42 | $data['date'] = DateTimeFactory::createBase(); 43 | $data['date']->setTimestamp($timestamp); 44 | } else { 45 | $data['date']= DateTimeFactory::createBase($data['date']); 46 | } 47 | return $data; 48 | } 49 | 50 | static protected function modifyToDb($data) { 51 | $data['date'] && $data['date'] instanceof \DateTime && $data['date'] = DateTimeFactory::createBitrix($data['date']); 52 | return $data; 53 | } 54 | 55 | /** 56 | * @return AppliedChangesLogModel[] 57 | */ 58 | public function getAppliedLogs() { 59 | return AppliedChangesLogModel::find(array( 60 | 'filter' => array( 61 | '=setupLogId' => $this->id 62 | ) 63 | )); 64 | } 65 | 66 | /** 67 | * @return array 68 | */ 69 | private function getUserData() { 70 | if ($this->_userData === false) { 71 | $this->_userData = UserTable::getById($this->userId)->fetch(); 72 | } 73 | return $this->_userData; 74 | } 75 | 76 | public function shortUserInfo() { 77 | $res = 'cli'; 78 | if ($this->userId) { 79 | $data = $this->getUserData(); 80 | $res = $data['NAME'].' '.$data['LAST_NAME']; 81 | } 82 | return $res; 83 | } 84 | } -------------------------------------------------------------------------------- /lib/factories/datetimefactory.php: -------------------------------------------------------------------------------- 1 | format($format), $format, self::timeZone()); 29 | return $object; 30 | } 31 | 32 | /** 33 | * @return \DateTimeZone 34 | */ 35 | public static function timeZone() { 36 | try { 37 | $obj = new \DateTime(); 38 | return $obj->getTimezone(); 39 | } catch (\Exception $e) { 40 | $timezoneIdentifier = 'Europe/Moscow'; 41 | date_default_timezone_set($timezoneIdentifier); 42 | return new \DateTimeZone($timezoneIdentifier); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /lib/localization.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class Localization { 9 | 10 | /** 11 | * @var Options 12 | */ 13 | private $data; 14 | 15 | public function __construct($data) { 16 | $this->data = new Options($data); 17 | } 18 | 19 | /** 20 | * @return Options 21 | */ 22 | private function getData() { 23 | return $this->data; 24 | } 25 | 26 | /** 27 | * @param string $path @see Options 28 | * @param array $replace 29 | * @return mixed 30 | */ 31 | public function message($path, $replace = null) { 32 | $m = $this->getData()->get($path, ''); 33 | $result = $m ?: $path; 34 | if (is_array($replace)) { 35 | $result = str_replace(array_keys($replace), array_values($replace), $m); 36 | } 37 | return $result; 38 | } 39 | 40 | /** 41 | * @param string $path @see Options 42 | * @return Localization 43 | */ 44 | public function fork($path) { 45 | return new static($this->getData()->get($path)); 46 | } 47 | 48 | public function getDataByPath($path) { 49 | return $this->getData()->get($path, ''); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/moduleoptions.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ModuleOptions { 14 | private $_moduleName = 'ws.migrations'; 15 | 16 | private $_cache = array(); 17 | 18 | private function __construct() { 19 | } 20 | 21 | /** 22 | * @staticvar self $self 23 | * @return Options 24 | */ 25 | static public function getInstance() { 26 | static $self = null; 27 | if (!$self) { 28 | $self = new self; 29 | } 30 | return $self; 31 | } 32 | 33 | private function _setToDb($name, $value) { 34 | \COption::SetOptionString($this->_moduleName, $name, serialize($value)); 35 | } 36 | 37 | private function _getFromDb($name) { 38 | $value = \COption::GetOptionString($this->_moduleName, $name); 39 | return unserialize($value); 40 | } 41 | 42 | public function __set($name, $value) { 43 | $this->_setToCache($name, $value); 44 | $this->_setToDb($name, $value); 45 | return $value; 46 | } 47 | 48 | public function __get($name) { 49 | $value = $this->_getFormCache($name); 50 | if (is_null($value)) { 51 | $value = $this->_getFromDb($name); 52 | $this->_setToCache($name, $value); 53 | } 54 | return $value; 55 | } 56 | 57 | /** 58 | * @param $class 59 | */ 60 | public function disableSubjectHandler($class) { 61 | $this->enabledSubjectHandlers = array_diff($this->enabledSubjectHandlers ?: array(), array($class)); 62 | } 63 | 64 | /** 65 | * @param $class 66 | */ 67 | public function enableSubjectHandler($class) { 68 | $this->enabledSubjectHandlers = array_unique(array_merge($this->enabledSubjectHandlers ?: array(), array($class))); 69 | } 70 | 71 | /** 72 | * @param $class 73 | * @return bool 74 | */ 75 | public function isEnableSubjectHandler($class) { 76 | return in_array($class, $this->enabledSubjectHandlers); 77 | } 78 | 79 | /** 80 | * @return array 81 | */ 82 | public function getOtherVersions() { 83 | return $this->otherVersions; 84 | } 85 | 86 | /** 87 | * @param $name 88 | * @return mixed 89 | */ 90 | private function _getFormCache($name) { 91 | return $this->_cache[$name]; 92 | } 93 | 94 | /** 95 | * @param $name 96 | * @param $value 97 | */ 98 | private function _setToCache($name, $value) { 99 | $this->_cache[$name] = $value; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/options.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class Options implements \Serializable, \ArrayAccess { 9 | 10 | private $_data = array(); 11 | 12 | public function __construct(array $data = null) { 13 | $data && ($this->_data = $data); 14 | } 15 | 16 | /** 17 | * @param string $path 18 | * @param mixed $default 19 | * @throws \Exception 20 | * @return mixed 21 | */ 22 | public function get($path, $default = null) { 23 | 24 | $usesAliases = array(); 25 | $rPath = preg_replace_callback('/\[.*?\]/', function ($matches) use (& $usesAliases){ 26 | $key = trim($matches[0], '[]'); 27 | $alias = str_replace('.', '_', $key); 28 | $usesAliases[$alias] = $key; 29 | return '.'.$alias; 30 | }, $path); 31 | 32 | $arPath = explode('.', $rPath); 33 | $data = $this->_data; 34 | while (($pathItem = array_shift($arPath)) !== null) { 35 | if ($usesAliases[$pathItem]) { 36 | $pathItem = $usesAliases[$pathItem]; 37 | unset($usesAliases[$pathItem]); 38 | } 39 | 40 | if ($data instanceof self) { 41 | $data = $data->toArray(); 42 | } 43 | if ( ! isset($data[$pathItem])) { 44 | if ( ! is_null($default)) { 45 | return $default; 46 | } 47 | throw new \Exception("Value by path `$path` not exist"); 48 | } 49 | $data = $data[$pathItem]; 50 | } 51 | return $data; 52 | } 53 | 54 | /** 55 | * @param string $path 56 | * @param null|mixed $default 57 | * @throws \Exception 58 | * @return $this 59 | */ 60 | public function getAsObject($path, $default = null) { 61 | $res = $this->get($path, $default); 62 | if (!is_array($res)) { 63 | throw new \Exception("Return value as object not available"); 64 | } 65 | return new static($this->get($path)); 66 | } 67 | 68 | /** 69 | * @param \ArrayAccess|array $mergedOptions 70 | * @return Options 71 | */ 72 | public function merge($mergedOptions) { 73 | if (is_object($mergedOptions) && $mergedOptions instanceof Options) { 74 | $mergedOptions = $mergedOptions->toArray(); 75 | } 76 | foreach ($mergedOptions as $path => $value) { 77 | $this->set($path, $value); 78 | } 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param string $path 84 | * @param mixed $value 85 | * @throws \Exception 86 | * @return Options 87 | */ 88 | public function set($path, $value) { 89 | $arPath = explode('.', $path); 90 | $data = & $this->_data; 91 | while (($key = array_shift($arPath)) !== null) { 92 | if (empty($arPath)) { 93 | $key ? $data[$key] = $value : $data[] = $value; 94 | } else { 95 | if ( ! $key) { 96 | throw new \Exception('Need last iterated by path. Available: ' . $path); 97 | } 98 | if ( ! isset($data[$key])) { 99 | $data[$key] = array(); 100 | } 101 | $data = & $data[$key]; 102 | } 103 | } 104 | return $this; 105 | } 106 | 107 | public function __invoke() { 108 | $args = func_get_args(); 109 | switch (count($args)) { 110 | case 1: 111 | return $this->get($args[0]); 112 | break; 113 | case 2: 114 | return $this->set($args[0], $args[1]); 115 | break; 116 | } 117 | } 118 | 119 | public function toArray() { 120 | return $this->_data; 121 | } 122 | 123 | public function serialize() { 124 | return serialize($this->_data); 125 | } 126 | 127 | public function unserialize($serialized) { 128 | $this->_data = unserialize($serialized); 129 | } 130 | 131 | public function offsetExists($offset) { 132 | try { 133 | $this->get($offset); 134 | return true; 135 | } catch (\Exception $e) { 136 | return false; 137 | } 138 | } 139 | 140 | public function offsetGet($offset) { 141 | return $this->get($offset); 142 | } 143 | 144 | public function offsetSet($offset, $value) { 145 | $this->set($offset, $value); 146 | return $value; 147 | } 148 | 149 | public function offsetUnset($offset) { 150 | $this->set($offset, null); 151 | } 152 | 153 | public function toJson() { 154 | return json_encode($this->toArray()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/platformversion.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations; 7 | 8 | class PlatformVersion { 9 | 10 | /** 11 | * @var string 12 | */ 13 | private $version; 14 | 15 | /** 16 | * @var string 17 | */ 18 | private $checkSum; 19 | 20 | /** 21 | * @var string 22 | */ 23 | private $owner; 24 | 25 | /** 26 | * @var array ["asfdsgs" => "Vasiliy Dubinin"] 27 | */ 28 | private $mapOtherVersions; 29 | 30 | public function __construct($mapOtherVersions) { 31 | $this->mapOtherVersions = $mapOtherVersions ?: array(); 32 | $filePath = $this->filePath(); 33 | file_exists($filePath) ? $this->initFromFile() : $this->generate(); 34 | } 35 | 36 | private function initFromFile() { 37 | $raw = explode(':#:', file_get_contents($this->filePath())); 38 | $this->version = $raw[0]; 39 | $this->checkSum = $raw[1]; 40 | $this->owner = $raw[2]; 41 | } 42 | 43 | private function generate() { 44 | $this->version = md5(time()); 45 | $this->checkSum = md5($this->version.__FILE__); 46 | $this->save(); 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function getValue() { 53 | return $this->version; 54 | } 55 | 56 | /** 57 | * @return bool 58 | */ 59 | public function isValid() { 60 | return $this->checkSum == md5($this->version.__FILE__); 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getOwner() { 67 | return $this->owner; 68 | } 69 | 70 | /** 71 | * @return array 72 | */ 73 | public function getMapVersions() { 74 | return array_merge( 75 | array( 76 | $this->getValue() => $this->getOwner() 77 | ), 78 | $this->mapOtherVersions 79 | ); 80 | } 81 | 82 | public function setOwner($owner) { 83 | $this->owner = $owner; 84 | $this->save(); 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | private function filePath() { 91 | $docRoot = rtrim($_SERVER['DOCUMENT_ROOT'], '/').'/'; 92 | return $docRoot . \COption::GetOptionString("main", "upload_dir", "upload")."/ws.migrations/version.dat"; 93 | } 94 | 95 | /** 96 | * @throws \Exception 97 | */ 98 | private function save() { 99 | $raw = $this->version.':#:'.md5($this->version.__FILE__).':#:'.$this->owner; 100 | $r = fopen($this->filePath(), 'w'); 101 | $writeRes = fwrite($r, $raw); 102 | if ($writeRes === false) { 103 | throw new \Exception("File with migration version data isn`t available to record, path ".$this->filePath()); 104 | } 105 | } 106 | 107 | public function refresh() { 108 | $this->generate(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/processes/addprocess.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Processes; 7 | 8 | use WS\Migrations\ChangeDataCollector\CollectorFix; 9 | use WS\Migrations\Entities\AppliedChangesLogModel; 10 | use WS\Migrations\Module; 11 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 12 | 13 | class AddProcess extends BaseProcess { 14 | 15 | public function update(BaseSubjectHandler $subjectHandler, CollectorFix $fix, AppliedChangesLogModel $log) { 16 | $data = $fix->getUpdateData(); 17 | $result = $subjectHandler->applySnapshot($data, $fix->getDbVersion()); 18 | 19 | $data = $subjectHandler->getSnapshot($result->getId()); 20 | $log->description = $fix->getName(); 21 | $log->originalData = array(); 22 | $log->updateData = $data; 23 | return $result; 24 | } 25 | 26 | public function rollback(BaseSubjectHandler $subjectHandler, AppliedChangesLogModel $log) { 27 | $id = $subjectHandler->getIdBySnapshot($log->updateData); 28 | return $subjectHandler->delete($id); 29 | } 30 | 31 | public function change(BaseSubjectHandler $subjectHandler, CollectorFix $fix, $data = array()) { 32 | $id = $subjectHandler->getIdByChangeMethod(Module::FIX_CHANGES_AFTER_ADD_KEY, $data); 33 | 34 | $snapshot = $subjectHandler->getSnapshot($id); 35 | if (!$snapshot) { 36 | return false; 37 | } 38 | $fix 39 | ->setOriginalData(array()) 40 | ->setUpdateData($snapshot); 41 | return true; 42 | } 43 | 44 | public function getName() { 45 | return $this->getLocalization()->getDataByPath('add'); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/processes/baseprocess.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Processes; 7 | 8 | use WS\Migrations\ApplyResult; 9 | use WS\Migrations\ChangeDataCollector\CollectorFix; 10 | use WS\Migrations\Entities\AppliedChangesLogModel; 11 | use WS\Migrations\Module; 12 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 13 | 14 | abstract class BaseProcess { 15 | 16 | public function getLocalization() { 17 | return Module::getInstance()->getLocalization('processes'); 18 | } 19 | 20 | static public function className() { 21 | return get_called_class(); 22 | } 23 | 24 | abstract public function getName(); 25 | 26 | /** 27 | * @param BaseSubjectHandler $subjectHandler 28 | * @param CollectorFix $fix 29 | * @param AppliedChangesLogModel $log 30 | * @return ApplyResult 31 | */ 32 | abstract public function update(BaseSubjectHandler $subjectHandler, CollectorFix $fix, AppliedChangesLogModel $log); 33 | 34 | /** 35 | * @param BaseSubjectHandler $subjectHandler 36 | * @param AppliedChangesLogModel $log 37 | * @return ApplyResult 38 | */ 39 | abstract public function rollback(BaseSubjectHandler $subjectHandler, AppliedChangesLogModel $log); 40 | } 41 | -------------------------------------------------------------------------------- /lib/processes/deleteprocess.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Processes; 7 | 8 | 9 | use WS\Migrations\ChangeDataCollector\CollectorFix; 10 | use WS\Migrations\Entities\AppliedChangesLogModel; 11 | use WS\Migrations\Module; 12 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 13 | use WS\Migrations\SubjectHandlers\IblockSectionHandler; 14 | 15 | class DeleteProcess extends BaseProcess { 16 | private $_beforeChangesSnapshots = array(); 17 | 18 | public function update(BaseSubjectHandler $subjectHandler, CollectorFix $fix, AppliedChangesLogModel $log) { 19 | $id = $fix->getUpdateData(); 20 | $originalData = $subjectHandler->getSnapshot($id, $fix->getDbVersion()); 21 | 22 | $result = $subjectHandler->delete($id, $fix->getDbVersion()); 23 | 24 | $log->description = $fix->getName(); 25 | $log->originalData = $originalData; 26 | $log->updateData = $id; 27 | return $result; 28 | } 29 | 30 | public function rollback(BaseSubjectHandler $subjectHandler, AppliedChangesLogModel $log) { 31 | return $subjectHandler->applySnapshot($log->originalData); 32 | } 33 | 34 | public function beforeChange(BaseSubjectHandler $subjectHandler, $data) { 35 | $id = $subjectHandler->getIdByChangeMethod(Module::FIX_CHANGES_BEFORE_DELETE_KEY, $data); 36 | $this->_beforeChangesSnapshots[$id] = $snapshot = $subjectHandler->getSnapshot($id); 37 | } 38 | 39 | public function afterChange(BaseSubjectHandler $subjectHandler, CollectorFix $fix, $data) { 40 | $id = $subjectHandler->getIdByChangeMethod(Module::FIX_CHANGES_AFTER_DELETE_KEY, $data); 41 | $fix 42 | ->setOriginalData($this->_beforeChangesSnapshots[$id]) 43 | ->setUpdateData($id); 44 | $subjectHandler->registerDelete($id); 45 | return true; 46 | } 47 | 48 | public function getName() { 49 | return $this->getLocalization()->getDataByPath('delete'); 50 | } 51 | } -------------------------------------------------------------------------------- /lib/processes/updateprocess.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Processes; 7 | 8 | 9 | use WS\Migrations\ChangeDataCollector\CollectorFix; 10 | use WS\Migrations\Entities\AppliedChangesLogModel; 11 | use WS\Migrations\Module; 12 | use WS\Migrations\SubjectHandlers\BaseSubjectHandler; 13 | 14 | class UpdateProcess extends BaseProcess { 15 | 16 | private $_beforeChangesSnapshots = array(); 17 | 18 | public function update(BaseSubjectHandler $subjectHandler, CollectorFix $fix, AppliedChangesLogModel $log) { 19 | $data = $fix->getUpdateData(); 20 | $id = $subjectHandler->getIdBySnapshot($data); 21 | $originalData = $subjectHandler->getSnapshot($id, $fix->getDbVersion()); 22 | $result = $subjectHandler->applyChanges($data, $fix->getDbVersion()); 23 | 24 | $log->description = $fix->getName(); 25 | $log->originalData = $originalData; 26 | $log->updateData = $data; 27 | return $result; 28 | } 29 | 30 | public function rollback(BaseSubjectHandler $subjectHandler, AppliedChangesLogModel $log) { 31 | return $subjectHandler->applySnapshot($log->originalData); 32 | } 33 | 34 | public function beforeChange(BaseSubjectHandler $subjectHandler, $data) { 35 | $id = $subjectHandler->getIdByChangeMethod(Module::FIX_CHANGES_BEFORE_CHANGE_KEY, $data); 36 | $this->_beforeChangesSnapshots[$id] = $snapshot = $subjectHandler->getSnapshot($id); 37 | } 38 | 39 | public function afterChange(BaseSubjectHandler $subjectHandler, CollectorFix $fix, $data) { 40 | $id = $subjectHandler->getIdByChangeMethod(Module::FIX_CHANGES_AFTER_CHANGE_KEY, $data); 41 | $originalData = $this->_beforeChangesSnapshots[$id]; 42 | $actualData = $subjectHandler->getSnapshot($id); 43 | $data = $subjectHandler->analysisOfChanges($actualData, $this->_beforeChangesSnapshots[$id]); 44 | $data && $fix 45 | ->setOriginalData($originalData) 46 | ->setUpdateData($data); 47 | return true; 48 | } 49 | 50 | public function getName() { 51 | return $this->getLocalization()->getDataByPath('update'); 52 | } 53 | } -------------------------------------------------------------------------------- /lib/reference/referenceitem.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Reference; 7 | 8 | 9 | class ReferenceItem { 10 | public $id, $group, $dbVersion, $reference; 11 | } -------------------------------------------------------------------------------- /lib/scriptscenario.php: -------------------------------------------------------------------------------- 1 | setData($data); 29 | $this->_referenceController = $controller; 30 | } 31 | 32 | /** 33 | * @return Reference\ReferenceController 34 | */ 35 | public function getReferenceController(){ 36 | return $this->_referenceController; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getData() { 43 | return $this->_data; 44 | } 45 | 46 | /** 47 | * @param array $value 48 | */ 49 | protected function setData(array $value = array()) { 50 | $this->_data = $value; 51 | } 52 | 53 | 54 | /** 55 | * Runs to commit migration 56 | */ 57 | abstract public function commit(); 58 | 59 | /** 60 | * Runs by rollback migration 61 | */ 62 | abstract public function rollback(); 63 | 64 | /** 65 | * Returns name of migration 66 | * @return string 67 | */ 68 | abstract static public function name(); 69 | 70 | /** 71 | * @return array First element is hash, second is owner name 72 | */ 73 | abstract public function version(); 74 | 75 | /** 76 | * Returns description of migration 77 | * @return string 78 | */ 79 | abstract static public function description(); 80 | 81 | /** 82 | * Check to valid class definition 83 | * @return bool 84 | */ 85 | static public function isValid() { 86 | return static::name(); 87 | } 88 | } -------------------------------------------------------------------------------- /lib/subjecthandlers/basesubjecthandler.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lib/subjecthandlers/basesubjecthandler.php -------------------------------------------------------------------------------- /lib/subjecthandlers/iblockpropertyhandler.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lib/subjecthandlers/iblockpropertyhandler.php -------------------------------------------------------------------------------- /lib/subjecthandlers/iblocksectionhandler.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\SubjectHandlers; 7 | 8 | use Bitrix\Iblock\SectionTable; 9 | use Bitrix\Main\DB\Exception; 10 | use Bitrix\Main\Type\Date; 11 | use Bitrix\Main\Type\DateTime; 12 | use WS\Migrations\ApplyResult; 13 | use WS\Migrations\Diagnostic\DiagnosticResult; 14 | use WS\Migrations\Module; 15 | use WS\Migrations\Reference\ReferenceController; 16 | 17 | class IblockSectionHandler extends BaseSubjectHandler { 18 | 19 | /** 20 | * Name of Handler in Web interface 21 | * @return string 22 | */ 23 | public function getName() { 24 | return $this->getLocalization()->getDataByPath('iblockSection.name'); 25 | } 26 | 27 | public function getIdByChangeMethod($method, $data = array()) { 28 | switch ($method) { 29 | case Module::FIX_CHANGES_AFTER_ADD_KEY: 30 | case Module::FIX_CHANGES_BEFORE_CHANGE_KEY: 31 | case Module::FIX_CHANGES_AFTER_CHANGE_KEY: 32 | case Module::FIX_CHANGES_AFTER_DELETE_KEY: 33 | return $data[0]['ID']; 34 | case Module::FIX_CHANGES_BEFORE_DELETE_KEY: 35 | return $data[0]; 36 | } 37 | return null; 38 | } 39 | 40 | public function getSnapshot($id, $dbVersion = null) { 41 | if (!$id) { 42 | return false; 43 | } 44 | $dbVersion && $id = $this->getCurrentVersionId($id, $dbVersion); 45 | !$dbVersion && !$this->hasCurrentReference($id) && $this->registerCurrentVersionId($id); 46 | $data = SectionTable::GetByID($id)->Fetch(); 47 | $data['~reference'] = $this->getReferenceValue($id); 48 | return $data; 49 | } 50 | 51 | /** 52 | * @param $data 53 | * @param null $dbVersion 54 | * @throws \Exception 55 | * @return ApplyResult 56 | */ 57 | public function applySnapshot($data, $dbVersion = null) { 58 | $data = $this->handleNullValues($data); 59 | $sec = new \CIBlockSection(); 60 | $res = new ApplyResult(); 61 | 62 | $extId = $data['ID']; 63 | if ($dbVersion) { 64 | $data['IBLOCK_ID'] = $this->getReferenceController()->getCurrentIdByOtherVersion($data['IBLOCK_ID'], ReferenceController::GROUP_IBLOCK, $dbVersion); 65 | $data['IBLOCK_SECTION_ID'] && $data['IBLOCK_SECTION_ID'] = $this->getCurrentVersionId($data['IBLOCK_SECTION_ID'], $dbVersion); 66 | $id = $this->getCurrentVersionId($extId, $dbVersion); 67 | } else { 68 | $id = $extId; 69 | } 70 | if ($id && !SectionTable::getList(array('filter' => array('=ID' => $id)))->fetch()) { 71 | $arSec = SectionTable::getList(array( 72 | 'limit' => 1, 73 | 'order' => array( 74 | 'RIGHT_MARGIN' => 'desc' 75 | ) 76 | ))->fetch(); 77 | $margin = $arSec['RIGHT_MARGIN'] ?: 0; 78 | $dateTime = new DateTime(); 79 | $addRes = SectionTable::add(array( 80 | 'ID' => $id, 81 | 'IBLOCK_ID' => $data['IBLOCK_ID'], 82 | 'TIMESTAMP_X' => $dateTime->format('Y-m-d H:i:s'), 83 | 'NAME' => $data['NAME'], 84 | 'DESCRIPTION_TYPE' => $data['DESCRIPTION_TYPE'], 85 | 'LEFT_MARGIN' => $margin + 1, 86 | 'RIGHT_MARGIN' => $margin + 2, 87 | 'DEPTH_LEVEL' => 1 88 | )); 89 | if (!$addRes->isSuccess()) { 90 | throw new \Exception('Can`t create section ' . implode(', ', $addRes->getErrorMessages())."\n".var_export($data, true)); 91 | } 92 | } 93 | unset($data['CREATED_BY'], $data['MODIFIED_BY']); 94 | if ($id && ($currentData = SectionTable::getById($id)->fetch())) { 95 | $data['PICTURE'] = $currentData['PICTURE']; 96 | $data['DETAIL_PICTURE'] = $currentData['DETAIL_PICTURE']; 97 | $res->setSuccess((bool)$sec->Update($id, $data)); 98 | } else { 99 | unset($data['PICTURE'], $data['DETAIL_PICTURE']); 100 | $res->setSuccess((bool) ($id = $sec->Add($data))); 101 | $this->registerCurrentVersionId($id, $this->getReferenceValue($extId, $dbVersion)); 102 | } 103 | $res->setId($id); 104 | $res->setMessage($sec->LAST_ERROR); 105 | return $res; 106 | } 107 | 108 | /** 109 | * Delete subject record 110 | * @param $id 111 | * @param null $dbVersion 112 | * @return ApplyResult 113 | */ 114 | public function delete($id, $dbVersion = null) { 115 | $dbVersion && $id = $this->getCurrentVersionId($id, $dbVersion); 116 | !$dbVersion && !$this->hasCurrentReference($id) && $this->registerCurrentVersionId($id); 117 | 118 | 119 | $sec = new \CIBlockSection(); 120 | $res = new ApplyResult(); 121 | $res 122 | ->setSuccess((bool) $sec->Delete($id)) 123 | ->setMessage($sec->LAST_ERROR); 124 | $res->isSuccess() && $this->removeReference($id); 125 | return $res; 126 | } 127 | 128 | protected function getSubjectGroup() { 129 | return ReferenceController::GROUP_IBLOCK_SECTION; 130 | } 131 | 132 | public function existsIds() { 133 | $dbRes = SectionTable::getList(array( 134 | 'select' => array('ID') 135 | )); 136 | $res = array(); 137 | while ($item = $dbRes->fetch()) { 138 | $res[] = $item['ID']; 139 | } 140 | return $res; 141 | } 142 | 143 | static public function depends() { 144 | return array( 145 | IblockHandler::className() 146 | ); 147 | } 148 | 149 | protected function getExistsSubjectIds() { 150 | $rs = SectionTable::getList(array( 151 | 'select' => array('ID') 152 | )); 153 | $res = array(); 154 | while ($arSection = $rs->fetch()) { 155 | $res[] = $arSection['ID']; 156 | } 157 | return $res; 158 | } 159 | 160 | /** 161 | * @return DiagnosticResult 162 | */ 163 | public function diagnostic() { 164 | $referenceResult = $this->diagnosticByReference(); 165 | $itemsResult = $this->diagnosticByItems(\CIBlockSection::GetList()); 166 | $success = $referenceResult->isSuccess() && $itemsResult->isSuccess(); 167 | return new DiagnosticResult( 168 | $success, 169 | array_merge($referenceResult->getMessages(), $itemsResult->getMessages()) 170 | ); 171 | } 172 | } -------------------------------------------------------------------------------- /lib/tests/abstractcase.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lib/tests/abstractcase.php -------------------------------------------------------------------------------- /lib/tests/cases/agentbuildercase.php: -------------------------------------------------------------------------------- 1 | localization->message('name'); 12 | } 13 | 14 | public function description() { 15 | return $this->localization->message('description'); 16 | } 17 | 18 | public function init() { 19 | 20 | \CModule::IncludeModule('iblock'); 21 | } 22 | 23 | public function close() { 24 | $agent = \CAgent::GetList(null, array( 25 | 'NAME' => 'abs(0);' 26 | ))->Fetch(); 27 | \CAgent::Delete($agent['ID']); 28 | } 29 | 30 | 31 | public function testAdd() { 32 | $date = new DateTime(); 33 | $date->add('+1 day'); 34 | $builder = new \WS\Migrations\Builder\AgentBuilder(); 35 | $builder 36 | ->addAgent('abs(0);') 37 | ->setSort(23) 38 | ->setActive(true) 39 | ->setNextExec($date); 40 | $builder->commit(); 41 | 42 | $agent = \CAgent::GetList(null, array( 43 | 'NAME' => $builder->getCurrentAgent()->callback 44 | ))->Fetch(); 45 | 46 | $this->assertNotEmpty($agent); 47 | $this->assertEquals($agent['NAME'], $builder->getCurrentAgent()->callback); 48 | $this->assertEquals($agent['SORT'], 23); 49 | $this->assertEquals($agent['ACTIVE'], "Y"); 50 | $this->assertEquals($agent['NEXT_EXEC'], $date->format('d.m.Y H:i:s')); 51 | } 52 | 53 | 54 | public function testUpdate() { 55 | $builder = new \WS\Migrations\Builder\AgentBuilder(); 56 | $builder 57 | ->getAgent('abs(0);') 58 | ->setActive(false) 59 | ->setIsPeriod(true); 60 | 61 | $builder->commit(); 62 | 63 | $agent = \CAgent::GetList(null, array( 64 | 'NAME' => $builder->getCurrentAgent()->callback 65 | ))->Fetch(); 66 | 67 | $this->assertNotEmpty($agent); 68 | $this->assertEquals($agent['NAME'], $builder->getCurrentAgent()->callback); 69 | $this->assertEquals($agent['ACTIVE'], 'N'); 70 | $this->assertEquals($agent['IS_PERIOD'], 'Y'); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /lib/tests/cases/errorexception.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Tests\Cases; 7 | 8 | 9 | class ErrorException extends \Exception { 10 | 11 | private $_dumpedValue; 12 | 13 | public function setDump($value) { 14 | $this->_dumpedValue = $value; 15 | } 16 | 17 | public function getDump() { 18 | return $this->_dumpedValue; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/tests/cases/eventsbuildercase.php: -------------------------------------------------------------------------------- 1 | localization->message('name'); 14 | } 15 | 16 | public function description() { 17 | return $this->localization->message('description'); 18 | } 19 | 20 | public function init() { 21 | 22 | \CModule::IncludeModule('iblock'); 23 | } 24 | 25 | public function close() { 26 | $eventType = \CEventType::GetList(array( 27 | 'TYPE_ID' => 'WS_MIGRATION_TEST_EVENT', 28 | 'LID' => 'en' 29 | ))->Fetch(); 30 | $gw = new \CEventType; 31 | $gw->Delete($eventType['ID']); 32 | } 33 | 34 | public function testAdd() { 35 | $builder = new EventsBuilder(); 36 | $builder 37 | ->addEventType('WS_MIGRATION_TEST_EVENT', 'ru') 38 | ->setName('Тестовое событие миграций') 39 | ->setSort(10) 40 | ->setDescription('#TEST# - test'); 41 | 42 | $builder 43 | ->addEventMessage('#EMAIL_FROM#', '#EMAIL_TO#', 's1') 44 | ->setSubject('Hello') 45 | ->setBodyType(EventMessage::BODY_TYPE_HTML) 46 | ->setActive(true) 47 | ->setMessage('Hello #TEST#!') 48 | ; 49 | 50 | $builder 51 | ->addEventMessage('#FROM#', '#TO#', 's1') 52 | ->setSubject('Hi') 53 | ->setActive(false) 54 | ->setBodyType(EventMessage::BODY_TYPE_TEXT) 55 | ->setMessage('Hi #TEST#!') 56 | ; 57 | 58 | $builder->commit(); 59 | 60 | $eventType = \CEventType::GetList(array( 61 | 'TYPE_ID' => 'WS_MIGRATION_TEST_EVENT', 62 | 'LID' => 'ru' 63 | ))->Fetch(); 64 | $this->assertNotEmpty($eventType); 65 | $this->assertEquals($eventType['SORT'], 10); 66 | $this->assertNotEmpty($eventType['DESCRIPTION'], '#TEST# - test'); 67 | $this->assertNotEmpty($eventType['NAME'], 'Тестовое событие миграций'); 68 | 69 | $res = EventMessageTable::getList(array( 70 | 'filter' => array( 71 | 'EVENT_NAME' => 'WS_MIGRATION_TEST_EVENT' 72 | ) 73 | )); 74 | $this->assertEquals($res->getSelectedRowsCount(), 2); 75 | while ($item = $res->fetch()) { 76 | if ($item['SUBJECT'] == 'Hi') { 77 | $this->assertEquals($item['BODY_TYPE'], 'text'); 78 | $this->assertEquals($item['MESSAGE'], 'Hi #TEST#!'); 79 | $this->assertEquals($item['LID'], 's1'); 80 | $this->assertEquals($item['ACTIVE'], 'N'); 81 | $this->assertEquals($item['EMAIL_FROM'], '#FROM#'); 82 | $this->assertEquals($item['EMAIL_TO'], '#TO#'); 83 | } 84 | } 85 | } 86 | 87 | 88 | public function testUpdate() { 89 | $builder = new EventsBuilder(); 90 | $builder 91 | ->getEventType('WS_MIGRATION_TEST_EVENT', 'ru') 92 | ->setLid('en') 93 | ->setName('Тестовое событие'); 94 | 95 | foreach ($builder->getEventMessages() as $message) { 96 | if ($message->subject == 'Hello') { 97 | $message->remove(); 98 | } 99 | $message->setBcc('#BCC#'); 100 | } 101 | 102 | $builder->commit(); 103 | 104 | $eventType = \CEventType::GetList(array( 105 | 'TYPE_ID' => 'WS_MIGRATION_TEST_EVENT', 106 | 'LID' => 'en' 107 | ))->Fetch(); 108 | $this->assertTrue(!empty($eventType)); 109 | $this->assertNotEmpty($eventType['NAME'], 'Тестовое событие'); 110 | 111 | $res = EventMessageTable::getList(array( 112 | 'filter' => array( 113 | 'EVENT_NAME' => 'WS_MIGRATION_TEST_EVENT' 114 | ) 115 | )); 116 | $this->assertEquals($res->getSelectedRowsCount(), 1); 117 | while ($item = $res->fetch()) { 118 | $this->assertEquals($item['BCC'], '#BCC#'); 119 | } 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /lib/tests/cases/formbuildercase.php: -------------------------------------------------------------------------------- 1 | localization->message('name'); 14 | } 15 | 16 | public function description() { 17 | return $this->localization->message('description'); 18 | } 19 | 20 | public function init() { 21 | \CModule::IncludeModule('form'); 22 | } 23 | 24 | public function close() { 25 | $form = \CForm::GetList($by, $order, array( 26 | 'SID' => 'TestForm', 27 | ), $isFiltered)->Fetch(); 28 | if (!$form) { 29 | return; 30 | } 31 | \CForm::Delete($form['ID'], 'N'); 32 | } 33 | 34 | public function testAdd() { 35 | $builder = new FormBuilder(); 36 | $builder 37 | ->addForm('TestForm', 'TestForm') 38 | ->setArSiteId(array('s1')) 39 | ->setSort('10') 40 | ->setDescription('Description') 41 | ->setUseCaptcha(true) 42 | ->setArGroup(array( 43 | '2' => 10 44 | )) 45 | ->setArMenu(array("ru" => "Анкета посетителя", "en" => "Visitor Form")) 46 | ->setDescriptionType('html') 47 | ; 48 | 49 | $builder 50 | ->addField('testQuestion') 51 | ->setFieldType(FormField::FIELD_TYPE_INTEGER) 52 | ->setSort(33) 53 | ->setActive(false) 54 | ->setRequired(true) 55 | ->setTitle('testTitle') 56 | ->setArFilterAnswerText(array("dropdown")) 57 | ->setArFilterAnswerValue(array("dropdown")) 58 | ->setArFilterUser(array("dropdown")) 59 | ->setArFilterField(array("integer")) 60 | ->setComments('test comment') 61 | ->addAnswer('Привет мир!'); 62 | 63 | $builder 64 | ->addField('testField') 65 | ->setAsField() 66 | ->setTitle('test') 67 | ; 68 | $builder 69 | ->addStatus('status') 70 | ->setArGroupCanDelete(array(2)) 71 | ->setIsDefault(true); 72 | 73 | $builder->commit(); 74 | 75 | $form = \CForm::GetList($by, $order, array( 76 | 'ID' => $builder->getCurrentForm()->getId(), 77 | ), $isFiltered)->Fetch(); 78 | 79 | $this->assertNotEmpty($form); 80 | $this->assertEquals($form['C_SORT'], 10); 81 | $this->assertNotEmpty($form['DESCRIPTION'], 'Description'); 82 | $this->assertNotEmpty($form['NAME'], 'TestForm'); 83 | $this->assertNotEmpty($form['USE_CAPTCHA'], 'Y'); 84 | 85 | $res = \CFormField::GetList($builder->getCurrentForm()->getId(), 'ALL', $by, $order, array(), $isFiltered); 86 | 87 | $this->assertEquals($res->SelectedRowsCount(), 2); 88 | while ($item = $res->fetch()) { 89 | if ($item['SID'] == 'testQuestion') { 90 | $this->assertEquals($item['ACTIVE'], 'N'); 91 | $this->assertEquals($item['ADDITIONAL'], 'N'); 92 | $this->assertEquals($item['FIELD_TYPE'], 'integer'); 93 | $this->assertEquals($item['TITLE'], 'testTitle'); 94 | $this->assertEquals($item['C_SORT'], 33); 95 | $this->assertEquals($item['REQUIRED'], 'Y'); 96 | $this->assertEquals($item['COMMENTS'], 'test comment'); 97 | } 98 | if ($item['SID'] == 'testField') { 99 | $this->assertEquals($item['ADDITIONAL'], 'Y'); 100 | $this->assertEquals($item['TITLE'], 'test'); 101 | } 102 | } 103 | 104 | $res = \CFormStatus::GetList($builder->getCurrentForm()->getId(), $by, $order, array(), $isFiltered)->Fetch(); 105 | $this->assertEquals($res['TITLE'], 'status'); 106 | $this->assertEquals($res['DEFAULT_VALUE'], 'Y'); 107 | } 108 | 109 | 110 | public function testUpdate() { 111 | $builder = new FormBuilder(); 112 | $builder 113 | ->getForm('TestForm') 114 | ->setName('MyTestForm'); 115 | 116 | $field = $builder 117 | ->getField('testQuestion') 118 | ->setActive(true) 119 | ->setRequired(false); 120 | 121 | $field->removeAnswer('Привет мир!'); 122 | 123 | $field 124 | ->addAnswer('Test') 125 | ->setValue('val1'); 126 | 127 | $builder 128 | ->getStatus('status') 129 | ->setDescription('test22') 130 | ->setArGroupCanDelete(array(2, 3)); 131 | 132 | $builder->commit(); 133 | 134 | $form = \CForm::GetList($by, $order, array( 135 | 'ID' => $builder->getCurrentForm()->getId(), 136 | ), $isFiltered)->Fetch(); 137 | 138 | $this->assertNotEmpty($form); 139 | $this->assertNotEmpty($form['NAME'], 'MyTestForm'); 140 | 141 | $res = \CFormField::GetList($builder->getCurrentForm()->getId(), 'ALL', $by, $order, array(), $isFiltered); 142 | 143 | $this->assertEquals($res->SelectedRowsCount(), 2); 144 | while ($item = $res->fetch()) { 145 | if ($item['SID'] == 'testQuestion') { 146 | $this->assertEquals($item['ACTIVE'], 'Y'); 147 | $this->assertEquals($item['ADDITIONAL'], 'N'); 148 | $this->assertEquals($item['FIELD_TYPE'], 'integer'); 149 | $this->assertEquals($item['TITLE'], 'testTitle'); 150 | $this->assertEquals($item['REQUIRED'], 'N'); 151 | $this->assertEquals($item['COMMENTS'], 'test comment'); 152 | } 153 | } 154 | 155 | $res = \CFormStatus::GetList($builder->getCurrentForm()->getId(), $by, $order, array(), $isFiltered)->Fetch(); 156 | $this->assertEquals($res['DESCRIPTION'], 'test22'); 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /lib/tests/cases/highloadblockbuildercase.php: -------------------------------------------------------------------------------- 1 | localization->message('name'); 14 | } 15 | 16 | public function description() { 17 | return $this->localization->message('description'); 18 | } 19 | 20 | public function init() { 21 | 22 | \CModule::IncludeModule('iblock'); 23 | } 24 | 25 | public function close() { 26 | $arIblock = HighloadBlockTable::getList(array( 27 | 'filter' => array( 28 | 'TABLE_NAME' => 'test_highloadblock' 29 | ) 30 | ))->fetch(); 31 | 32 | HighloadBlockTable::delete($arIblock['ID']); 33 | } 34 | 35 | 36 | public function testAdd() { 37 | $builder = new HighLoadBlockBuilder(); 38 | $builder 39 | ->addHLBlock('TestBlock', 'test_highloadblock') 40 | ; 41 | $prop = $builder 42 | ->addField('uf_test1') 43 | ->setSort(10) 44 | ->setLabel(['ru' => 'Тест']) 45 | ->setUserTypeId(UserField::TYPE_ENUMERATION) 46 | ; 47 | $prop->addEnum('Тест1'); 48 | $prop->addEnum('Тест2'); 49 | $prop->addEnum('Тест3'); 50 | 51 | $builder 52 | ->addField('uf_test2') 53 | ->setLabel(['ru' => 'Тест2']) 54 | ->setUserTypeId(UserField::TYPE_HLBLOCK); 55 | 56 | $builder 57 | ->addField('uf_test3') 58 | ->setLabel(['ru' => 'Тест2']) 59 | ->setUserTypeId(UserField::TYPE_BOOLEAN); 60 | 61 | $builder 62 | ->addField('uf_test4') 63 | ->setLabel(['ru' => 'Тест2']) 64 | ->setUserTypeId(UserField::TYPE_DATETIME); 65 | 66 | $builder 67 | ->addField('uf_test5') 68 | ->setLabel(['ru' => 'Тест2']) 69 | ->setUserTypeId(UserField::TYPE_IBLOCK_ELEMENT); 70 | 71 | $builder 72 | ->addField('uf_test6') 73 | ->setLabel(['ru' => 'Тест2']) 74 | ->setUserTypeId(UserField::TYPE_VOTE); 75 | 76 | $builder 77 | ->addField('uf_test7') 78 | ->setLabel(['ru' => 'Тест2']) 79 | ->setUserTypeId(UserField::TYPE_VIDEO); 80 | 81 | $builder 82 | ->addField('uf_test8') 83 | ->setLabel(['ru' => 'Тест2']) 84 | ->setUserTypeId(UserField::TYPE_IBLOCK_SECTION); 85 | 86 | $builder->commit(); 87 | 88 | $arIblock = HighloadBlockTable::getList(array( 89 | 'filter' => array( 90 | 'ID' => $builder->getCurrentHighLoadBlock()->getId() 91 | ) 92 | ))->fetch(); 93 | 94 | $this->assertNotEmpty($arIblock, "hlblock wasn't created"); 95 | $this->assertEquals($arIblock['TABLE_NAME'], $builder->getCurrentHighLoadBlock()->tableName); 96 | $this->assertEquals($arIblock['NAME'], $builder->getCurrentHighLoadBlock()->name); 97 | 98 | $fields = \CUserTypeEntity::GetList(null, array( 99 | 'ENTITY_ID' => "HLBLOCK_" . $builder->getCurrentHighLoadBlock()->getId(), 100 | )); 101 | 102 | $this->assertEquals($fields->SelectedRowsCount(), 8); 103 | while ($field = $fields->Fetch()) { 104 | $field['NAME'] == 'uf_test5' && $this->assertEquals($field['USER_TYPE_ID'], UserField::TYPE_IBLOCK_ELEMENT); 105 | } 106 | 107 | } 108 | 109 | 110 | public function testUpdate() { 111 | $builder = new HighLoadBlockBuilder(); 112 | $builder 113 | ->getHLBlock('test_highloadblock') 114 | ->setName('TestBlock2') 115 | ; 116 | 117 | $prop = $builder 118 | ->getField('uf_test1') 119 | ->setMultiple(true) 120 | ->setRequired(true) 121 | ; 122 | $prop->updateEnum('Тест1')->setXmlId('test1'); 123 | $prop->removeEnum('Тест2'); 124 | 125 | $builder->commit(); 126 | 127 | $arIblock = HighloadBlockTable::getList(array( 128 | 'filter' => array( 129 | 'ID' => $builder->getCurrentHighLoadBlock()->getId() 130 | ) 131 | ))->fetch(); 132 | 133 | $this->assertEquals($arIblock['TABLE_NAME'], $builder->getCurrentHighLoadBlock()->tableName); 134 | $this->assertEquals($arIblock['NAME'], $builder->getCurrentHighLoadBlock()->name); 135 | 136 | $res = \CUserFieldEnum::GetList(null, array( 137 | 'USER_FIELD_ID' => $builder->getCurrentHighLoadBlock()->getId(), 138 | 'VALUE' => 'Тест2', 139 | ))->Fetch(); 140 | 141 | $this->assertEmpty($res); 142 | 143 | $res = \CUserFieldEnum::GetList(null, array( 144 | 'USER_FIELD_ID' => $prop->getId(), 145 | 'VALUE' => 'Тест1', 146 | ))->Fetch(); 147 | 148 | $this->assertNotEmpty($res); 149 | $this->assertEquals($res['XML_ID'], 'test1'); 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /lib/tests/cases/installtestcase.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Tests\Cases; 7 | 8 | 9 | use Bitrix\Iblock\IblockTable; 10 | use Bitrix\Iblock\PropertyTable; 11 | use Bitrix\Iblock\SectionTable; 12 | use WS\Migrations\Entities\DbVersionReferencesTable; 13 | use WS\Migrations\Module; 14 | use WS\Migrations\Reference\ReferenceController; 15 | use WS\Migrations\Tests\AbstractCase; 16 | 17 | class InstallTestCase extends AbstractCase { 18 | 19 | public function name() { 20 | return $this->localization->message('name'); 21 | } 22 | 23 | public function description() { 24 | return $this->localization->message('description'); 25 | } 26 | 27 | public function init() { 28 | \CModule::IncludeModule('iblock'); 29 | Module::getInstance()->clearReferences(); 30 | } 31 | 32 | 33 | public function testExistsReferencesRegister() { 34 | Module::getInstance()->install(); 35 | 36 | $dbRsRef = DbVersionReferencesTable::getList(array( 37 | 'filter' => array( 38 | 'GROUP' => ReferenceController::GROUP_IBLOCK 39 | ) 40 | )); 41 | $dbRsIblock = IblockTable::getList(); 42 | $this->assertEquals($dbRsIblock->getSelectedRowsCount(), $dbRsRef->getSelectedRowsCount(), $this->errorMessage('number of links to the information block and the information block entries must match')); 43 | 44 | $dbRsRef = DbVersionReferencesTable::getList(array( 45 | 'filter' => array( 46 | 'GROUP' => ReferenceController::GROUP_IBLOCK_PROPERTY 47 | ) 48 | )); 49 | $dbRsProp = PropertyTable::getList(); 50 | $this->assertEquals($dbRsProp->getSelectedRowsCount(), $dbRsRef->getSelectedRowsCount(), $this->errorMessage('number of links on the properties of information blocks and records must match')); 51 | 52 | $dbRsRef = DbVersionReferencesTable::getList(array( 53 | 'filter' => array( 54 | 'GROUP' => ReferenceController::GROUP_IBLOCK_SECTION 55 | ) 56 | )); 57 | $dbRsSection = SectionTable::getList(); 58 | $this->assertEquals($dbRsSection->getSelectedRowsCount(), $dbRsRef->getSelectedRowsCount(), $this->errorMessage('number of links to information block sections and records must match')); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /lib/tests/cases/rollbacktestcase.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Tests\Cases; 7 | 8 | 9 | use Bitrix\Iblock\IblockTable; 10 | use Bitrix\Iblock\PropertyTable; 11 | use Bitrix\Iblock\SectionTable; 12 | use WS\Migrations\ChangeDataCollector\Collector; 13 | use WS\Migrations\Module; 14 | use WS\Migrations\Tests\AbstractCase; 15 | 16 | class RollbackTestCase extends AbstractCase { 17 | 18 | public function name() { 19 | return $this->localization->message('name'); 20 | } 21 | 22 | public function description() { 23 | return ''; 24 | } 25 | 26 | public function init() { 27 | \CModule::IncludeModule('iblock'); 28 | Module::getInstance()->clearReferences(); 29 | } 30 | 31 | public function testReinitIblockReference() { 32 | $beforeApplyFix = array( 33 | 'iblocks' => IblockTable::getList()->getSelectedRowsCount(), 34 | 'properties' => PropertyTable::getList()->getSelectedRowsCount(), 35 | 'sections' => SectionTable::getList()->getSelectedRowsCount(), 36 | ); 37 | 38 | $collector = Collector::createByFile(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'fixtures'.DIRECTORY_SEPARATOR.'add_collection.json'); 39 | $this->assertNotEmpty($collector->getFixes()); 40 | Module::getInstance()->applyFixesList($collector->getFixes()); 41 | 42 | $afterApplyFix = array( 43 | 'iblocks' => IblockTable::getList()->getSelectedRowsCount(), 44 | 'properties' => PropertyTable::getList()->getSelectedRowsCount(), 45 | 'sections' => SectionTable::getList()->getSelectedRowsCount(), 46 | ); 47 | 48 | Module::getInstance()->rollbackLastChanges(); 49 | $afterRollback = array( 50 | 'iblocks' => IblockTable::getList()->getSelectedRowsCount(), 51 | 'properties' => PropertyTable::getList()->getSelectedRowsCount(), 52 | 'sections' => SectionTable::getList()->getSelectedRowsCount(), 53 | ); 54 | 55 | Module::getInstance()->applyFixesList($collector->getFixes()); 56 | $afterRollbackApply = array( 57 | 'iblocks' => IblockTable::getList()->getSelectedRowsCount(), 58 | 'properties' => PropertyTable::getList()->getSelectedRowsCount(), 59 | 'sections' => SectionTable::getList()->getSelectedRowsCount(), 60 | ); 61 | 62 | $this->assertEquals($beforeApplyFix['iblocks'], $afterApplyFix['iblocks'] - 1, $this->errorMessage('iblock not created after apply fix')); 63 | $this->assertEquals($beforeApplyFix['properties'], $afterApplyFix['properties'] - 2, $this->errorMessage('properties not created after apply fix')); 64 | $this->assertEquals($beforeApplyFix['sections'], $afterApplyFix['sections'] - 1, $this->errorMessage('sections not created after apply fix')); 65 | 66 | $this->assertEquals($beforeApplyFix['iblocks'], $afterRollback['iblocks'], $this->errorMessage('iblock not removed after rollback fix')); 67 | $this->assertEquals($beforeApplyFix['properties'], $afterRollback['properties'], $this->errorMessage('properties not removed after rollback fix')); 68 | $this->assertEquals($beforeApplyFix['sections'], $afterRollback['sections'], $this->errorMessage('sections not removed after rollback fix')); 69 | 70 | $this->assertEquals($afterRollback['iblocks'] + 1, $afterRollbackApply['iblocks'], $this->errorMessage('iblock not created after apply rollback fix')); 71 | $this->assertEquals($afterRollback['properties'] + 2, $afterRollbackApply['properties'], $this->errorMessage('properties not created after apply rollback fix')); 72 | $this->assertEquals($afterRollback['sections'] + 1, $afterRollbackApply['sections'], $this->errorMessage('sections not created after apply rollback fix')); 73 | } 74 | } -------------------------------------------------------------------------------- /lib/tests/cases/updatetestcase.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/lib/tests/cases/updatetestcase.php -------------------------------------------------------------------------------- /lib/tests/fixtures/delete_property.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "process": "reference", 4 | "subject": null, 5 | "data": { 6 | "reference": "d13bd5f833ec98310a112080508580b7", 7 | "group": "iblockProperty", 8 | "dbVersion": "test", 9 | "id": 46 10 | }, 11 | "originalData": null, 12 | "name": "Reference fix", 13 | "version": "test" 14 | }, 15 | { 16 | "process": "WS\\Migrations\\Processes\\DeleteProcess", 17 | "subject": "WS\\Migrations\\SubjectHandlers\\IblockPropertyHandler", 18 | "data": 46, 19 | "originalData": { 20 | "ID": "46", 21 | "TIMESTAMP_X": "2014-07-23 14:05:54", 22 | "IBLOCK_ID": "6", 23 | "NAME": "Test Add prop 1 ++", 24 | "ACTIVE": "Y", 25 | "SORT": "100", 26 | "CODE": "prop1", 27 | "DEFAULT_VALUE": "", 28 | "PROPERTY_TYPE": "S", 29 | "ROW_COUNT": "1", 30 | "COL_COUNT": "30", 31 | "LIST_TYPE": "L", 32 | "MULTIPLE": "N", 33 | "XML_ID": null, 34 | "FILE_TYPE": "", 35 | "MULTIPLE_CNT": "5", 36 | "TMP_ID": null, 37 | "LINK_IBLOCK_ID": "0", 38 | "WITH_DESCRIPTION": "N", 39 | "SEARCHABLE": "N", 40 | "FILTRABLE": "N", 41 | "IS_REQUIRED": "N", 42 | "VERSION": "1", 43 | "USER_TYPE": null, 44 | "USER_TYPE_SETTINGS": null, 45 | "HINT": "" 46 | }, 47 | "name": "\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430. \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435", 48 | "version": "test" 49 | } 50 | ] -------------------------------------------------------------------------------- /lib/tests/fixtures/delete_section.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "process": "reference", 4 | "subject": null, 5 | "data": { 6 | "reference": "27cd0bdc8556f84c7d4b77600acefabe", 7 | "group": "iblockSection", 8 | "dbVersion": "test", 9 | "id": 18 10 | }, 11 | "originalData": null, 12 | "name": "Reference fix", 13 | "version": "test" 14 | }, 15 | { 16 | "process": "WS\\Migrations\\Processes\\DeleteProcess", 17 | "subject": "WS\\Migrations\\SubjectHandlers\\IblockSectionHandler", 18 | "data": "18", 19 | "originalData": null, 20 | "name": "\u0420\u0430\u0437\u0434\u0435\u043b \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430. \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435", 21 | "version": "test" 22 | } 23 | ] -------------------------------------------------------------------------------- /lib/tests/result.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Tests; 7 | 8 | 9 | class Result { 10 | private $_success; 11 | private $_message; 12 | private $_trace; 13 | 14 | /** 15 | * @return mixed 16 | */ 17 | public function getMessage() { 18 | return $this->_message; 19 | } 20 | 21 | /** 22 | * @param mixed $value 23 | * @return $this 24 | */ 25 | public function setMessage($value) { 26 | $this->_message = $value; 27 | return $this; 28 | } 29 | 30 | public function setTrace($aTrace) { 31 | $this->_trace = $aTrace; 32 | return $this; 33 | } 34 | 35 | /** 36 | * @return mixed 37 | */ 38 | public function getTrace() { 39 | return $this->_trace; 40 | } 41 | 42 | /** 43 | * @return mixed 44 | */ 45 | public function isSuccess() { 46 | return $this->_success; 47 | } 48 | 49 | /** 50 | * @param mixed $value 51 | * @return $this 52 | */ 53 | public function setSuccess($value) { 54 | $this->_success = $value; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return array 60 | */ 61 | public function toArray() { 62 | return array( 63 | 'STATUS' => $this->isSuccess(), 64 | 'MESSAGE' => array( 65 | 'PREVIEW' => str_replace("\n", "
", $this->getMessage()), 66 | 'DETAIL' => $this->getTrace() 67 | ) 68 | ); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /lib/tests/starter.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace WS\Migrations\Tests; 7 | 8 | 9 | use WS\Migrations\Module; 10 | use WS\Migrations\Tests\Cases\AgentBuilderCase; 11 | use WS\Migrations\Tests\Cases\EventsBuilderCase; 12 | use WS\Migrations\Tests\Cases\FixTestCase; 13 | use WS\Migrations\Tests\Cases\FormBuilderCase; 14 | use WS\Migrations\Tests\Cases\HighLoadBlockBuilderCase; 15 | use WS\Migrations\Tests\Cases\IblockBuilderCase; 16 | use WS\Migrations\Tests\Cases\InstallTestCase; 17 | use WS\Migrations\Tests\Cases\RollbackTestCase; 18 | use WS\Migrations\Tests\Cases\UpdateTestCase; 19 | 20 | class Starter { 21 | 22 | const SECTION = 'WSMIGRATIONS'; 23 | 24 | static public function className() { 25 | return get_called_class(); 26 | } 27 | 28 | /** 29 | * @return \WS\Migrations\Localization 30 | */ 31 | static public function getLocalization() { 32 | return Module::getInstance()->getLocalization('tests'); 33 | } 34 | 35 | static public function cases() { 36 | return array( 37 | FixTestCase::className(), 38 | UpdateTestCase::className(), 39 | InstallTestCase::className(), 40 | RollbackTestCase::className(), 41 | IblockBuilderCase::className(), 42 | HighLoadBlockBuilderCase::className(), 43 | AgentBuilderCase::className(), 44 | EventsBuilderCase::className(), 45 | FormBuilderCase::className(), 46 | ); 47 | } 48 | 49 | static private function _getLocalizationByCase ($class) { 50 | return static::getLocalization()->fork('cases.'.$class); 51 | } 52 | 53 | /** 54 | * Run module tests 55 | * @internal param $aCheckList 56 | * @return array 57 | */ 58 | static public function items() { 59 | if (!Module::getInstance()->getOptions()->useAutotests) { 60 | return array(); 61 | } 62 | $points = array(); 63 | $i = 1; 64 | $fGetCaseId = function ($className) { 65 | $arClass = implode('\\', $className); 66 | return array_pop($arClass); 67 | }; 68 | foreach (self::cases() as $caseClass) { 69 | /** @var $case AbstractCase */ 70 | $case = new $caseClass(static::_getLocalizationByCase($caseClass)); 71 | $points[self::SECTION.'-'.$i++] = array( 72 | 'AUTO' => 'Y', 73 | 'NAME' => $case->name(), 74 | 'DESC' => $case->description(), 75 | 'CLASS_NAME' => get_called_class(), 76 | 'METHOD_NAME' => 'run', 77 | 'PARENT' => self::SECTION, 78 | 'PARAMS' => array( 79 | 'class' => $caseClass 80 | ) 81 | ); 82 | } 83 | 84 | return array( 85 | 'CATEGORIES' => array( 86 | self::SECTION => array( 87 | 'NAME' => static::getLocalization()->message('run.name') 88 | ) 89 | ), 90 | 'POINTS' => $points 91 | ); 92 | } 93 | 94 | static public function run($params) { 95 | $class = $params['class']; 96 | $result = new Result(); 97 | if (!$class) { 98 | $result->setSuccess(false); 99 | $result->setMessage('Params not is correct'); 100 | return $result->toArray(); 101 | } 102 | $testCase = new $class(static::_getLocalizationByCase($class)); 103 | if (!$testCase instanceof AbstractCase) { 104 | $result->setSuccess(false); 105 | $result->setMessage('Case class is not correct'); 106 | return $result->toArray(); 107 | } 108 | $refClass = new \ReflectionObject($testCase); 109 | $testMethods = array_filter($refClass->getMethods(), function (\ReflectionMethod $method) { 110 | return strpos(strtolower($method->getName()), 'test') === 0; 111 | }); 112 | try { 113 | $count = 0; 114 | /** @var $method \ReflectionMethod */ 115 | $testCase->init(); 116 | foreach ($testMethods as $method) { 117 | $testCase->setUp(); 118 | $method->invoke($testCase); 119 | $testCase->tearDown(); 120 | $count++; 121 | } 122 | } catch (\Exception $e) { 123 | $result->setSuccess(false) 124 | ->setTrace($e->getTraceAsString()); 125 | $message = $method->getShortName(). ', '. $e->getMessage(); 126 | if ($e instanceof \WS\Migrations\Tests\Cases\ErrorException) { 127 | $e->getDump() && $message .= "\ndump: \n" . var_export($e->getDump(), true); 128 | } 129 | $result->setMessage($message); 130 | return $result->toArray(); 131 | } 132 | $testCase->close(); 133 | return $result->setSuccess(true) 134 | ->setMessage(static::getLocalization()->message('run.report.completed').':'.$count."\n".static::getLocalization()->message('run.report.assertions').': '.$testCase->getAssertsCount()) 135 | ->toArray(); 136 | } 137 | } -------------------------------------------------------------------------------- /options.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worksolutions/bitrix-module-migrations/9b748cfcfc6e8e3031d07896969839436b4805d4/options.php -------------------------------------------------------------------------------- /prolog.php: -------------------------------------------------------------------------------- 1 | errorMessage[] = $mess; 13 | }; 14 | //===================================================== 15 | 16 | $docRoot = rtrim($_SERVER['DOCUMENT_ROOT'], '/').'/'; 17 | // install file platform version 18 | $uploadDir = $docRoot . \COption::GetOptionString("main", "upload_dir", "upload"); 19 | $modulePath = rtrim($docRoot, '/').$updater->kernelPath.'/modules/'.$updater->moduleID; 20 | $updatePath = $docRoot.$updater->curModulePath; 21 | 22 | $isInstalled = \Bitrix\Main\ModuleManager::isModuleInstalled($updater->moduleID); 23 | --------------------------------------------------------------------------------