├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── migrator ├── phpunit.xml ├── src ├── Autocreate │ ├── Handlers │ │ ├── BaseHandler.php │ │ ├── HandlerInterface.php │ │ ├── OnBeforeGroupAdd.php │ │ ├── OnBeforeGroupDelete.php │ │ ├── OnBeforeGroupUpdate.php │ │ ├── OnBeforeHLBlockAdd.php │ │ ├── OnBeforeHLBlockDelete.php │ │ ├── OnBeforeHLBlockUpdate.php │ │ ├── OnBeforeIBlockAdd.php │ │ ├── OnBeforeIBlockDelete.php │ │ ├── OnBeforeIBlockPropertyAdd.php │ │ ├── OnBeforeIBlockPropertyDelete.php │ │ ├── OnBeforeIBlockPropertyUpdate.php │ │ ├── OnBeforeIBlockUpdate.php │ │ ├── OnBeforeUserTypeAdd.php │ │ └── OnBeforeUserTypeDelete.php │ ├── Manager.php │ └── Notifier.php ├── BaseMigrations │ └── BitrixMigration.php ├── Commands │ ├── AbstractCommand.php │ ├── ArchiveCommand.php │ ├── InstallCommand.php │ ├── MakeCommand.php │ ├── MigrateCommand.php │ ├── RollbackCommand.php │ ├── StatusCommand.php │ └── TemplatesCommand.php ├── Constructors │ ├── Constructor.php │ ├── FieldConstructor.php │ ├── HighloadBlock.php │ ├── IBlock.php │ ├── IBlockProperty.php │ ├── IBlockPropertyEnum.php │ ├── IBlockType.php │ └── UserField.php ├── Exceptions │ ├── MigrationException.php │ ├── SkipHandlerException.php │ └── StopHandlerException.php ├── Helpers.php ├── Interfaces │ ├── DatabaseStorageInterface.php │ ├── FileStorageInterface.php │ └── MigrationInterface.php ├── Logger.php ├── Migrator.php ├── Storages │ ├── BitrixDatabaseStorage.php │ └── FileStorage.php └── TemplatesCollection.php ├── templates ├── add_iblock.template ├── add_iblock_element_property.template ├── add_iblock_type.template ├── add_table.template ├── add_uf.template ├── auto │ ├── add_group.template │ ├── add_hlblock.template │ ├── add_iblock.template │ ├── add_iblock_element_property.template │ ├── add_uf.template │ ├── delete_group.template │ ├── delete_hlblock.template │ ├── delete_iblock.template │ ├── delete_iblock_element_property.template │ ├── delete_uf.template │ ├── update_group.template │ ├── update_hlblock.template │ ├── update_iblock.template │ ├── update_iblock_element_property.template │ └── update_uf.template ├── default.template ├── delete_table.template └── query.template └── tests ├── CommandTestCase.php ├── InstallCommandTest.php ├── MakeCommandTest.php ├── MigrateCommandTest.php ├── MigratorTest.php └── RollbackCommandTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | /.idea 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: 3 | - 'src/*' 4 | excluded_paths: 5 | - 'vendor/*' 6 | - 'tests/*' 7 | tools: 8 | php_cs_fixer: 9 | config: { level: psr2 } 10 | checks: 11 | php: 12 | code_rating: true 13 | duplication: true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - 7.2 8 | - 7.3 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --prefer-source --no-interaction 13 | 14 | script: 15 | - vendor/bin/phpunit 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nekrasov Ilya 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/arrilot/bitrix-migrations/v/stable.svg)](https://packagist.org/packages/arrilot/bitrix-migrations/) 2 | [![Total Downloads](https://img.shields.io/packagist/dt/arrilot/bitrix-migrations.svg?style=flat)](https://packagist.org/packages/Arrilot/bitrix-migrations) 3 | [![Build Status](https://img.shields.io/travis/arrilot/bitrix-migrations/master.svg?style=flat)](https://travis-ci.org/arrilot/bitrix-migrations) 4 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/arrilot/bitrix-migrations/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/arrilot/bitrix-migrations/) 5 | 6 | # Данный пакет больше активно не поддерживается 7 | 8 | Причина - мы больше не используем Битрикс в своих проектах. 9 | Если вам интересен этот проект и вы хотите заняться его поддержкой - форкните его и создайте Issue в данном репозитории чтобы мы поместили здесь ссылку на форк. 10 | 11 | Форки: 12 | - https://github.com/informunity/bitrix-migrations 13 | 14 | # Bitrix-migrations 15 | 16 | *Миграции БД для Битрикса и не только* 17 | 18 | ## Установка 19 | 20 | 1) `composer require arrilot/bitrix-migrations` 21 | 22 | 2) `cp vendor/arrilot/bitrix-migrations/migrator migrator` - копируем исполняемый файл в удобное место. 23 | 24 | 3) заходим внутрь и удостоверяемся что задается правильный $_SERVER['DOCUMENT_ROOT']. Меняем настройки если нужно 25 | 26 | 4) `php migrator install` 27 | 28 | Данная команда создаст в БД таблицу для хранения названий выполненных миграций. 29 | 30 | По умолчанию: 31 | 32 | 1) Таблица называется migrations. 33 | 34 | 2) `composer.json` и `migrator` лежат в корне сайта. 35 | 36 | 3) Файлы миграций будут создаваться в директории `./migrations` относительно скопированного на этапе 2 файла. 37 | 38 | При необходимости всё это можно изменить в скопированном файле `migrator`. 39 | 40 | * Крайне рекомендуется сделать `migrator` и `./migrations` недоступными по http через веб-сервер. * 41 | 42 | ## Использование 43 | 44 | ### Рабочий процесс 45 | 46 | Рабочий процесс происходит через консоль и кратко описывается примерно так: 47 | 48 | 1) Создаем файл (или файлы) миграции при помощи `php migrator make название_миграции` 49 | 50 | Файл миграции представляет из себя класс с двумя методами `up()` и `down()` 51 | 52 | 2) Реализуем в методе `up()`необходимые изменения в БД. При желании в методе `down()` реализуем откат этих измнений 53 | 54 | 3) Применяем имеющиеся миграции - `php migrator migrate` 55 | 56 | 4) Вносим файлы миграций в систему контроля версий, чтобы их можно было запустить и на других машинах 57 | 58 | 59 | ### Доступные команды 60 | 61 | Список доступных команд можно получить в консоли - `php migrator list` 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 105 | 106 |
НазваниеОписание
`php migrator install`Создает таблицу для хранения миграций. Запускается один раз.
`php migrator make название_миграции` 72 | Создает файл миграции 73 | Опции:
74 | `-d foo/bar` - указать поддиректорию, в которой будет создана миграция
75 |
`php migrator migrate`Применяет все доступные для применения миграции. Миграции примененные ранее не применяются.
`php migrator rollback` 84 | Откатывает последнюю миграцию (метод `down()`). После этого её можно применить повторно.
85 | Опции:
86 | `--hard` - выполнить жесткий откат без вызова метода `down()`
87 | `--delete` - удалить файл с миграцией после отката.
88 |
`php migrator templates`Показывает подробную таблицу со всем существующими шаблонами миграций
`php migrator status`Показывает доступные для выполнения миграции, а также последние выполненные.
`php migrator archive` 101 | Переносит все миграции в архив. По умолчанию это директория archive, но можно переопределить в конфиге, указав "dir_archive"
102 | Опции:
103 | `-w 10` - не переносить в архив последние N миграций
104 |
107 | 108 | ### Шаблоны миграций 109 | 110 | Так как изменение структуры БД битрикса через его АПИ - занятие крайне малоприятное, то для облегчения этого процесса есть механизм шаблонов миграций, работающий следущим образом: 111 | При генерации файла миграции можно указать его шаблон: `php migrator make название_миграции -t add_iblock` где `add_block` - название шаблона. 112 | При этом сгенерируется класс с бойлерплейтом из шаблона и остается лишь указать детали (например название и код инфоблока) 113 | Свои шаблоны миграций можно добавить напрямую в файле `migrator` при помощи `TemplateCollection::registerTemplate()` 114 | 115 | Имеющиеся шаблоны: 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 |
НазваниеОписаниеАлиасы
`default`Чистый шаблон по умолчанию
`add_iblock_type`Добавление типа инфоблока
`add_iblock`Добавление инфоблока
`add_iblock_element_property`Добавление свойства в инфоблок`add_iblock_prop`, `add_iblock_element_prop`, `add_element_prop`, `add_element_property`
`add_uf`Добавление UF свойства
`query`Произвольный запрос в БД через АПИ d7
`add_table`Создание таблицы через АПИ d7`create_table`
`delete_table`Удаление таблицы через АПИ d7`drop_table`
160 | 161 | 6) `php migrator status` - показывает доступные для выполнения миграции, а также последние выполненные. 162 | 163 | 164 | ### Автоматическое создание миграций 165 | 166 | Еще одна киллер-фича - режим автоматического создания миграций. 167 | Для его включения необходимо добавить примерно следующее в `init.php` 168 | 169 | ```php 170 | Arrilot\BitrixMigrations\Autocreate\Manager::init($_SERVER["DOCUMENT_ROOT"].'/migrations'); 171 | ``` 172 | 173 | В метод `Manager::init()` передается путь до директории аналогичной конфигу в файле `migrator`. 174 | 175 | После этого при выполнении ряда действий в админке будет происходить следующее 176 | 177 | 1) Срабатывает битриксовый обработчик события 178 | 179 | 2) Создается файл миграции как при `php migrator make` 180 | 181 | 3) Миграция помечается примененной 182 | 183 | 4) Показывается нотификация о предыдущих пунктах 184 | 185 | Включение данного режима позволяет избавиться от ручного написания миграций для многих случаев. 186 | Ничего в создаваемых автоматически миграциях править не требуется. 187 | 188 | Список обрабатываемых событий: 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
СобытиеКомментарии
Добавление инфоблока
Обновление базовых полей инфоблокаИз-за специфики работы админки битрикса эта миграция зачастую создается когда не нужно, допустим при добавлении кастомного свойства в инфоблок. Ничего смертельного, но надо смириться.
Удаление инфоблока
Добавление кастомного свойства в инфоблок
Обновление кастомного свойства инфоблокаМиграция создается только если какой-либо из атрибутов свойства был изменён
Удаление кастомного свойства инфоблока
Добавление UF свойства куда-либо (раздел ИБ, пользователь, хайлоадблок)К сожалению Битрикс не даёт возможности отслеживать изменение такого свойства - только добавление и удаление
Удаление UF свойства
Добавление хайлоадблока
Изменение хайлоадблокаМиграция создается только если какой-либо из атрибутов хайлоадблока был изменён
Удаление хайлоадблока
Добавление группы пользователей
Изменение группы пользователей
Удаление группы пользователей
249 | 250 | * Миграции используют события `OnBefore...`. Если при вашем изменении произошла ошибка (допустим не указана привязка к сайту при добавлении инфоблока) 251 | и было показано уведомление о том что миграция создана, необходимо вручную откатить такую миграцию при помощи `php migrator rollback --hard --delete` * 252 | 253 | ### Обработка ошибок миграций 254 | 255 | Для отмены миграции в момент её выполнения достаточно выкинуть исключение - ```php throw new MigrationException('Тут текст ошибки');``` 256 | Ни сама миграция, ни последующие при этом применены не будут. 257 | 258 | ## Использование вне Битрикс 259 | 260 | Пакет создан для использования совместно с Битриксом, однако его довольно просто можно использовать и в других системах. 261 | Для этого нужно в файле `migrator`: 262 | 263 | 1) Заменить подключение ядра Битрикса на ядро другой системы. 264 | 265 | 2) Реализовать свой аналог ` Arrilot\BitrixMigrations\Repositories\BitrixDatabaseRepository;` и использовать его. 266 | 267 | 3) По желанию отключить существующие шаблоны миграций, сделав свои. 268 | 269 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arrilot/bitrix-migrations", 3 | "license": "MIT", 4 | "description": "Database migrations for Bitrix CMS", 5 | "keywords": [ 6 | "bitrix", 7 | "migrations" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Nekrasov Ilya", 12 | "email": "nekrasov.ilya90@gmail.com" 13 | } 14 | ], 15 | "homepage": "https://github.com/Arrilot/bitrix-migrations", 16 | "require": { 17 | "php": ">=5.5.0", 18 | "symfony/console": "~2|~3|~4", 19 | "tightenco/collect": "5.*" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "~4.0", 23 | "mockery/mockery": "~0.9" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Arrilot\\BitrixMigrations\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Arrilot\\Tests\\BitrixMigrations\\": "tests/" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /migrator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'migrations', 26 | 'dir' => './migrations', 27 | // 'dir_archive' => 'archive', // not required. default = "archive" 28 | 'use_transaction' => true, // not required. default = false 29 | 'default_fields' => [ 30 | IBlock::class => [ 31 | 'INDEX_ELEMENT' => 'N', 32 | 'INDEX_SECTION' => 'N', 33 | 'VERSION' => 2, 34 | 'SITE_ID' => 's1', 35 | ] 36 | ] 37 | ]; 38 | 39 | $database = new BitrixDatabaseStorage($config['table']); 40 | $templates = new TemplatesCollection(); 41 | $templates->registerBasicTemplates(); 42 | 43 | $migrator = new Migrator($config, $templates, $database); 44 | 45 | $app = new Application('Migrator'); 46 | $app->add(new MakeCommand($migrator)); 47 | $app->add(new InstallCommand($config['table'], $database)); 48 | $app->add(new MigrateCommand($migrator)); 49 | $app->add(new RollbackCommand($migrator)); 50 | $app->add(new TemplatesCommand($templates)); 51 | $app->add(new StatusCommand($migrator)); 52 | $app->add(new ArchiveCommand($migrator)); 53 | $app->run(); 54 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/BaseHandler.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 19 | 20 | if (!$this->fields['STRING_ID']) { 21 | throw new StopHandlerException('Code is required to create a migration'); 22 | } 23 | } 24 | 25 | /** 26 | * Get migration name. 27 | * 28 | * @return string 29 | */ 30 | public function getName() 31 | { 32 | return "auto_add_group_{$this->fields['STRING_ID']}"; 33 | } 34 | 35 | /** 36 | * Get template name. 37 | * 38 | * @return string 39 | */ 40 | public function getTemplate() 41 | { 42 | return 'auto_add_group'; 43 | } 44 | 45 | /** 46 | * Get array of placeholders to replace. 47 | * 48 | * @return array 49 | */ 50 | public function getReplace() 51 | { 52 | return [ 53 | 'fields' => var_export($this->fields, true), 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeGroupDelete.php: -------------------------------------------------------------------------------- 1 | id = $params[0]; 27 | 28 | $this->fields = CGroup::GetByID($this->id)->fetch(); 29 | } 30 | 31 | /** 32 | * Get migration name. 33 | * 34 | * @return string 35 | */ 36 | public function getName() 37 | { 38 | return "auto_delete_group_{$this->fields['STRING_ID']}"; 39 | } 40 | 41 | /** 42 | * Get template name. 43 | * 44 | * @return string 45 | */ 46 | public function getTemplate() 47 | { 48 | return 'auto_delete_group'; 49 | } 50 | 51 | /** 52 | * Get array of placeholders to replace. 53 | * 54 | * @return array 55 | */ 56 | public function getReplace() 57 | { 58 | return [ 59 | 'id' => $this->id, 60 | ]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeGroupUpdate.php: -------------------------------------------------------------------------------- 1 | id = $params[0]; 26 | $this->fields = $params[1]; 27 | 28 | if (!$this->fields['STRING_ID']) { 29 | throw new StopHandlerException('Code is required to create a migration'); 30 | } 31 | } 32 | 33 | /** 34 | * Get migration name. 35 | * 36 | * @return string 37 | */ 38 | public function getName() 39 | { 40 | return "auto_update_group_{$this->fields['STRING_ID']}"; 41 | } 42 | 43 | /** 44 | * Get template name. 45 | * 46 | * @return string 47 | */ 48 | public function getTemplate() 49 | { 50 | return 'auto_update_group'; 51 | } 52 | 53 | /** 54 | * Get array of placeholders to replace. 55 | * 56 | * @return array 57 | */ 58 | public function getReplace() 59 | { 60 | return [ 61 | 'fields' => var_export($this->fields, true), 62 | 'id' => $this->id, 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeHLBlockAdd.php: -------------------------------------------------------------------------------- 1 | event = $params[0]; 25 | 26 | $this->fields = $this->event->getParameter('fields'); 27 | } 28 | 29 | /** 30 | * Get migration name. 31 | * 32 | * @return string 33 | */ 34 | public function getName() 35 | { 36 | return 'auto_add_hlblock_'.$this->fields['TABLE_NAME']; 37 | } 38 | 39 | /** 40 | * Get template name. 41 | * 42 | * @return string 43 | */ 44 | public function getTemplate() 45 | { 46 | return 'auto_add_hlblock'; 47 | } 48 | 49 | /** 50 | * Get array of placeholders to replace. 51 | * 52 | * @return array 53 | */ 54 | public function getReplace() 55 | { 56 | return [ 57 | 'fields' => var_export($this->fields, true), 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeHLBlockDelete.php: -------------------------------------------------------------------------------- 1 | event = $params[0]; 33 | 34 | $eventParams = $this->event->getParameters(); 35 | 36 | $this->id = $eventParams['id']['ID']; 37 | $this->fields = HighloadBlockTable::getById($this->id)->fetch(); 38 | } 39 | 40 | /** 41 | * Get migration name. 42 | * 43 | * @return string 44 | */ 45 | public function getName() 46 | { 47 | return 'auto_delete_hlblock_'.$this->fields['TABLE_NAME']; 48 | } 49 | 50 | /** 51 | * Get template name. 52 | * 53 | * @return string 54 | */ 55 | public function getTemplate() 56 | { 57 | return 'auto_delete_hlblock'; 58 | } 59 | 60 | /** 61 | * Get array of placeholders to replace. 62 | * 63 | * @return array 64 | */ 65 | public function getReplace() 66 | { 67 | return [ 68 | 'fields' => var_export($this->fields, true), 69 | 'id' => $this->id, 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeHLBlockUpdate.php: -------------------------------------------------------------------------------- 1 | event = $params[0]; 33 | 34 | $eventParams = $this->event->getParameters(); 35 | 36 | $this->fields = $eventParams['fields']; 37 | $this->id = $eventParams['id']['ID']; 38 | 39 | if (!$this->fieldsHaveBeenChanged()) { 40 | throw new SkipHandlerException(); 41 | } 42 | } 43 | 44 | /** 45 | * Get migration name. 46 | * 47 | * @return string 48 | */ 49 | public function getName() 50 | { 51 | return 'auto_update_hlblock_'.$this->fields['TABLE_NAME']; 52 | } 53 | 54 | /** 55 | * Get template name. 56 | * 57 | * @return string 58 | */ 59 | public function getTemplate() 60 | { 61 | return 'auto_update_hlblock'; 62 | } 63 | 64 | /** 65 | * Get array of placeholders to replace. 66 | * 67 | * @return array 68 | */ 69 | public function getReplace() 70 | { 71 | return [ 72 | 'id' => $this->id, 73 | 'fields' => var_export($this->fields, true), 74 | ]; 75 | } 76 | 77 | /** 78 | * Determine if fields have been changed. 79 | * 80 | * @return bool 81 | */ 82 | protected function fieldsHaveBeenChanged() 83 | { 84 | $old = HighloadBlockTable::getById($this->id)->fetch(); 85 | $new = $this->fields + ['ID' => (string) $this->id]; 86 | 87 | return $new != $old; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockAdd.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 15 | } 16 | 17 | /** 18 | * Get migration name. 19 | * 20 | * @return string 21 | */ 22 | public function getName() 23 | { 24 | return "auto_add_iblock_{$this->fields['CODE']}"; 25 | } 26 | 27 | /** 28 | * Get template name. 29 | * 30 | * @return string 31 | */ 32 | public function getTemplate() 33 | { 34 | return 'auto_add_iblock'; 35 | } 36 | 37 | /** 38 | * Get array of placeholders to replace. 39 | * 40 | * @return array 41 | */ 42 | public function getReplace() 43 | { 44 | return [ 45 | 'fields' => var_export($this->fields, true), 46 | 'code' => "'".$this->fields['CODE']."'", 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockDelete.php: -------------------------------------------------------------------------------- 1 | fields = $this->getIBlockById($params[0]); 17 | } 18 | 19 | /** 20 | * Get migration name. 21 | * 22 | * @return string 23 | */ 24 | public function getName() 25 | { 26 | return "auto_delete_iblock_{$this->fields['CODE']}"; 27 | } 28 | 29 | /** 30 | * Get template name. 31 | * 32 | * @return string 33 | */ 34 | public function getTemplate() 35 | { 36 | return 'auto_delete_iblock'; 37 | } 38 | 39 | /** 40 | * Get array of placeholders to replace. 41 | * 42 | * @return array 43 | */ 44 | public function getReplace() 45 | { 46 | return [ 47 | 'code' => "'".$this->fields['CODE']."'", 48 | ]; 49 | } 50 | 51 | /** 52 | * Get iblock by id without checking permissions. 53 | * 54 | * @param $id 55 | * 56 | * @return array 57 | */ 58 | protected function getIBlockById($id) 59 | { 60 | $filter = [ 61 | 'ID' => $id, 62 | 'CHECK_PERMISSIONS' => 'N', 63 | ]; 64 | 65 | return (new CIBlock())->getList([], $filter)->fetch(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockPropertyAdd.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 18 | 19 | if (!$this->fields['IBLOCK_ID']) { 20 | throw new SkipHandlerException(); 21 | } 22 | } 23 | 24 | /** 25 | * Get migration name. 26 | * 27 | * @return string 28 | */ 29 | public function getName() 30 | { 31 | return "auto_add_iblock_element_property_{$this->fields['CODE']}_to_ib_{$this->fields['IBLOCK_ID']}"; 32 | } 33 | 34 | /** 35 | * Get template name. 36 | * 37 | * @return string 38 | */ 39 | public function getTemplate() 40 | { 41 | return 'auto_add_iblock_element_property'; 42 | } 43 | 44 | /** 45 | * Get array of placeholders to replace. 46 | * 47 | * @return array 48 | */ 49 | public function getReplace() 50 | { 51 | return [ 52 | 'fields' => var_export($this->fields, true), 53 | 'iblockId' => $this->fields['IBLOCK_ID'], 54 | 'code' => "'".$this->fields['CODE']."'", 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockPropertyDelete.php: -------------------------------------------------------------------------------- 1 | fields = CIBlockProperty::getByID($params[0])->fetch(); 17 | } 18 | 19 | /** 20 | * Get migration name. 21 | * 22 | * @return string 23 | */ 24 | public function getName() 25 | { 26 | return "auto_delete_iblock_element_property_{$this->fields['CODE']}_in_ib_{$this->fields['IBLOCK_ID']}"; 27 | } 28 | 29 | /** 30 | * Get template name. 31 | * 32 | * @return string 33 | */ 34 | public function getTemplate() 35 | { 36 | return 'auto_delete_iblock_element_property'; 37 | } 38 | 39 | /** 40 | * Get array of placeholders to replace. 41 | * 42 | * @return array 43 | */ 44 | public function getReplace() 45 | { 46 | return [ 47 | 'iblockId' => $this->fields['IBLOCK_ID'], 48 | 'code' => "'".$this->fields['CODE']."'", 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockPropertyUpdate.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 28 | 29 | $this->dbFields = $this->collectPropertyFieldsFromDB(); 30 | 31 | if (!$this->propertyHasChanged() || !$this->fields['IBLOCK_ID']) { 32 | throw new SkipHandlerException(); 33 | } 34 | } 35 | 36 | /** 37 | * Get migration name. 38 | * 39 | * @return string 40 | */ 41 | public function getName() 42 | { 43 | return "auto_update_iblock_element_property_{$this->fields['CODE']}_in_ib_{$this->fields['IBLOCK_ID']}"; 44 | } 45 | 46 | /** 47 | * Get template name. 48 | * 49 | * @return string 50 | */ 51 | public function getTemplate() 52 | { 53 | return 'auto_update_iblock_element_property'; 54 | } 55 | 56 | /** 57 | * Get array of placeholders to replace. 58 | * 59 | * @return array 60 | */ 61 | public function getReplace() 62 | { 63 | return [ 64 | 'fields' => var_export($this->fields, true), 65 | 'iblockId' => $this->fields['IBLOCK_ID'], 66 | 'code' => "'".$this->fields['CODE']."'", 67 | ]; 68 | } 69 | 70 | /** 71 | * Collect property fields from DB and convert them to format that can be compared from user input. 72 | * 73 | * @return array 74 | */ 75 | protected function collectPropertyFieldsFromDB() 76 | { 77 | $fields = CIBlockProperty::getByID($this->fields['ID'])->fetch(); 78 | $fields['VALUES'] = []; 79 | 80 | $filter = [ 81 | 'IBLOCK_ID' => $this->fields['IBLOCK_ID'], 82 | 'PROPERTY_ID' => $this->fields['ID'], 83 | ]; 84 | $sort = [ 85 | 'SORT' => 'ASC', 86 | 'VALUE' => 'ASC', 87 | ]; 88 | 89 | $propertyEnums = CIBlockPropertyEnum::GetList($sort, $filter); 90 | while ($v = $propertyEnums->GetNext()) { 91 | $fields['VALUES'][$v['ID']] = [ 92 | 'ID' => $v['ID'], 93 | 'VALUE' => $v['VALUE'], 94 | 'SORT' => $v['SORT'], 95 | 'XML_ID' => $v['XML_ID'], 96 | 'DEF' => $v['DEF'], 97 | ]; 98 | } 99 | 100 | return $fields; 101 | } 102 | 103 | /** 104 | * Determine if property has been changed. 105 | * 106 | * @return bool 107 | */ 108 | protected function propertyHasChanged() 109 | { 110 | foreach ($this->dbFields as $field => $value) { 111 | if (isset($this->fields[$field]) && ($this->fields[$field] != $value)) { 112 | return true; 113 | } 114 | } 115 | 116 | return false; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeIBlockUpdate.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 18 | 19 | // Если кода нет то миграция создастся битая. 20 | // Еще это позволяет решить проблему с тем что создается лишняя миграция для торгового каталога 21 | // когда обновляют связанный с ним инфоблок. 22 | if (!$this->fields['CODE']) { 23 | throw new SkipHandlerException(); 24 | } 25 | } 26 | 27 | /** 28 | * Get migration name. 29 | * 30 | * @return string 31 | */ 32 | public function getName() 33 | { 34 | return "auto_update_iblock_{$this->fields['CODE']}"; 35 | } 36 | 37 | /** 38 | * Get template name. 39 | * 40 | * @return string 41 | */ 42 | public function getTemplate() 43 | { 44 | return 'auto_update_iblock'; 45 | } 46 | 47 | /** 48 | * Get array of placeholders to replace. 49 | * 50 | * @return array 51 | */ 52 | public function getReplace() 53 | { 54 | return [ 55 | 'fields' => var_export($this->fields, true), 56 | 'code' => "'".$this->fields['CODE']."'", 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeUserTypeAdd.php: -------------------------------------------------------------------------------- 1 | fields = $params[0]; 15 | } 16 | 17 | /** 18 | * Get migration name. 19 | * 20 | * @return string 21 | */ 22 | public function getName() 23 | { 24 | return "auto_add_uf_{$this->fields['FIELD_NAME']}_to_entity_{$this->fields['ENTITY_ID']}"; 25 | } 26 | 27 | /** 28 | * Get template name. 29 | * 30 | * @return string 31 | */ 32 | public function getTemplate() 33 | { 34 | return 'auto_add_uf'; 35 | } 36 | 37 | /** 38 | * Get array of placeholders to replace. 39 | * 40 | * @return array 41 | */ 42 | public function getReplace() 43 | { 44 | return [ 45 | 'fields' => var_export($this->fields, true), 46 | 'code' => "'".$this->fields['FIELD_NAME']."'", 47 | 'entity' => "'".$this->fields['ENTITY_ID']."'", 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Autocreate/Handlers/OnBeforeUserTypeDelete.php: -------------------------------------------------------------------------------- 1 | fields = is_array($params[0]) ? $params[0] : CUserTypeEntity::getByID($params[0]); 17 | } 18 | 19 | /** 20 | * Get migration name. 21 | * 22 | * @return string 23 | */ 24 | public function getName() 25 | { 26 | return "auto_delete_uf_{$this->fields['FIELD_NAME']}_from_entity_{$this->fields['ENTITY_ID']}"; 27 | } 28 | 29 | /** 30 | * Get template name. 31 | * 32 | * @return string 33 | */ 34 | public function getTemplate() 35 | { 36 | return 'auto_delete_uf'; 37 | } 38 | 39 | /** 40 | * Get array of placeholders to replace. 41 | * 42 | * @return array 43 | */ 44 | public function getReplace() 45 | { 46 | return [ 47 | 'iblockId' => $this->fields['IBLOCK_ID'], 48 | 'code' => "'".$this->fields['FIELD_NAME']."'", 49 | 'entity' => "'".$this->fields['ENTITY_ID']."'", 50 | 'fields' => var_export($this->fields, true), 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Autocreate/Manager.php: -------------------------------------------------------------------------------- 1 | [ 34 | 'OnBeforeIBlockAdd' => 'OnBeforeIBlockAdd', 35 | 'OnBeforeIBlockUpdate' => 'OnBeforeIBlockUpdate', 36 | 'OnBeforeIBlockDelete' => 'OnBeforeIBlockDelete', 37 | 'OnBeforeIBlockPropertyAdd' => 'OnBeforeIBlockPropertyAdd', 38 | 'OnBeforeIBlockPropertyUpdate' => 'OnBeforeIBlockPropertyUpdate', 39 | 'OnBeforeIBlockPropertyDelete' => 'OnBeforeIBlockPropertyDelete', 40 | ], 41 | 'main' => [ 42 | 'OnBeforeUserTypeAdd' => 'OnBeforeUserTypeAdd', 43 | 'OnBeforeUserTypeDelete' => 'OnBeforeUserTypeDelete', 44 | 'OnBeforeGroupAdd' => 'OnBeforeGroupAdd', 45 | 'OnBeforeGroupUpdate' => 'OnBeforeGroupUpdate', 46 | 'OnBeforeGroupDelete' => 'OnBeforeGroupDelete', 47 | ], 48 | 'highloadblock' => [ 49 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeAdd' => 'OnBeforeHLBlockAdd', 50 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeUpdate' => 'OnBeforeHLBlockUpdate', 51 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeDelete' => 'OnBeforeHLBlockDelete', 52 | ], 53 | ]; 54 | 55 | /** 56 | * Initialize autocreation. 57 | * 58 | * @param string $dir 59 | * @param string|null $table 60 | */ 61 | public static function init($dir, $table = null) 62 | { 63 | $templates = new TemplatesCollection(); 64 | $templates->registerAutoTemplates(); 65 | 66 | $config = [ 67 | 'dir' => $dir, 68 | 'table' => is_null($table) ? 'migrations' : $table, 69 | ]; 70 | 71 | static::$migrator = new Migrator($config, $templates); 72 | 73 | static::addEventHandlers(); 74 | 75 | static::turnOn(); 76 | } 77 | 78 | /** 79 | * Determine if autocreation is turned on. 80 | * 81 | * @return bool 82 | */ 83 | public static function isTurnedOn() 84 | { 85 | return static::$isTurnedOn && defined('ADMIN_SECTION'); 86 | } 87 | 88 | /** 89 | * Turn on autocreation. 90 | * 91 | * @return void 92 | */ 93 | public static function turnOn() 94 | { 95 | static::$isTurnedOn = true; 96 | } 97 | 98 | /** 99 | * Turn off autocreation. 100 | * 101 | * @return void 102 | */ 103 | public static function turnOff() 104 | { 105 | static::$isTurnedOn = false; 106 | } 107 | 108 | /** 109 | * Instantiate handler. 110 | * 111 | * @param string $handler 112 | * @param array $parameters 113 | * 114 | * @return mixed 115 | */ 116 | protected static function instantiateHandler($handler, $parameters) 117 | { 118 | $class = __NAMESPACE__.'\\Handlers\\'.$handler; 119 | 120 | return new $class($parameters); 121 | } 122 | 123 | /** 124 | * Create migration and apply it. 125 | * 126 | * @param HandlerInterface $handler 127 | */ 128 | protected static function createMigration(HandlerInterface $handler) 129 | { 130 | $migrator = static::$migrator; 131 | $notifier = new Notifier(); 132 | 133 | $migration = $migrator->createMigration( 134 | strtolower($handler->getName()), 135 | $handler->getTemplate(), 136 | $handler->getReplace() 137 | ); 138 | 139 | $migrator->logSuccessfulMigration($migration); 140 | $notifier->newMigration($migration); 141 | } 142 | 143 | /** 144 | * Add event handlers. 145 | */ 146 | protected static function addEventHandlers() 147 | { 148 | $eventManager = EventManager::getInstance(); 149 | 150 | foreach (static::$handlers as $module => $handlers) { 151 | foreach ($handlers as $event => $handler) { 152 | $eventManager->addEventHandler($module, $event, [__CLASS__, $handler], false, 5000); 153 | } 154 | } 155 | 156 | $eventManager->addEventHandler('main', 'OnAfterEpilog', function () { 157 | $notifier = new Notifier(); 158 | $notifier->deleteNotificationFromPreviousMigration(); 159 | 160 | return new EventResult(); 161 | }); 162 | } 163 | 164 | /** 165 | * Magic static call to a handler. 166 | * 167 | * @param string $method 168 | * @param array $parameters 169 | * 170 | * @return mixed 171 | */ 172 | public static function __callStatic($method, $parameters) 173 | { 174 | $eventResult = new EventResult(); 175 | 176 | if (!static::isTurnedOn()) { 177 | return $eventResult; 178 | } 179 | 180 | try { 181 | $handler = static::instantiateHandler($method, $parameters); 182 | } catch (SkipHandlerException $e) { 183 | return $eventResult; 184 | } catch (StopHandlerException $e) { 185 | global $APPLICATION; 186 | $APPLICATION->throwException($e->getMessage()); 187 | 188 | return false; 189 | } 190 | 191 | static::createMigration($handler); 192 | 193 | return $eventResult; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Autocreate/Notifier.php: -------------------------------------------------------------------------------- 1 | 'Migration '.$migration.' has been created and applied.', 25 | 'TAG' => $this->tag, 26 | 'MODULE_ID' => 'main', 27 | 'ENABLE_CLOSE' => 'Y', 28 | ]; 29 | 30 | CAdminNotify::add($notification); 31 | } 32 | 33 | /** 34 | * Delete notification from the previous migration. 35 | */ 36 | public function deleteNotificationFromPreviousMigration() 37 | { 38 | if (defined('ADMIN_SECTION')) { 39 | CAdminNotify::deleteByTag($this->tag); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/BaseMigrations/BitrixMigration.php: -------------------------------------------------------------------------------- 1 | db = Application::getConnection(); 33 | } 34 | 35 | /** 36 | * Run the migration. 37 | * 38 | * @return mixed 39 | */ 40 | public function up() 41 | { 42 | // 43 | } 44 | 45 | /** 46 | * Reverse the migration. 47 | * 48 | * @return mixed 49 | */ 50 | public function down() 51 | { 52 | // 53 | } 54 | 55 | /** 56 | * Does migration use transaction 57 | * @param bool $default 58 | * @return bool 59 | */ 60 | public function useTransaction($default = false) 61 | { 62 | if (!is_null($this->use_transaction)) { 63 | return $this->use_transaction; 64 | } 65 | 66 | return $default; 67 | } 68 | 69 | /** 70 | * Find iblock id by its code. 71 | * 72 | * @param string $code 73 | * @param null|string $iBlockType 74 | * 75 | * @throws MigrationException 76 | * 77 | * @return int 78 | */ 79 | protected function getIblockIdByCode($code, $iBlockType = null) 80 | { 81 | if (!$code) { 82 | throw new MigrationException('Не задан код инфоблока'); 83 | } 84 | 85 | $filter = [ 86 | 'CODE' => $code, 87 | 'CHECK_PERMISSIONS' => 'N', 88 | ]; 89 | 90 | if ($iBlockType !== null) { 91 | $filter['TYPE'] = $iBlockType; 92 | } 93 | 94 | $iblock = (new CIBlock())->GetList([], $filter)->fetch(); 95 | 96 | if (!$iblock['ID']) { 97 | throw new MigrationException("Не удалось найти инфоблок с кодом '{$code}'"); 98 | } 99 | 100 | return $iblock['ID']; 101 | } 102 | 103 | /** 104 | * Delete iblock by its code. 105 | * 106 | * @param string $code 107 | * 108 | * @throws MigrationException 109 | * 110 | * @return void 111 | */ 112 | protected function deleteIblockByCode($code) 113 | { 114 | $id = $this->getIblockIdByCode($code); 115 | 116 | $this->db->startTransaction(); 117 | if (!CIBlock::Delete($id)) { 118 | $this->db->rollbackTransaction(); 119 | throw new MigrationException('Ошибка при удалении инфоблока'); 120 | } 121 | 122 | $this->db->commitTransaction(); 123 | } 124 | 125 | /** 126 | * Add iblock element property. 127 | * 128 | * @param array $fields 129 | * 130 | * @throws MigrationException 131 | * 132 | * @return int 133 | */ 134 | public function addIblockElementProperty($fields) 135 | { 136 | $ibp = new CIBlockProperty(); 137 | $propId = $ibp->add($fields); 138 | 139 | if (!$propId) { 140 | throw new MigrationException('Ошибка при добавлении свойства инфоблока '.$ibp->LAST_ERROR); 141 | } 142 | 143 | return $propId; 144 | } 145 | 146 | /** 147 | * Delete iblock element property. 148 | * 149 | * @param string $code 150 | * @param string|int $iblockId 151 | * 152 | * @throws MigrationException 153 | */ 154 | public function deleteIblockElementPropertyByCode($iblockId, $code) 155 | { 156 | if (!$iblockId) { 157 | throw new MigrationException('Не задан ID инфоблока'); 158 | } 159 | 160 | if (!$code) { 161 | throw new MigrationException('Не задан код свойства'); 162 | } 163 | 164 | $id = $this->getIblockPropIdByCode($code, $iblockId); 165 | 166 | CIBlockProperty::Delete($id); 167 | } 168 | 169 | /** 170 | * Add User Field. 171 | * 172 | * @param $fields 173 | * 174 | * @throws MigrationException 175 | * 176 | * @return int 177 | */ 178 | public function addUF($fields) 179 | { 180 | if (!$fields['FIELD_NAME']) { 181 | throw new MigrationException('Не заполнен FIELD_NAME'); 182 | } 183 | 184 | if (!$fields['ENTITY_ID']) { 185 | throw new MigrationException('Не заполнен код ENTITY_ID'); 186 | } 187 | 188 | $oUserTypeEntity = new CUserTypeEntity(); 189 | 190 | $fieldId = $oUserTypeEntity->Add($fields); 191 | 192 | if (!$fieldId) { 193 | throw new MigrationException("Не удалось создать пользовательское свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}"); 194 | } 195 | 196 | return $fieldId; 197 | } 198 | 199 | /** 200 | * Get UF by its code. 201 | * 202 | * @param string $entity 203 | * @param string $code 204 | * 205 | * @throws MigrationException 206 | */ 207 | public function getUFIdByCode($entity, $code) 208 | { 209 | if (!$entity) { 210 | throw new MigrationException('Не задана сущность свойства'); 211 | } 212 | 213 | if (!$code) { 214 | throw new MigrationException('Не задан код свойства'); 215 | } 216 | 217 | $filter = [ 218 | 'ENTITY_ID' => $entity, 219 | 'FIELD_NAME' => $code, 220 | ]; 221 | 222 | $arField = CUserTypeEntity::GetList(['ID' => 'ASC'], $filter)->fetch(); 223 | if (!$arField || !$arField['ID']) { 224 | throw new MigrationException("Не найдено свойство с FIELD_NAME = {$filter['FIELD_NAME']} и ENTITY_ID = {$filter['ENTITY_ID']}"); 225 | } 226 | 227 | return $arField['ID']; 228 | } 229 | 230 | /** 231 | * @param $code 232 | * @param $iblockId 233 | * 234 | * @throws MigrationException 235 | * 236 | * @return array 237 | */ 238 | protected function getIblockPropIdByCode($code, $iblockId) 239 | { 240 | $filter = [ 241 | 'CODE' => $code, 242 | 'IBLOCK_ID' => $iblockId, 243 | ]; 244 | 245 | $prop = CIBlockProperty::getList(['sort' => 'asc', 'name' => 'asc'], $filter)->getNext(); 246 | if (!$prop || !$prop['ID']) { 247 | throw new MigrationException("Не удалось найти свойство с кодом '{$code}'"); 248 | } 249 | 250 | return $prop['ID']; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/Commands/AbstractCommand.php: -------------------------------------------------------------------------------- 1 | error($message); 32 | } 33 | 34 | $this->error('Abort!'); 35 | 36 | throw new DomainException(); 37 | } 38 | 39 | /** 40 | * Executes the current command. 41 | * 42 | * @param InputInterface $input An InputInterface instance 43 | * @param OutputInterface $output An OutputInterface instance 44 | * 45 | * @return null|int null or 0 if everything went fine, or an error code. 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $this->input = $input; 50 | $this->output = $output; 51 | 52 | try { 53 | return $this->fire(); 54 | } catch (DomainException $e) { 55 | return 1; 56 | } catch (Exception $e) { 57 | $this->error($e->getMessage()); 58 | $this->error('Abort!'); 59 | 60 | return $e->getCode(); 61 | } 62 | } 63 | 64 | /** 65 | * Echo an error message. 66 | * 67 | * @param string$message 68 | */ 69 | protected function error($message) 70 | { 71 | $this->output->writeln("{$message}"); 72 | } 73 | 74 | /** 75 | * Echo an info. 76 | * 77 | * @param string $message 78 | */ 79 | protected function info($message) 80 | { 81 | $this->output->writeln("{$message}"); 82 | } 83 | 84 | /** 85 | * Echo a message. 86 | * 87 | * @param string $message 88 | */ 89 | protected function message($message) 90 | { 91 | $this->output->writeln("{$message}"); 92 | } 93 | 94 | /** 95 | * Execute the console command. 96 | * 97 | * @return null|int 98 | */ 99 | abstract protected function fire(); 100 | } 101 | -------------------------------------------------------------------------------- /src/Commands/ArchiveCommand.php: -------------------------------------------------------------------------------- 1 | migrator = $migrator; 27 | 28 | parent::__construct($name); 29 | } 30 | 31 | /** 32 | * Configures the current command. 33 | */ 34 | protected function configure() 35 | { 36 | $this->setDescription('Move migration into archive') 37 | ->addOption('without', 'w', InputOption::VALUE_REQUIRED, 'Archive without last N migration'); 38 | } 39 | 40 | /** 41 | * Execute the console command. 42 | * 43 | * @return null|int 44 | */ 45 | protected function fire() 46 | { 47 | $files = $this->migrator->getAllMigrations(); 48 | $without = $this->input->getOption('without') ?: 0; 49 | if ($without > 0) { 50 | $files = array_slice($files, 0, $without * -1); 51 | } 52 | 53 | $count = $this->migrator->moveMigrationFiles($files); 54 | 55 | if ($count) { 56 | $this->message("Moved to archive: {$count}"); 57 | } else { 58 | $this->info('Nothing to move'); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | table = $table; 35 | $this->database = $database; 36 | 37 | parent::__construct($name); 38 | } 39 | 40 | /** 41 | * Configures the current command. 42 | */ 43 | protected function configure() 44 | { 45 | $this->setDescription('Create the migration database table'); 46 | } 47 | 48 | /** 49 | * Execute the console command. 50 | * 51 | * @return null|int 52 | */ 53 | protected function fire() 54 | { 55 | if ($this->database->checkMigrationTableExistence()) { 56 | $this->abort("Table \"{$this->table}\" already exists"); 57 | } 58 | 59 | $this->database->createMigrationTable(); 60 | 61 | $this->info('Migration table has been successfully created!'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Commands/MakeCommand.php: -------------------------------------------------------------------------------- 1 | migrator = $migrator; 29 | 30 | parent::__construct($name); 31 | } 32 | 33 | /** 34 | * Configures the current command. 35 | */ 36 | protected function configure() 37 | { 38 | $this->setDescription('Create a new migration file') 39 | ->addArgument( 40 | 'name', 41 | InputArgument::REQUIRED, 42 | 'The name of the migration' 43 | ) 44 | ->addOption( 45 | 'template', 46 | 't', 47 | InputOption::VALUE_REQUIRED, 48 | 'Migration template' 49 | ) 50 | ->addOption( 51 | 'directory', 52 | 'd', 53 | InputOption::VALUE_REQUIRED, 54 | 'Migration directory' 55 | ); 56 | } 57 | 58 | /** 59 | * Execute the console command. 60 | * 61 | * @return null|int 62 | */ 63 | protected function fire() 64 | { 65 | $migration = $this->migrator->createMigration( 66 | $this->input->getArgument('name'), 67 | $this->input->getOption('template'), 68 | [], 69 | $this->input->getOption('directory') 70 | ); 71 | 72 | $this->message("Migration created: {$migration}.php"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Commands/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | migrator = $migrator; 26 | 27 | parent::__construct($name); 28 | } 29 | 30 | /** 31 | * Configures the current command. 32 | */ 33 | protected function configure() 34 | { 35 | $this->setDescription('Run all outstanding migrations'); 36 | } 37 | 38 | /** 39 | * Execute the console command. 40 | * 41 | * @return null|int 42 | */ 43 | protected function fire() 44 | { 45 | $toRun = $this->migrator->getMigrationsToRun(); 46 | 47 | if (!empty($toRun)) { 48 | foreach ($toRun as $migration) { 49 | $this->migrator->runMigration($migration); 50 | $this->message("Migrated: {$migration}.php"); 51 | } 52 | } else { 53 | $this->info('Nothing to migrate'); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Commands/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | migrator = $migrator; 29 | 30 | parent::__construct($name); 31 | } 32 | 33 | /** 34 | * Configures the current command. 35 | */ 36 | protected function configure() 37 | { 38 | $this->setDescription('Rollback the last migration') 39 | ->addOption('hard', null, InputOption::VALUE_NONE, 'Rollback without running down()') 40 | ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete migration file after rolling back'); 41 | } 42 | 43 | /** 44 | * Execute the console command. 45 | * 46 | * @return null|int 47 | */ 48 | protected function fire() 49 | { 50 | $ran = $this->migrator->getRanMigrations(); 51 | 52 | if (empty($ran)) { 53 | return $this->info('Nothing to rollback'); 54 | } 55 | 56 | $migration = $ran[count($ran) - 1]; 57 | 58 | $this->input->getOption('hard') 59 | ? $this->hardRollbackMigration($migration) 60 | : $this->rollbackMigration($migration); 61 | 62 | return $this->deleteIfNeeded($migration); 63 | } 64 | 65 | /** 66 | * Call rollback. 67 | * 68 | * @param $migration 69 | * 70 | * @return null 71 | */ 72 | protected function rollbackMigration($migration) 73 | { 74 | if ($this->migrator->doesMigrationFileExist($migration)) { 75 | $this->migrator->rollbackMigration($migration); 76 | } else { 77 | $this->markRolledBackWithConfirmation($migration); 78 | } 79 | 80 | $this->message("Rolled back: {$migration}.php"); 81 | } 82 | 83 | /** 84 | * Call hard rollback. 85 | * 86 | * @param $migration 87 | * 88 | * @return null 89 | */ 90 | protected function hardRollbackMigration($migration) 91 | { 92 | $this->migrator->removeSuccessfulMigrationFromLog($migration); 93 | 94 | $this->message("Rolled back with --hard: {$migration}.php"); 95 | } 96 | 97 | /** 98 | * Ask a user to confirm rolling back non-existing migration and remove it from log. 99 | * 100 | * @param $migration 101 | * 102 | * @return void 103 | */ 104 | protected function markRolledBackWithConfirmation($migration) 105 | { 106 | $helper = $this->getHelper('question'); 107 | $question = new ConfirmationQuestion("Migration $migration was not found.\r\nDo you want to mark it as rolled back? (y/n)\r\n", false); 108 | 109 | if (!$helper->ask($this->input, $this->output, $question)) { 110 | $this->abort(); 111 | } 112 | 113 | $this->migrator->removeSuccessfulMigrationFromLog($migration); 114 | } 115 | 116 | /** 117 | * Delete migration file if options is set 118 | * 119 | * @param string $migration 120 | * 121 | * @return null 122 | */ 123 | protected function deleteIfNeeded($migration) 124 | { 125 | if (!$this->input->getOption('delete')) { 126 | return; 127 | } 128 | 129 | if ($this->migrator->deleteMigrationFile($migration)) { 130 | $this->message("Deleted: {$migration}.php"); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Commands/StatusCommand.php: -------------------------------------------------------------------------------- 1 | migrator = $migrator; 27 | 28 | parent::__construct($name); 29 | } 30 | 31 | /** 32 | * Configures the current command. 33 | */ 34 | protected function configure() 35 | { 36 | $this->setDescription('Show status about last migrations'); 37 | } 38 | 39 | /** 40 | * Execute the console command. 41 | * 42 | * @return null|int 43 | */ 44 | protected function fire() 45 | { 46 | $this->showOldMigrations(); 47 | 48 | $this->output->write("\r\n"); 49 | 50 | $this->showNewMigrations(); 51 | } 52 | 53 | /** 54 | * Show old migrations. 55 | * 56 | * @return void 57 | */ 58 | protected function showOldMigrations() 59 | { 60 | $old = collect($this->migrator->getRanMigrations()); 61 | 62 | $this->output->writeln("Old migrations:\r\n"); 63 | 64 | $max = 5; 65 | if ($old->count() > $max) { 66 | $this->output->writeln('...'); 67 | 68 | $old = $old->take(-$max); 69 | } 70 | 71 | foreach ($old as $migration) { 72 | $this->output->writeln("{$migration}.php"); 73 | } 74 | } 75 | 76 | /** 77 | * Show new migrations. 78 | * 79 | * @return void 80 | */ 81 | protected function showNewMigrations() 82 | { 83 | $new = collect($this->migrator->getMigrationsToRun()); 84 | 85 | $this->output->writeln("New migrations:\r\n"); 86 | 87 | foreach ($new as $migration) { 88 | $this->output->writeln("{$migration}.php"); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Commands/TemplatesCommand.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 29 | 30 | parent::__construct($name); 31 | } 32 | 33 | /** 34 | * Configures the current command. 35 | */ 36 | protected function configure() 37 | { 38 | $this->setDescription('Show the list of available migration templates'); 39 | } 40 | 41 | /** 42 | * Execute the console command. 43 | * 44 | * @return null|int 45 | */ 46 | protected function fire() 47 | { 48 | $table = new Table($this->output); 49 | $table->setHeaders(['Name', 'Path', 'Description'])->setRows($this->collectRows()); 50 | $table->setStyle('borderless'); 51 | $table->render(); 52 | } 53 | 54 | /** 55 | * Collect and return templates from a Migrator. 56 | * 57 | * @return array 58 | */ 59 | protected function collectRows() 60 | { 61 | $rows = collect($this->collection->all()) 62 | ->filter(function ($template) { 63 | return $template['is_alias'] == false; 64 | }) 65 | ->sortBy('name') 66 | ->map(function ($template) { 67 | $row = []; 68 | 69 | $names = array_merge([$template['name']], $template['aliases']); 70 | $row[] = implode("\n/ ", $names); 71 | $row[] = wordwrap($template['path'], 65, "\n", true); 72 | $row[] = wordwrap($template['description'], 25, "\n", true); 73 | 74 | return $row; 75 | }); 76 | 77 | return $this->separateRows($rows); 78 | } 79 | 80 | /** 81 | * Separate rows with a separator. 82 | * 83 | * @param $templates 84 | * 85 | * @return array 86 | */ 87 | protected function separateRows($templates) 88 | { 89 | $rows = []; 90 | foreach ($templates as $template) { 91 | $rows[] = $template; 92 | $rows[] = new TableSeparator(); 93 | } 94 | unset($rows[count($rows) - 1]); 95 | 96 | return $rows; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Constructors/Constructor.php: -------------------------------------------------------------------------------- 1 | fields); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Constructors/HighloadBlock.php: -------------------------------------------------------------------------------- 1 | getFieldsWithDefault()); 25 | 26 | if (!$result->isSuccess()) { 27 | throw new \Exception(join(', ', $result->getErrorMessages())); 28 | } 29 | 30 | foreach ($this->lang as $lid => $name) { 31 | HighloadBlockLangTable::add([ 32 | "ID" => $result->getId(), 33 | "LID" => $lid, 34 | "NAME" => $name 35 | ]); 36 | } 37 | 38 | Logger::log("Добавлен HL {$this->fields['NAME']}", Logger::COLOR_GREEN); 39 | 40 | return $result->getId(); 41 | } 42 | 43 | /** 44 | * Обновить HL 45 | * @param $table_name 46 | * @throws \Exception 47 | */ 48 | public function update($table_name) 49 | { 50 | $id = Helpers::getHlId($table_name); 51 | $result = HighloadBlockTable::update($id, $this->fields); 52 | 53 | if (!$result->isSuccess()) { 54 | throw new \Exception(join(', ', $result->getErrorMessages())); 55 | } 56 | 57 | Logger::log("Обновлен HL {$table_name}", Logger::COLOR_GREEN); 58 | } 59 | 60 | /** 61 | * Удалить HL 62 | * @param $table_name 63 | * @throws \Exception 64 | */ 65 | public static function delete($table_name) 66 | { 67 | $id = Helpers::getHlId($table_name); 68 | $result = HighloadBlockTable::delete($id); 69 | 70 | if (!$result->isSuccess()) { 71 | throw new \Exception(join(', ', $result->getErrorMessages())); 72 | } 73 | 74 | Logger::log("Удален HL {$table_name}", Logger::COLOR_GREEN); 75 | } 76 | 77 | /** 78 | * Установить настройки для добавления HL по умолчанию 79 | * @param string $name Название highload-блока 80 | * @param string $table_name Название таблицы с элементами highload-блока. 81 | * @return $this 82 | */ 83 | public function constructDefault($name, $table_name) 84 | { 85 | return $this->setName($name)->setTableName($table_name); 86 | } 87 | 88 | /** 89 | * Название highload-блока. 90 | * @param string $name 91 | * @return $this 92 | */ 93 | public function setName($name) 94 | { 95 | $this->fields['NAME'] = $name; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Название таблицы с элементами highload-блока. 102 | * @param string $table_name 103 | * @return $this 104 | */ 105 | public function setTableName($table_name) 106 | { 107 | $this->fields['TABLE_NAME'] = $table_name; 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * Установить локализацию 114 | * @param $lang 115 | * @param $text 116 | * @return HighloadBlock 117 | */ 118 | public function setLang($lang, $text) 119 | { 120 | $this->lang[$lang] = $text; 121 | 122 | return $this; 123 | } 124 | } -------------------------------------------------------------------------------- /src/Constructors/IBlock.php: -------------------------------------------------------------------------------- 1 | Add($this->getFieldsWithDefault()); 23 | if (!$iblockId) { 24 | throw new \Exception($obj->LAST_ERROR); 25 | } 26 | 27 | Logger::log("Добавлен инфоблок {$this->fields['CODE']}", Logger::COLOR_GREEN); 28 | 29 | return $iblockId; 30 | } 31 | 32 | /** 33 | * Обновить инфоблок 34 | * @param $id 35 | * @throws \Exception 36 | */ 37 | public function update($id) 38 | { 39 | $obj = new \CIBlock(); 40 | if (!$obj->Update($id, $this->fields)) { 41 | throw new \Exception($obj->LAST_ERROR); 42 | } 43 | 44 | Logger::log("Обновлен инфоблок {$id}", Logger::COLOR_GREEN); 45 | } 46 | 47 | /** 48 | * Удалить инфоблок 49 | * @param $id 50 | * @throws \Exception 51 | */ 52 | public static function delete($id) 53 | { 54 | if (!\CIBlock::Delete($id)) { 55 | throw new \Exception('Ошибка при удалении инфоблока'); 56 | } 57 | 58 | Logger::log("Удален инфоблок {$id}", Logger::COLOR_GREEN); 59 | } 60 | 61 | /** 62 | * Установить настройки для добавления инфоблока по умолчанию 63 | * @param $name 64 | * @param $code 65 | * @param $iblock_type_id 66 | * @return $this 67 | */ 68 | public function constructDefault($name, $code, $iblock_type_id) 69 | { 70 | return $this->setName($name)->setCode($code)->setIblockTypeId($iblock_type_id); 71 | } 72 | 73 | /** 74 | * ID сайта. 75 | * @param string $siteId 76 | * @return $this 77 | */ 78 | public function setSiteId($siteId) 79 | { 80 | $this->fields['SITE_ID'] = $siteId; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Символьный идентификатор. 87 | * @param string $code 88 | * @return $this 89 | */ 90 | public function setCode($code) 91 | { 92 | $this->fields['CODE'] = $code; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * Внешний код. 99 | * @param string $xml_id 100 | * @return $this 101 | */ 102 | public function setXmlId($xml_id) 103 | { 104 | $this->fields['XML_ID'] = $xml_id; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * Код типа инфоблока 111 | * @param string $iblockTypeId 112 | * @return $this 113 | */ 114 | public function setIblockTypeId($iblockTypeId) 115 | { 116 | $this->fields['IBLOCK_TYPE_ID'] = $iblockTypeId; 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * Название. 123 | * @param string $name 124 | * @return $this 125 | */ 126 | public function setName($name) 127 | { 128 | $this->fields['NAME'] = $name; 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Флаг активности 135 | * @param bool $active 136 | * @return $this 137 | */ 138 | public function setActive($active = true) 139 | { 140 | $this->fields['ACTIVE'] = $active ? 'Y' : 'N'; 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Индекс сортировки. 147 | * @param int $sort 148 | * @return $this 149 | */ 150 | public function setSort($sort = 500) 151 | { 152 | $this->fields['SORT'] = $sort; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * Шаблон URL-а к странице для публичного просмотра списка элементов информационного блока. 159 | * @param string $listPageUrl 160 | * @return $this 161 | */ 162 | public function setListPageUrl($listPageUrl) 163 | { 164 | $this->fields['LIST_PAGE_URL'] = $listPageUrl; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Шаблон URL-а к странице для просмотра раздела. 171 | * @param string $sectionPageUrl 172 | * @return $this 173 | */ 174 | public function setSectionPageUrl($sectionPageUrl) 175 | { 176 | $this->fields['SECTION_PAGE_URL'] = $sectionPageUrl; 177 | 178 | return $this; 179 | } 180 | 181 | /** 182 | * Канонический URL элемента. 183 | * @param string $canonicalPageUrl 184 | * @return $this 185 | */ 186 | public function setCanonicalPageUrl($canonicalPageUrl) 187 | { 188 | $this->fields['CANONICAL_PAGE_URL'] = $canonicalPageUrl; 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * URL детальной страницы элемента. 195 | * 196 | * @param string $detailPageUrl 197 | * 198 | * @return $this 199 | */ 200 | public function setDetailPageUrl($detailPageUrl) 201 | { 202 | $this->fields['DETAIL_PAGE_URL'] = $detailPageUrl; 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * Устанавливает значения по умолчанию для страниц инфоблока, раздела и деталей элемента 209 | * (как при создании через административный интерфейс или с ЧПУ). 210 | * 211 | * Для использовании ЧПУ рекомендуется сделать обязательными для заполнения символьный код 212 | * элементов и разделов инфоблока. 213 | * 214 | * @param bool sef Использовать ли ЧПУ (понадобится добавить правило в urlrewrite) 215 | * 216 | * @return IBlock 217 | */ 218 | public function setDefaultUrls($sef = false) 219 | { 220 | if ($sef === true) { 221 | $prefix = "#SITE_DIR#/#IBLOCK_TYPE_ID#/#IBLOCK_CODE#/"; 222 | $this 223 | ->setListPageUrl($prefix) 224 | ->setSectionPageUrl("$prefix#SECTION_CODE_PATH#/") 225 | ->setDetailPageUrl("$prefix#SECTION_CODE_PATH#/#ELEMENT_CODE#/"); 226 | } else { 227 | $prefix = "#SITE_DIR#/#IBLOCK_TYPE_ID#"; 228 | $this 229 | ->setListPageUrl("$prefix/index.php?ID=#IBLOCK_ID#") 230 | ->setSectionPageUrl("$prefix/list.php?SECTION_ID=#SECTION_ID#") 231 | ->setDetailPageUrl("$prefix/detail.php?ID=#ELEMENT_ID#"); 232 | } 233 | 234 | return $this; 235 | } 236 | 237 | /** 238 | * Код картинки в таблице файлов. 239 | * @param array $picture 240 | * @return $this 241 | */ 242 | public function setPicture($picture) 243 | { 244 | $this->fields['PICTURE'] = $picture; 245 | 246 | return $this; 247 | } 248 | 249 | /** 250 | * Описание. 251 | * @param string $description 252 | * @return $this 253 | */ 254 | public function setDescription($description) 255 | { 256 | $this->fields['DESCRIPTION'] = $description; 257 | 258 | return $this; 259 | } 260 | 261 | /** 262 | * Тип описания (text/html) 263 | * @param string $descriptionType 264 | * @return $this 265 | */ 266 | public function setDescriptionType($descriptionType = 'text') 267 | { 268 | $this->fields['DESCRIPTION_TYPE'] = $descriptionType; 269 | 270 | return $this; 271 | } 272 | 273 | /** 274 | * Разрешен экспорт в RSS динамически 275 | * @param bool $rssActive 276 | * @return $this 277 | */ 278 | public function setRssActive($rssActive = true) 279 | { 280 | $this->fields['RSS_ACTIVE'] = $rssActive ? 'Y' : 'N'; 281 | 282 | return $this; 283 | } 284 | 285 | /** 286 | * Время жизни RSS и интервал между генерациями файлов RSS (при включенном RSS_FILE_ACTIVE или RSS_YANDEX_ACTIVE) (часов). 287 | * @param int $rssTtl 288 | * @return $this 289 | */ 290 | public function setRssTtl($rssTtl = 24) 291 | { 292 | $this->fields['RSS_TTL'] = $rssTtl; 293 | 294 | return $this; 295 | } 296 | 297 | /** 298 | * Прегенерировать выгрузку в файл. 299 | * @param bool $rssFileActive 300 | * @return $this 301 | */ 302 | public function setRssFileActive($rssFileActive = false) 303 | { 304 | $this->fields['RSS_FILE_ACTIVE'] = $rssFileActive ? 'Y' : 'N'; 305 | 306 | return $this; 307 | } 308 | 309 | /** 310 | * Количество экспортируемых в RSS файл элементов (при включенном RSS_FILE_ACTIVE) 311 | * @param int $rssFileLimit 312 | * @return $this 313 | */ 314 | public function setRssFileLimit($rssFileLimit) 315 | { 316 | $this->fields['RSS_FILE_LIMIT'] = $rssFileLimit; 317 | 318 | return $this; 319 | } 320 | 321 | /** 322 | * За сколько последних дней экспортировать в RSS файл. (при включенном RSS_FILE_ACTIVE). -1 без ограничения по дням. 323 | * @param int $rssFileDays 324 | * @return $this 325 | */ 326 | public function setRssFileDays($rssFileDays) 327 | { 328 | $this->fields['RSS_FILE_DAYS'] = $rssFileDays; 329 | 330 | return $this; 331 | } 332 | 333 | /** 334 | * Экспортировать в RSS файл в формате для yandex 335 | * @param bool $rssYandexActive 336 | * @return $this 337 | */ 338 | public function setRssYandexActive($rssYandexActive = false) 339 | { 340 | $this->fields['RSS_YANDEX_ACTIVE'] = $rssYandexActive ? 'Y' : 'N'; 341 | 342 | return $this; 343 | } 344 | 345 | /** 346 | * Индексировать для поиска элементы информационного блока. 347 | * @param bool $indexElement 348 | * @return $this 349 | */ 350 | public function setIndexElement($indexElement = true) 351 | { 352 | $this->fields['INDEX_ELEMENT'] = $indexElement ? 'Y' : 'N'; 353 | 354 | return $this; 355 | } 356 | 357 | /** 358 | * Индексировать для поиска разделы информационного блока. 359 | * @param bool $indexSection 360 | * @return $this 361 | */ 362 | public function setIndexSection($indexSection = false) 363 | { 364 | $this->fields['INDEX_SECTION'] = $indexSection ? 'Y' : 'N'; 365 | 366 | return $this; 367 | } 368 | 369 | /** 370 | * Режим отображения списка элементов в административном разделе (S|C). 371 | * @param string $listMode 372 | * @return $this 373 | */ 374 | public function setListMode($listMode) 375 | { 376 | $this->fields['LIST_MODE'] = $listMode; 377 | 378 | return $this; 379 | } 380 | 381 | /** 382 | * Режим проверки прав доступа (S|E). 383 | * @param string $rightsMode 384 | * @return $this 385 | */ 386 | public function setRightsMode($rightsMode = 'S') 387 | { 388 | $this->fields['RIGHTS_MODE'] = $rightsMode; 389 | 390 | return $this; 391 | } 392 | 393 | /** 394 | * Признак наличия привязки свойств к разделам (Y|N). 395 | * @param string $sectionProperty 396 | * @return $this 397 | */ 398 | public function setSectionProperty($sectionProperty) 399 | { 400 | $this->fields['SECTION_PROPERTY'] = $sectionProperty; 401 | 402 | return $this; 403 | } 404 | 405 | /** 406 | * Признак наличия фасетного индекса (N|Y|I). 407 | * @param string $propertyIndex 408 | * @return $this 409 | */ 410 | public function setPropertyIndex($propertyIndex) 411 | { 412 | $this->fields['PROPERTY_INDEX'] = $propertyIndex; 413 | 414 | return $this; 415 | } 416 | 417 | /** 418 | * Служебное поле для процедуры конвертации места хранения значений свойств инфоблока. 419 | * @param int $lastConvElement 420 | * @return $this 421 | */ 422 | public function setLastConvElement($lastConvElement) 423 | { 424 | $this->fields['LAST_CONV_ELEMENT'] = $lastConvElement; 425 | 426 | return $this; 427 | } 428 | 429 | /** 430 | * Служебное поле для установки прав для разных групп на доступ к информационному блоку. 431 | * @param array $groupId Массив соответствий кодов групп правам доступа 432 | * @return $this 433 | */ 434 | public function setGroupId($groupId) 435 | { 436 | $this->fields['GROUP_ID'] = $groupId; 437 | 438 | return $this; 439 | } 440 | 441 | /** 442 | * Служебное поле для привязки к группе социальной сети. 443 | * @param int $socnetGroupId 444 | * @return $this 445 | */ 446 | public function setSocnetGroupId($socnetGroupId) 447 | { 448 | $this->fields['SOCNET_GROUP_ID'] = $socnetGroupId; 449 | 450 | return $this; 451 | } 452 | 453 | /** 454 | * Инфоблок участвует в документообороте (Y|N). 455 | * @param bool $workflow 456 | * @return $this 457 | */ 458 | public function setWorkflow($workflow = true) 459 | { 460 | $this->fields['WORKFLOW'] = $workflow ? 'Y' : 'N'; 461 | 462 | return $this; 463 | } 464 | 465 | /** 466 | * Инфоблок участвует в бизнес-процессах (Y|N). 467 | * @param bool $bizproc 468 | * @return $this 469 | */ 470 | public function setBizProc($bizproc = false) 471 | { 472 | $this->fields['BIZPROC'] = $bizproc ? 'Y' : 'N'; 473 | 474 | return $this; 475 | } 476 | 477 | /** 478 | * Флаг выбора интерфейса отображения привязки элемента к разделам (D|L|P). 479 | * @param string $sectionChooser 480 | * @return $this 481 | */ 482 | public function setSectionChooser($sectionChooser) 483 | { 484 | $this->fields['SECTION_CHOOSER'] = $sectionChooser; 485 | 486 | return $this; 487 | } 488 | 489 | /** 490 | * Флаг хранения значений свойств элементов инфоблока (1 - в общей таблице | 2 - в отдельной). 491 | * @param int $version 492 | * @return $this 493 | */ 494 | public function setVersion($version = 1) 495 | { 496 | $this->fields['VERSION'] = $version; 497 | 498 | return $this; 499 | } 500 | 501 | /** 502 | * Полный путь к файлу-обработчику массива полей элемента перед сохранением на странице редактирования элемента. 503 | * @param string $editFileBefore 504 | * @return $this 505 | */ 506 | public function setEditFileBefore($editFileBefore) 507 | { 508 | $this->fields['EDIT_FILE_BEFORE'] = $editFileBefore; 509 | 510 | return $this; 511 | } 512 | 513 | /** 514 | * Полный путь к файлу-обработчику вывода интерфейса редактирования элемента. 515 | * @param string $editFileAfter 516 | * @return $this 517 | */ 518 | public function setEditFileAfter($editFileAfter) 519 | { 520 | $this->fields['EDIT_FILE_AFTER'] = $editFileAfter; 521 | 522 | return $this; 523 | } 524 | 525 | /** 526 | * Название элемента в единственном числе 527 | * @param string $message 528 | * @return $this 529 | */ 530 | public function setMessElementName($message = 'Элемент') 531 | { 532 | $this->fields['ELEMENT_NAME'] = $message; 533 | 534 | return $this; 535 | } 536 | 537 | /** 538 | * Название элемента во множнственном числе 539 | * @param string $message 540 | * @return $this 541 | */ 542 | public function setMessElementsName($message = 'Элементы') 543 | { 544 | $this->fields['ELEMENTS_NAME'] = $message; 545 | 546 | return $this; 547 | } 548 | 549 | /** 550 | * Действие по добавлению элемента 551 | * @param string $message 552 | * @return $this 553 | */ 554 | public function setMessElementAdd($message = 'Добавить элемент') 555 | { 556 | $this->fields['ELEMENT_ADD'] = $message; 557 | 558 | return $this; 559 | } 560 | 561 | /** 562 | * Действие по редактированию/изменению элемента 563 | * @param string $message 564 | * @return $this 565 | */ 566 | public function setMessElementEdit($message = 'Изменить элемент') 567 | { 568 | $this->fields['ELEMENT_EDIT'] = $message; 569 | 570 | return $this; 571 | } 572 | 573 | /** 574 | * Действие по удалению элемента 575 | * @param string $message 576 | * @return $this 577 | */ 578 | public function setMessElementDelete($message = 'Удалить элемент') 579 | { 580 | $this->fields['ELEMENT_DELETE'] = $message; 581 | 582 | return $this; 583 | } 584 | 585 | /** 586 | * Название раздела в единственном числе 587 | * @param string $message 588 | * @return $this 589 | */ 590 | public function setMessSectionName($message = 'Раздел') 591 | { 592 | $this->fields['SECTION_NAME'] = $message; 593 | 594 | return $this; 595 | } 596 | 597 | /** 598 | * Название раздела во множнственном числе 599 | * @param string $message 600 | * @return $this 601 | */ 602 | public function setMessSectionsName($message = 'Разделы') 603 | { 604 | $this->fields['SECTIONS_NAME'] = $message; 605 | 606 | return $this; 607 | } 608 | 609 | /** 610 | * Действие по добавлению раздела 611 | * @param string $message 612 | * @return $this 613 | */ 614 | public function setMessSectionAdd($message = 'Добавить раздел') 615 | { 616 | $this->fields['SECTION_ADD'] = $message; 617 | 618 | return $this; 619 | } 620 | 621 | /** 622 | * Действие по редактированию/изменению раздела 623 | * @param string $message 624 | * @return $this 625 | */ 626 | public function setMessSectionEdit($message = 'Изменить раздел') 627 | { 628 | $this->fields['SECTION_EDIT'] = $message; 629 | 630 | return $this; 631 | } 632 | 633 | /** 634 | * Действие по удалению раздела 635 | * @param string $message 636 | * @return $this 637 | */ 638 | public function setMessSectionDelete($message = 'Удалить раздел') 639 | { 640 | $this->fields['SECTION_DELETE'] = $message; 641 | 642 | return $this; 643 | } 644 | 645 | 646 | } 647 | -------------------------------------------------------------------------------- /src/Constructors/IBlockProperty.php: -------------------------------------------------------------------------------- 1 | Add($this->getFieldsWithDefault()); 23 | 24 | if (!$property_id) { 25 | throw new \Exception($obj->LAST_ERROR); 26 | } 27 | 28 | Logger::log("Добавлено свойство инфоблока {$this->fields['CODE']}", Logger::COLOR_GREEN); 29 | 30 | return $property_id; 31 | } 32 | 33 | /** 34 | * Обновить свойство инфоблока 35 | * @param $id 36 | * @throws \Exception 37 | */ 38 | public function update($id) 39 | { 40 | $obj = new \CIBlockProperty(); 41 | if (!$obj->Update($id, $this->fields)) { 42 | throw new \Exception($obj->LAST_ERROR); 43 | } 44 | 45 | Logger::log("Обновлено свойство инфоблока {$id}", Logger::COLOR_GREEN); 46 | } 47 | 48 | /** 49 | * Удалить свойство инфоблока 50 | * @param $id 51 | * @throws \Exception 52 | */ 53 | public static function delete($id) 54 | { 55 | if (!\CIBlockProperty::Delete($id)) { 56 | throw new \Exception('Ошибка при удалении свойства инфоблока'); 57 | } 58 | 59 | Logger::log("Удалено свойство инфоблока {$id}", Logger::COLOR_GREEN); 60 | } 61 | 62 | /** 63 | * Установить настройки для добавления свойства инфоблока по умолчанию 64 | * @param string $code 65 | * @param string $name 66 | * @param int $iblockId 67 | * @return IBlockProperty 68 | */ 69 | public function constructDefault($code, $name, $iblockId) 70 | { 71 | return $this->setPropertyType('S')->setCode($code)->setName($name)->setIblockId($iblockId); 72 | } 73 | 74 | /** 75 | * Символьный идентификатор. 76 | * @param string $code 77 | * @return $this 78 | */ 79 | public function setCode($code) 80 | { 81 | $this->fields['CODE'] = $code; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Внешний код. 88 | * @param string $xml_id 89 | * @return $this 90 | */ 91 | public function setXmlId($xml_id) 92 | { 93 | $this->fields['XML_ID'] = $xml_id; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Код информационного блока. 100 | * @param string $iblock_id 101 | * @return $this 102 | */ 103 | public function setIblockId($iblock_id) 104 | { 105 | $this->fields['IBLOCK_ID'] = $iblock_id; 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Название. 112 | * @param string $name 113 | * @return $this 114 | */ 115 | public function setName($name) 116 | { 117 | $this->fields['NAME'] = $name; 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * Флаг активности 124 | * @param bool $active 125 | * @return $this 126 | */ 127 | public function setActive($active = true) 128 | { 129 | $this->fields['ACTIVE'] = $active ? 'Y' : 'N'; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Обязательное (Y|N). 136 | * @param bool $isRequired 137 | * @return $this 138 | */ 139 | public function setIsRequired($isRequired = true) 140 | { 141 | $this->fields['IS_REQUIRED'] = $isRequired ? 'Y' : 'N'; 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * Индекс сортировки. 148 | * @param int $sort 149 | * @return $this 150 | */ 151 | public function setSort($sort = 500) 152 | { 153 | $this->fields['SORT'] = $sort; 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Тип свойства. Возможные значения: S - строка, N - число, F - файл, L - список, E - привязка к элементам, G - привязка к группам. 160 | * @param string $propertyType 161 | * @return $this 162 | */ 163 | public function setPropertyType($propertyType = 'S') 164 | { 165 | $this->fields['PROPERTY_TYPE'] = $propertyType; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Установить тип свойства "Список" 172 | * @param array $values массив доступных значений (можно собрать с помощью класса IBlockPropertyEnum) 173 | * @param string $listType Тип, может быть "L" - выпадающий список или "C" - флажки. 174 | * @param int $multipleCnt Количество строк в выпадающем списке 175 | * @return $this 176 | */ 177 | public function setPropertyTypeList($values, $listType = null, $multipleCnt = null) 178 | { 179 | $this->setPropertyType('L'); 180 | $this->fields['VALUES'] = $values; 181 | 182 | if (!is_null($listType)) { 183 | $this->setListType($listType); 184 | } 185 | 186 | if (!is_null($multipleCnt)) { 187 | $this->setMultipleCnt($multipleCnt); 188 | } 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * Установить тип свойства "Файл" 195 | * @param string $fileType Список допустимых расширений (через запятую). 196 | * @return $this 197 | */ 198 | public function setPropertyTypeFile($fileType = null) 199 | { 200 | $this->setPropertyType('F'); 201 | 202 | if (!is_null($fileType)) { 203 | $this->setFileType($fileType); 204 | } 205 | 206 | return $this; 207 | } 208 | 209 | /** 210 | * Установить тип свойства "привязка к элементам" или "привязка к группам" 211 | * @param string $property_type Тип свойства. Возможные значения: E - привязка к элементам, G - привязка к группам. 212 | * @param string $linkIblockId код информационного блока с элементами/группами которого и будут связано значение. 213 | * @return $this 214 | */ 215 | public function setPropertyTypeIblock($property_type, $linkIblockId) 216 | { 217 | $this->setPropertyType($property_type)->setLinkIblockId($linkIblockId); 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * Установить тип свойства "справочник" 224 | * @param string $table_name таблица HL для связи 225 | * @return $this 226 | */ 227 | public function setPropertyTypeHl($table_name) 228 | { 229 | $this->setPropertyType('S')->setUserType('directory')->setUserTypeSettings([ 230 | 'TABLE_NAME' => $table_name 231 | ]); 232 | 233 | return $this; 234 | } 235 | 236 | /** 237 | * Множественность (Y|N). 238 | * @param bool $multiple 239 | * @return $this 240 | */ 241 | public function setMultiple($multiple = false) 242 | { 243 | $this->fields['MULTIPLE'] = $multiple ? 'Y' : 'N'; 244 | 245 | return $this; 246 | } 247 | 248 | /** 249 | * Количество строк в выпадающем списке для свойств типа "список". 250 | * @param int $multipleCnt 251 | * @return $this 252 | */ 253 | public function setMultipleCnt($multipleCnt) 254 | { 255 | $this->fields['MULTIPLE_CNT'] = $multipleCnt; 256 | 257 | return $this; 258 | } 259 | 260 | /** 261 | * Значение свойства по умолчанию (кроме свойства типа список L). 262 | * @param string $defaultValue 263 | * @return $this 264 | */ 265 | public function setDefaultValue($defaultValue) 266 | { 267 | $this->fields['DEFAULT_VALUE'] = $defaultValue; 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * Количество строк в ячейке ввода значения свойства. 274 | * @param int $rowCount 275 | * @return $this 276 | */ 277 | public function setRowCount($rowCount) 278 | { 279 | $this->fields['ROW_COUNT'] = $rowCount; 280 | 281 | return $this; 282 | } 283 | 284 | /** 285 | * Количество столбцов в ячейке ввода значения свойства. 286 | * @param int $colCount 287 | * @return $this 288 | */ 289 | public function setColCount($colCount) 290 | { 291 | $this->fields['COL_COUNT'] = $colCount; 292 | 293 | return $this; 294 | } 295 | 296 | /** 297 | * Тип для свойства список (L). Может быть "L" - выпадающий список или "C" - флажки. 298 | * @param string $listType 299 | * @return $this 300 | */ 301 | public function setListType($listType = 'L') 302 | { 303 | $this->fields['LIST_TYPE'] = $listType; 304 | 305 | return $this; 306 | } 307 | 308 | /** 309 | * Список допустимых расширений для свойств файл "F" (через запятую). 310 | * @param string $fileType 311 | * @return $this 312 | */ 313 | public function setFileType($fileType) 314 | { 315 | $this->fields['FILE_TYPE'] = $fileType; 316 | 317 | return $this; 318 | } 319 | 320 | /** 321 | * Индексировать значения данного свойства. 322 | * @param bool $searchable 323 | * @return $this 324 | */ 325 | public function setSearchable($searchable = false) 326 | { 327 | $this->fields['SEARCHABLE'] = $searchable ? 'Y' : 'N'; 328 | 329 | return $this; 330 | } 331 | 332 | /** 333 | * Выводить поля для фильтрации по данному свойству на странице списка элементов в административном разделе. 334 | * @param bool $filtrable 335 | * @return $this 336 | */ 337 | public function setFiltrable($filtrable = false) 338 | { 339 | $this->fields['FILTRABLE'] = $filtrable ? 'Y' : 'N'; 340 | 341 | return $this; 342 | } 343 | 344 | /** 345 | * Для свойств типа привязки к элементам и группам задает код информационного блока с элементами/группами которого и будут связано значение. 346 | * @param int $linkIblockId 347 | * @return $this 348 | */ 349 | public function setLinkIblockId($linkIblockId) 350 | { 351 | $this->fields['LINK_IBLOCK_ID'] = $linkIblockId; 352 | 353 | return $this; 354 | } 355 | 356 | /** 357 | * Признак наличия у значения свойства дополнительного поля описания. Только для типов S - строка, N - число и F - файл (Y|N). 358 | * @param bool $withDescription 359 | * @return $this 360 | */ 361 | public function setWithDescription($withDescription) 362 | { 363 | $this->fields['WITH_DESCRIPTION'] = $withDescription ? 'Y' : 'N'; 364 | 365 | return $this; 366 | } 367 | 368 | /** 369 | * Идентификатор пользовательского типа свойства. 370 | * @param string $user_type 371 | * @return $this 372 | */ 373 | public function setUserType($user_type) 374 | { 375 | $this->fields['USER_TYPE'] = $user_type; 376 | 377 | return $this; 378 | } 379 | 380 | /** 381 | * Идентификатор пользовательского типа свойства. 382 | * @param array $user_type_settings 383 | * @return $this 384 | */ 385 | public function setUserTypeSettings($user_type_settings) 386 | { 387 | $this->fields['USER_TYPE_SETTINGS'] = array_merge((array)$this->fields['USER_TYPE_SETTINGS'], $user_type_settings); 388 | 389 | return $this; 390 | } 391 | 392 | /** 393 | * Подсказка 394 | * @param string $hint 395 | * @return $this 396 | */ 397 | public function setHint($hint) 398 | { 399 | $this->fields['HINT'] = $hint; 400 | 401 | return $this; 402 | } 403 | } -------------------------------------------------------------------------------- /src/Constructors/IBlockPropertyEnum.php: -------------------------------------------------------------------------------- 1 | Add($this->getFieldsWithDefault()); 23 | 24 | if (!$property_enum_id) { 25 | throw new \Exception("Ошибка добавления значения enum"); 26 | } 27 | 28 | Logger::log("Добавлено значение списка enum {$this->fields['VALUE']}", Logger::COLOR_GREEN); 29 | 30 | return $property_enum_id; 31 | } 32 | 33 | /** 34 | * Обновить свойство инфоблока 35 | * @param $id 36 | * @throws \Exception 37 | */ 38 | public function update($id) 39 | { 40 | $obj = new \CIBlockPropertyEnum(); 41 | if (!$obj->Update($id, $this->fields)) { 42 | throw new \Exception("Ошибка обновления значения enum"); 43 | } 44 | 45 | Logger::log("Обновлено значение списка enum {$id}", Logger::COLOR_GREEN); 46 | } 47 | 48 | /** 49 | * Удалить свойство инфоблока 50 | * @param $id 51 | * @throws \Exception 52 | */ 53 | public static function delete($id) 54 | { 55 | if (!\CIBlockPropertyEnum::Delete($id)) { 56 | throw new \Exception('Ошибка при удалении значения enum'); 57 | } 58 | 59 | Logger::log("Удалено значение списка enum {$id}", Logger::COLOR_GREEN); 60 | } 61 | 62 | /** 63 | * Установить настройки для добавления значения enum инфоблока по умолчанию 64 | * @param string $xml_id 65 | * @param string $value 66 | * @param int $propertyId 67 | * @return $this 68 | */ 69 | public function constructDefault($xml_id, $value, $propertyId = null) 70 | { 71 | $this->setXmlId($xml_id)->setValue($value); 72 | 73 | if ($propertyId) { 74 | $this->setPropertyId($propertyId); 75 | } 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Код свойства. 82 | * @param string $propertyId 83 | * @return $this 84 | */ 85 | public function setPropertyId($propertyId) 86 | { 87 | $this->fields['PROPERTY_ID'] = $propertyId; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * Внешний код. 94 | * @param string $xml_id 95 | * @return $this 96 | */ 97 | public function setXmlId($xml_id) 98 | { 99 | $this->fields['XML_ID'] = $xml_id; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Индекс сортировки. 106 | * @param int $sort 107 | * @return $this 108 | */ 109 | public function setSort($sort = 500) 110 | { 111 | $this->fields['SORT'] = $sort; 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Значение варианта свойства. 118 | * @param string $value 119 | * @return $this 120 | */ 121 | public function setValue($value) 122 | { 123 | $this->fields['VALUE'] = $value; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * Значение варианта свойства. 130 | * @param bool $def 131 | * @return $this 132 | */ 133 | public function setDef($def) 134 | { 135 | $this->fields['DEF'] = $def ? 'Y' : 'N'; 136 | 137 | return $this; 138 | } 139 | } -------------------------------------------------------------------------------- /src/Constructors/IBlockType.php: -------------------------------------------------------------------------------- 1 | Add($this->getFieldsWithDefault())) { 22 | throw new \Exception($obj->LAST_ERROR); 23 | } 24 | 25 | Logger::log("Добавлен тип инфоблока {$this->fields['ID']}", Logger::COLOR_GREEN); 26 | } 27 | 28 | /** 29 | * Обновить тип инфоблока 30 | * @param $id 31 | * @throws \Exception 32 | */ 33 | public function update($id) 34 | { 35 | $obj = new \CIBlockType(); 36 | if (!$obj->Update($id, $this->fields)) { 37 | throw new \Exception($obj->LAST_ERROR); 38 | } 39 | 40 | Logger::log("Обновлен тип инфоблока {$id}", Logger::COLOR_GREEN); 41 | } 42 | 43 | /** 44 | * Удалить тип инфоблока 45 | * @param $id 46 | * @throws \Exception 47 | */ 48 | public static function delete($id) 49 | { 50 | if (!\CIBlockType::Delete($id)) { 51 | throw new \Exception('Ошибка при удалении типа инфоблока'); 52 | } 53 | 54 | Logger::log("Удален тип инфоблока {$id}", Logger::COLOR_GREEN); 55 | } 56 | 57 | /** 58 | * ID типа информационных блоков. Уникален. 59 | * @param string $id 60 | * @return $this 61 | */ 62 | public function setId($id) 63 | { 64 | $this->fields['ID'] = $id; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Разделяются ли элементы блока этого типа по разделам. 71 | * @param bool $has 72 | * @return $this 73 | */ 74 | public function setSections($has = true) 75 | { 76 | $this->fields['SECTIONS'] = $has ? 'Y' : 'N'; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Полный путь к файлу-обработчику массива полей элемента перед сохранением на странице редактирования элемента. 83 | * @param string $editFileBefore 84 | * @return $this 85 | */ 86 | public function setEditFileBefore($editFileBefore) 87 | { 88 | $this->fields['EDIT_FILE_BEFORE'] = $editFileBefore; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Полный путь к файлу-обработчику вывода интерфейса редактирования элемента. 95 | * @param string $editFileAfter 96 | * @return $this 97 | */ 98 | public function setEditFileAfter($editFileAfter) 99 | { 100 | $this->fields['EDIT_FILE_AFTER'] = $editFileAfter; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Блоки данного типа экспортировать в RSS 107 | * @param bool $inRss 108 | * @return $this 109 | */ 110 | public function setInRss($inRss = false) 111 | { 112 | $this->fields['IN_RSS'] = $inRss ? 'Y' : 'N'; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * Порядок сортировки типа 119 | * @param int $sort 120 | * @return $this 121 | */ 122 | public function setSort($sort = 500) 123 | { 124 | $this->fields['SORT'] = $sort; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Указать языковые фразы 131 | * @param string $lang ключ языка (ru) 132 | * @param string $name 133 | * @param string $sectionName 134 | * @param string $elementName 135 | * @return $this 136 | */ 137 | public function setLang($lang, $name, $sectionName = null, $elementName = null) 138 | { 139 | $setting = ['NAME' => $name]; 140 | 141 | if ($sectionName) { 142 | $setting['SECTION_NAME'] = $sectionName; 143 | } 144 | if ($elementName) { 145 | $setting['ELEMENT_NAME'] = $elementName; 146 | } 147 | 148 | $this->fields['LANG'][$lang] = $setting; 149 | 150 | return $this; 151 | } 152 | } -------------------------------------------------------------------------------- /src/Constructors/UserField.php: -------------------------------------------------------------------------------- 1 | Add($this->getFieldsWithDefault()); 24 | 25 | if (!$result) { 26 | global $APPLICATION; 27 | throw new \Exception($APPLICATION->GetException()); 28 | } 29 | 30 | Logger::log("Добавлен UF {$this->fields['FIELD_NAME']} для {$this->fields['ENTITY_ID']}", Logger::COLOR_GREEN); 31 | 32 | return $result; 33 | } 34 | 35 | /** 36 | * Обновить UF 37 | * @param $id 38 | * @throws \Exception 39 | */ 40 | public function update($id) 41 | { 42 | $uf = new \CUserTypeEntity(); 43 | $result = $uf->Update($id, $this->fields); 44 | 45 | if (!$result) { 46 | global $APPLICATION; 47 | throw new \Exception($APPLICATION->GetException()); 48 | } 49 | 50 | Logger::log("Обновлен UF {$id}", Logger::COLOR_GREEN); 51 | } 52 | 53 | /** 54 | * Удалить UF 55 | * @param $id 56 | * @throws \Exception 57 | */ 58 | public static function delete($id) 59 | { 60 | $result = (new \CUserTypeEntity())->Delete($id); 61 | 62 | if (!$result) { 63 | global $APPLICATION; 64 | throw new \Exception($APPLICATION->GetException()); 65 | } 66 | 67 | Logger::log("Удален UF {$id}", Logger::COLOR_GREEN); 68 | } 69 | 70 | /** 71 | * Установить настройки для добавления UF по умолчанию 72 | * @param string $entityId Идентификатор сущности 73 | * @param string $fieldName Код поля. 74 | * @return $this 75 | */ 76 | public function constructDefault($entityId, $fieldName) 77 | { 78 | return $this->setEntityId($entityId)->setFieldName($fieldName)->setUserType('string'); 79 | } 80 | 81 | /** 82 | * Идентификатор сущности, к которой будет привязано свойство. 83 | * @param string $entityId 84 | * @return $this 85 | */ 86 | public function setEntityId($entityId) 87 | { 88 | $this->fields['ENTITY_ID'] = $entityId; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Код поля. Всегда должно начинаться с UF_ 95 | * @param string $fieldName 96 | * @return $this 97 | */ 98 | public function setFieldName($fieldName) 99 | { 100 | $this->fields['FIELD_NAME'] = static::prepareUf($fieldName); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * тип пользовательского свойства 107 | * @param string $userType 108 | * @return $this 109 | */ 110 | public function setUserType($userType) 111 | { 112 | $this->fields['USER_TYPE_ID'] = $userType; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * тип нового пользовательского свойства HL 119 | * @param string $table_name 120 | * @param string $showField 121 | * @return $this 122 | */ 123 | public function setUserTypeHL($table_name, $showField) 124 | { 125 | $linkId = Helpers::getHlId($table_name); 126 | $this->setUserType('hlblock')->setSettings([ 127 | 'HLBLOCK_ID' => Helpers::getHlId($table_name), 128 | 'HLFIELD_ID' => Helpers::getFieldId(Constructor::objHLBlock($linkId), static::prepareUf($showField)), 129 | ]); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * тип нового пользовательского свойства "связь с разелом ИБ" 136 | * @param string $iblockId 137 | * @return $this 138 | */ 139 | public function setUserTypeIblockSection($iblockId) 140 | { 141 | $this->setUserType('iblock_section')->setSettings([ 142 | 'IBLOCK_ID' => $iblockId, 143 | ]); 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * тип нового пользовательского свойства "связь с элементом ИБ" 150 | * @param string $iblockId 151 | * @return $this 152 | */ 153 | public function setUserTypeIblockElement($iblockId) 154 | { 155 | $this->setUserType('iblock_element')->setSettings([ 156 | 'IBLOCK_ID' => $iblockId, 157 | ]); 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * XML_ID пользовательского свойства. Используется при выгрузке в качестве названия поля 164 | * @param string $xmlId 165 | * @return $this 166 | */ 167 | public function setXmlId($xmlId) 168 | { 169 | $this->fields['XML_ID'] = $xmlId; 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * Сортировка 176 | * @param int $sort 177 | * @return $this 178 | */ 179 | public function setSort($sort) 180 | { 181 | $this->fields['SORT'] = $sort; 182 | 183 | return $this; 184 | } 185 | 186 | /** 187 | * Является поле множественным или нет 188 | * @param bool $multiple 189 | * @return $this 190 | */ 191 | public function setMultiple($multiple) 192 | { 193 | $this->fields['MULTIPLE'] = $multiple ? 'Y' : 'N'; 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Обязательное или нет свойство 200 | * @param bool $mandatory 201 | * @return $this 202 | */ 203 | public function setMandatory($mandatory) 204 | { 205 | $this->fields['MANDATORY'] = $mandatory ? 'Y' : 'N'; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Показывать в фильтре списка. Возможные значения: не показывать = N, точное совпадение = I, поиск по маске = E, поиск по подстроке = S 212 | * @param string $showInFilter 213 | * @return $this 214 | */ 215 | public function setShowFilter($showInFilter) 216 | { 217 | $this->fields['SHOW_FILTER'] = $showInFilter; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * Не показывать в списке. Если передать какое-либо значение, то будет считаться, что флаг выставлен. 224 | * @param bool $showInList 225 | * @return $this 226 | */ 227 | public function setShowInList($showInList) 228 | { 229 | $this->fields['SHOW_IN_LIST'] = $showInList ? 'Y' : ''; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * Пустая строка разрешает редактирование. Если передать какое-либо значение, то будет считаться, что флаг выставлен. 236 | * @param bool $editInList 237 | * @return $this 238 | */ 239 | public function setEditInList($editInList) 240 | { 241 | $this->fields['EDIT_IN_LIST'] = $editInList ? 'Y' : ''; 242 | 243 | return $this; 244 | } 245 | 246 | /** 247 | * Значения поля участвуют в поиске 248 | * @param bool $isSearchable 249 | * @return $this 250 | */ 251 | public function setIsSearchable($isSearchable = false) 252 | { 253 | $this->fields['IS_SEARCHABLE'] = $isSearchable ? 'Y' : 'N'; 254 | 255 | return $this; 256 | } 257 | 258 | /** 259 | * Дополнительные настройки поля (зависят от типа). В нашем случае для типа string 260 | * @param array $settings 261 | * @return $this 262 | */ 263 | public function setSettings($settings) 264 | { 265 | $this->fields['SETTINGS'] = array_merge((array)$this->fields['SETTINGS'], $settings); 266 | 267 | return $this; 268 | } 269 | 270 | /** 271 | * Языковые фразы 272 | * @param string $lang 273 | * @param string $text 274 | * @return $this 275 | */ 276 | public function setLangDefault($lang, $text) 277 | { 278 | $this->setLangForm($lang, $text); 279 | $this->setLangColumn($lang, $text); 280 | $this->setLangFilter($lang, $text); 281 | 282 | return $this; 283 | } 284 | 285 | /** 286 | * Текст "Заголовок в списке" 287 | * @param string $lang 288 | * @param string $text 289 | * @return $this 290 | */ 291 | public function setLangForm($lang, $text) 292 | { 293 | $this->fields['EDIT_FORM_LABEL'][$lang] = $text; 294 | 295 | return $this; 296 | } 297 | 298 | /** 299 | * Текст "Заголовок в списке" 300 | * @param string $lang 301 | * @param string $text 302 | * @return $this 303 | */ 304 | public function setLangColumn($lang, $text) 305 | { 306 | $this->fields['LIST_COLUMN_LABEL'][$lang] = $text; 307 | 308 | return $this; 309 | } 310 | 311 | /** 312 | * Текст "Подпись фильтра в списке" 313 | * @param string $lang 314 | * @param string $text 315 | * @return $this 316 | */ 317 | public function setLangFilter($lang, $text) 318 | { 319 | $this->fields['LIST_FILTER_LABEL'][$lang] = $text; 320 | 321 | return $this; 322 | } 323 | 324 | /** 325 | * Текст "Помощь" 326 | * @param string $lang 327 | * @param string $text 328 | * @return $this 329 | */ 330 | public function setLangHelp($lang, $text) 331 | { 332 | $this->fields['HELP_MESSAGE'][$lang] = $text; 333 | 334 | return $this; 335 | } 336 | 337 | /** 338 | * Текст "Сообщение об ошибке (не обязательное)" 339 | * @param string $lang 340 | * @param string $text 341 | * @return $this 342 | */ 343 | public function setLangError($lang, $text) 344 | { 345 | $this->fields['ERROR_MESSAGE'][$lang] = $text; 346 | 347 | return $this; 348 | } 349 | 350 | protected static function prepareUf($name) 351 | { 352 | if (substr($name, 0, 3) != 'UF_') { 353 | $name = "UF_{$name}"; 354 | } 355 | 356 | return $name; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/Exceptions/MigrationException.php: -------------------------------------------------------------------------------- 1 | query('SELECT `ID`, `NAME`, `TABLE_NAME` FROM b_hlblock_entity'); 48 | while ($block = $dbRes->fetch()) { 49 | static::$hls[$block['TABLE_NAME']] = $block; 50 | } 51 | } 52 | 53 | return static::$hls[$table_name]['ID']; 54 | } 55 | 56 | /** 57 | * Получить ID UF 58 | * @param $obj 59 | * @param $field_name 60 | * @return mixed 61 | */ 62 | public static function getFieldId($obj, $field_name) 63 | { 64 | if (!isset(static::$ufs[$obj][$field_name])) { 65 | $dbRes = Application::getConnection()->query('SELECT * FROM b_user_field'); 66 | while ($uf = $dbRes->fetch()) { 67 | static::$ufs[$uf['ENTITY_ID']][$uf['FIELD_NAME']] = $uf; 68 | } 69 | } 70 | 71 | return static::$ufs[$obj][$field_name]['ID']; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Interfaces/DatabaseStorageInterface.php: -------------------------------------------------------------------------------- 1 | config = $config; 78 | $this->dir = $config['dir']; 79 | $this->dir_archive = isset($config['dir_archive']) ? $config['dir_archive'] : 'archive'; 80 | $this->use_transaction = isset($config['use_transaction']) ? $config['use_transaction'] : false; 81 | 82 | if (isset($config['default_fields']) && is_array($config['default_fields'])) { 83 | foreach ($config['default_fields'] as $class => $default_fields) { 84 | FieldConstructor::$defaultFields[$class] = $default_fields; 85 | } 86 | } 87 | 88 | $this->templates = $templates; 89 | $this->database = $database ?: new BitrixDatabaseStorage($config['table']); 90 | $this->files = $files ?: new FileStorage(); 91 | } 92 | 93 | /** 94 | * Create migration file. 95 | * 96 | * @param string $name - migration name 97 | * @param string $templateName 98 | * @param array $replace - array of placeholders that should be replaced with a given values. 99 | * @param string $subDir 100 | * 101 | * @return string 102 | */ 103 | public function createMigration($name, $templateName, array $replace = [], $subDir = '') 104 | { 105 | $targetDir = $this->dir; 106 | $subDir = trim(str_replace('\\', '/', $subDir), '/'); 107 | if ($subDir) { 108 | $targetDir .= '/' . $subDir; 109 | } 110 | 111 | $this->files->createDirIfItDoesNotExist($targetDir); 112 | 113 | $fileName = $this->constructFileName($name); 114 | $className = $this->getMigrationClassNameByFileName($fileName); 115 | $templateName = $this->templates->selectTemplate($templateName); 116 | 117 | $template = $this->files->getContent($this->templates->getTemplatePath($templateName)); 118 | $template = $this->replacePlaceholdersInTemplate($template, array_merge($replace, ['className' => $className])); 119 | 120 | $this->files->putContent($targetDir.'/'.$fileName.'.php', $template); 121 | 122 | return $fileName; 123 | } 124 | 125 | /** 126 | * Run all migrations that were not run before. 127 | */ 128 | public function runMigrations() 129 | { 130 | $migrations = $this->getMigrationsToRun(); 131 | $ran = []; 132 | 133 | if (empty($migrations)) { 134 | return $ran; 135 | } 136 | 137 | foreach ($migrations as $migration) { 138 | $this->runMigration($migration); 139 | $ran[] = $migration; 140 | } 141 | 142 | return $ran; 143 | } 144 | 145 | /** 146 | * Run a given migration. 147 | * 148 | * @param string $file 149 | * 150 | * @throws Exception 151 | * 152 | * @return string 153 | */ 154 | public function runMigration($file) 155 | { 156 | $migration = $this->getMigrationObjectByFileName($file); 157 | 158 | $this->disableBitrixIblockHelperCache(); 159 | 160 | $this->checkTransactionAndRun($migration, function () use ($migration, $file) { 161 | if ($migration->up() === false) { 162 | throw new Exception("Migration up from {$file}.php returned false"); 163 | } 164 | }); 165 | 166 | $this->logSuccessfulMigration($file); 167 | } 168 | 169 | /** 170 | * Log successful migration. 171 | * 172 | * @param string $migration 173 | * 174 | * @return void 175 | */ 176 | public function logSuccessfulMigration($migration) 177 | { 178 | $this->database->logSuccessfulMigration($migration); 179 | } 180 | 181 | /** 182 | * Get ran migrations. 183 | * 184 | * @return array 185 | */ 186 | public function getRanMigrations() 187 | { 188 | return $this->database->getRanMigrations(); 189 | } 190 | 191 | /** 192 | * Get all migrations. 193 | * 194 | * @return array 195 | */ 196 | public function getAllMigrations() 197 | { 198 | return $this->files->getMigrationFiles($this->dir); 199 | } 200 | 201 | /** 202 | * Determine whether migration file for migration exists. 203 | * 204 | * @param string $migration 205 | * 206 | * @return bool 207 | */ 208 | public function doesMigrationFileExist($migration) 209 | { 210 | return $this->files->exists($this->getMigrationFilePath($migration)); 211 | } 212 | 213 | /** 214 | * Rollback a given migration. 215 | * 216 | * @param string $file 217 | * 218 | * @throws Exception 219 | * 220 | * @return mixed 221 | */ 222 | public function rollbackMigration($file) 223 | { 224 | $migration = $this->getMigrationObjectByFileName($file); 225 | 226 | $this->checkTransactionAndRun($migration, function () use ($migration, $file) { 227 | if ($migration->down() === false) { 228 | throw new Exception("Can't rollback migration: {$file}.php"); 229 | } 230 | }); 231 | 232 | $this->removeSuccessfulMigrationFromLog($file); 233 | } 234 | 235 | /** 236 | * Remove a migration name from the database so it can be run again. 237 | * 238 | * @param string $file 239 | * 240 | * @return void 241 | */ 242 | public function removeSuccessfulMigrationFromLog($file) 243 | { 244 | $this->database->removeSuccessfulMigrationFromLog($file); 245 | } 246 | 247 | /** 248 | * Delete migration file. 249 | * 250 | * @param string $migration 251 | * 252 | * @return bool 253 | */ 254 | public function deleteMigrationFile($migration) 255 | { 256 | return $this->files->delete($this->getMigrationFilePath($migration)); 257 | } 258 | 259 | /** 260 | * Get array of migrations that should be ran. 261 | * 262 | * @return array 263 | */ 264 | public function getMigrationsToRun() 265 | { 266 | $allMigrations = $this->getAllMigrations(); 267 | 268 | $ranMigrations = $this->getRanMigrations(); 269 | 270 | return array_diff($allMigrations, $ranMigrations); 271 | } 272 | 273 | /** 274 | * Move migration files. 275 | * 276 | * @param array $files 277 | * @param string $toDir 278 | * 279 | * @return int 280 | */ 281 | public function moveMigrationFiles($files = [], $toDir = '') 282 | { 283 | $toDir = trim($toDir ?: $this->dir_archive, '/'); 284 | $files = $files ?: $this->getAllMigrations(); 285 | $this->files->createDirIfItDoesNotExist("$this->dir/$toDir"); 286 | 287 | $count = 0; 288 | foreach ($files as $migration) { 289 | $from = $this->getMigrationFilePath($migration); 290 | $to = "$this->dir/$toDir/$migration.php"; 291 | 292 | if ($from == $to) { 293 | continue; 294 | } 295 | 296 | $flag = $this->files->move($from, $to); 297 | 298 | if ($flag) { 299 | $count++; 300 | } 301 | } 302 | 303 | return $count; 304 | } 305 | 306 | /** 307 | * Construct migration file name from migration name and current time. 308 | * 309 | * @param string $name 310 | * 311 | * @return string 312 | */ 313 | protected function constructFileName($name) 314 | { 315 | list($usec, $sec) = explode(' ', microtime()); 316 | 317 | $usec = substr($usec, 2, 6); 318 | 319 | return date('Y_m_d_His', $sec).'_'.$usec.'_'.$name; 320 | } 321 | 322 | /** 323 | * Get a migration class name by a migration file name. 324 | * 325 | * @param string $file 326 | * 327 | * @return string 328 | */ 329 | protected function getMigrationClassNameByFileName($file) 330 | { 331 | $fileExploded = explode('_', $file); 332 | 333 | $datePart = implode('_', array_slice($fileExploded, 0, 5)); 334 | $namePart = implode('_', array_slice($fileExploded, 5)); 335 | 336 | return Helpers::studly($namePart.'_'.$datePart); 337 | } 338 | 339 | /** 340 | * Replace all placeholders in the stub. 341 | * 342 | * @param string $template 343 | * @param array $replace 344 | * 345 | * @return string 346 | */ 347 | protected function replacePlaceholdersInTemplate($template, array $replace) 348 | { 349 | foreach ($replace as $placeholder => $value) { 350 | $template = str_replace("__{$placeholder}__", $value, $template); 351 | } 352 | 353 | return $template; 354 | } 355 | 356 | /** 357 | * Resolve a migration instance from a file. 358 | * 359 | * @param string $file 360 | * 361 | * @throws Exception 362 | * 363 | * @return MigrationInterface 364 | */ 365 | protected function getMigrationObjectByFileName($file) 366 | { 367 | $class = $this->getMigrationClassNameByFileName($file); 368 | 369 | $this->requireMigrationFile($file); 370 | 371 | $object = new $class(); 372 | 373 | if (!$object instanceof MigrationInterface) { 374 | throw new Exception("Migration class {$class} must implement Arrilot\\BitrixMigrations\\Interfaces\\MigrationInterface"); 375 | } 376 | 377 | return $object; 378 | } 379 | 380 | /** 381 | * Require migration file. 382 | * 383 | * @param string $file 384 | * 385 | * @return void 386 | */ 387 | protected function requireMigrationFile($file) 388 | { 389 | $this->files->requireFile($this->getMigrationFilePath($file)); 390 | } 391 | 392 | /** 393 | * Get path to a migration file. 394 | * 395 | * @param string $migration 396 | * 397 | * @return string 398 | */ 399 | protected function getMigrationFilePath($migration) 400 | { 401 | $files = Helpers::rGlob("$this->dir/$migration.php"); 402 | if (count($files) != 1) { 403 | throw new \Exception("Not found migration file"); 404 | } 405 | 406 | return $files[0]; 407 | } 408 | 409 | /** 410 | * If package arrilot/bitrix-iblock-helper is loaded then we should disable its caching to avoid problems. 411 | */ 412 | private function disableBitrixIblockHelperCache() 413 | { 414 | if (class_exists('\\Arrilot\\BitrixIblockHelper\\IblockId')) { 415 | IblockId::setCacheTime(0); 416 | if (method_exists('\\Arrilot\\BitrixIblockHelper\\IblockId', 'flushLocalCache')) { 417 | IblockId::flushLocalCache(); 418 | } 419 | } 420 | 421 | if (class_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock')) { 422 | HLBlock::setCacheTime(0); 423 | if (method_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock', 'flushLocalCache')) { 424 | HLBlock::flushLocalCache(); 425 | } 426 | } 427 | } 428 | 429 | /** 430 | * @param MigrationInterface $migration 431 | * @param callable $callback 432 | * @throws Exception 433 | */ 434 | protected function checkTransactionAndRun($migration, $callback) 435 | { 436 | if ($migration->useTransaction($this->use_transaction)) { 437 | $this->database->startTransaction(); 438 | Logger::log("Начало транзакции", Logger::COLOR_LIGHT_BLUE); 439 | try { 440 | $callback(); 441 | } catch (\Exception $e) { 442 | $this->database->rollbackTransaction(); 443 | Logger::log("Откат транзакции из-за ошибки '{$e->getMessage()}'", Logger::COLOR_LIGHT_RED); 444 | throw $e; 445 | } 446 | $this->database->commitTransaction(); 447 | Logger::log("Конец транзакции", Logger::COLOR_LIGHT_BLUE); 448 | } else { 449 | $callback(); 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/Storages/BitrixDatabaseStorage.php: -------------------------------------------------------------------------------- 1 | db = $DB; 35 | $this->table = $table; 36 | } 37 | 38 | /** 39 | * Check if a given table already exists. 40 | * 41 | * @return bool 42 | */ 43 | public function checkMigrationTableExistence() 44 | { 45 | return (bool) $this->db->query('SHOW TABLES LIKE "'.$this->table.'"')->fetch(); 46 | } 47 | 48 | /** 49 | * Create migration table. 50 | * 51 | * @return void 52 | */ 53 | public function createMigrationTable() 54 | { 55 | $this->db->query("CREATE TABLE {$this->table} (ID INT NOT NULL AUTO_INCREMENT, MIGRATION VARCHAR(255) NOT NULL, PRIMARY KEY (ID))"); 56 | } 57 | 58 | /** 59 | * Get an array of migrations the have been ran previously. 60 | * Must be ordered by order asc. 61 | * 62 | * @return array 63 | */ 64 | public function getRanMigrations() 65 | { 66 | $migrations = []; 67 | 68 | $dbRes = $this->db->query("SELECT MIGRATION FROM {$this->table} ORDER BY ID ASC"); 69 | while ($result = $dbRes->fetch()) { 70 | $migrations[] = $result['MIGRATION']; 71 | } 72 | 73 | return $migrations; 74 | } 75 | 76 | /** 77 | * Save migration name to the database to prevent it from running again. 78 | * 79 | * @param string $name 80 | * 81 | * @return void 82 | */ 83 | public function logSuccessfulMigration($name) 84 | { 85 | $this->db->insert($this->table, [ 86 | 'MIGRATION' => "'".$this->db->forSql($name)."'", 87 | ]); 88 | } 89 | 90 | /** 91 | * Remove a migration name from the database so it can be run again. 92 | * 93 | * @param string $name 94 | * 95 | * @return void 96 | */ 97 | public function removeSuccessfulMigrationFromLog($name) 98 | { 99 | $this->db->query("DELETE FROM {$this->table} WHERE MIGRATION = '".$this->db->forSql($name)."'"); 100 | } 101 | 102 | /** 103 | * Start transaction 104 | */ 105 | public function startTransaction() 106 | { 107 | $this->db->StartTransaction(); 108 | } 109 | 110 | /** 111 | * Commit transaction 112 | */ 113 | public function commitTransaction() 114 | { 115 | $this->db->Commit(); 116 | } 117 | 118 | /** 119 | * Rollback transaction 120 | */ 121 | public function rollbackTransaction() 122 | { 123 | $this->db->Rollback(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Storages/FileStorage.php: -------------------------------------------------------------------------------- 1 | exists($path) ? unlink($path) : false; 116 | } 117 | 118 | /** 119 | * Move file. 120 | * 121 | * @param string $path_from 122 | * @param string $path_to 123 | * 124 | * @return bool 125 | */ 126 | public function move($path_from, $path_to) 127 | { 128 | return $this->exists($path_from) ? rename($path_from, $path_to) : false; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/TemplatesCollection.php: -------------------------------------------------------------------------------- 1 | dir = dirname(__DIR__).'/templates'; 30 | 31 | $this->registerTemplate([ 32 | 'name' => 'default', 33 | 'path' => $this->dir.'/default.template', 34 | 'description' => 'Default migration template', 35 | ]); 36 | } 37 | 38 | /** 39 | * Register basic templates. 40 | */ 41 | public function registerBasicTemplates() 42 | { 43 | $templates = [ 44 | [ 45 | 'name' => 'add_iblock', 46 | 'path' => $this->dir.'/add_iblock.template', 47 | 'description' => 'Add iblock', 48 | ], 49 | [ 50 | 'name' => 'add_iblock_type', 51 | 'path' => $this->dir.'/add_iblock_type.template', 52 | 'description' => 'Add iblock type', 53 | ], 54 | [ 55 | 'name' => 'add_iblock_element_property', 56 | 'path' => $this->dir.'/add_iblock_element_property.template', 57 | 'description' => 'Add iblock element property', 58 | 'aliases' => [ 59 | 'add_iblock_prop', 60 | 'add_iblock_element_prop', 61 | 'add_element_prop', 62 | 'add_element_property', 63 | ], 64 | ], 65 | [ 66 | 'name' => 'add_uf', 67 | 'path' => $this->dir.'/add_uf.template', 68 | 'description' => 'Add user field (for sections, users e.t.c)', 69 | ], 70 | [ 71 | 'name' => 'add_table', 72 | 'path' => $this->dir.'/add_table.template', 73 | 'description' => 'Create table', 74 | 'aliases' => [ 75 | 'create_table', 76 | ], 77 | ], 78 | [ 79 | 'name' => 'delete_table', 80 | 'path' => $this->dir.'/delete_table.template', 81 | 'description' => 'Drop table', 82 | 'aliases' => [ 83 | 'drop_table', 84 | ], 85 | ], 86 | [ 87 | 'name' => 'query', 88 | 'path' => $this->dir.'/query.template', 89 | 'description' => 'Simple database query', 90 | ], 91 | ]; 92 | 93 | foreach ($templates as $template) { 94 | $this->registerTemplate($template); 95 | } 96 | } 97 | 98 | /** 99 | * Register templates for automigrations. 100 | */ 101 | public function registerAutoTemplates() 102 | { 103 | $templates = [ 104 | 'add_iblock', 105 | 'update_iblock', 106 | 'delete_iblock', 107 | 'add_iblock_element_property', 108 | 'update_iblock_element_property', 109 | 'delete_iblock_element_property', 110 | 'add_uf', 111 | 'update_uf', 112 | 'delete_uf', 113 | 'add_hlblock', 114 | 'update_hlblock', 115 | 'delete_hlblock', 116 | 'add_group', 117 | 'update_group', 118 | 'delete_group', 119 | ]; 120 | 121 | foreach ($templates as $template) { 122 | $this->registerTemplate([ 123 | 'name' => 'auto_'.$template, 124 | 'path' => $this->dir.'/auto/'.$template.'.template', 125 | ]); 126 | } 127 | } 128 | 129 | /** 130 | * Getter for registered templates. 131 | * 132 | * @return array 133 | */ 134 | public function all() 135 | { 136 | return $this->templates; 137 | } 138 | 139 | /** 140 | * Dynamically register migration template. 141 | * 142 | * @param array $template 143 | * 144 | * @return void 145 | */ 146 | public function registerTemplate($template) 147 | { 148 | $template = $this->normalizeTemplateDuringRegistration($template); 149 | 150 | $this->templates[$template['name']] = $template; 151 | 152 | $this->registerTemplateAliases($template, $template['aliases']); 153 | } 154 | 155 | /** 156 | * Path to the file where a template is located. 157 | * 158 | * @param string $name 159 | * 160 | * @return string 161 | */ 162 | public function getTemplatePath($name) 163 | { 164 | return $this->templates[$name]['path']; 165 | } 166 | 167 | /** 168 | * Find out template name from user input. 169 | * 170 | * @param string|null $template 171 | * 172 | * @return string 173 | */ 174 | public function selectTemplate($template) 175 | { 176 | if (is_null($template)) { 177 | return 'default'; 178 | } 179 | 180 | if (!array_key_exists($template, $this->templates)) { 181 | throw new RuntimeException("Template \"{$template}\" is not registered"); 182 | } 183 | 184 | return $template; 185 | } 186 | 187 | /** 188 | * Check template fields and normalize them. 189 | * 190 | * @param $template 191 | * 192 | * @return array 193 | */ 194 | protected function normalizeTemplateDuringRegistration($template) 195 | { 196 | if (empty($template['name'])) { 197 | throw new InvalidArgumentException('Impossible to register a template without "name"'); 198 | } 199 | 200 | if (empty($template['path'])) { 201 | throw new InvalidArgumentException('Impossible to register a template without "path"'); 202 | } 203 | 204 | $template['description'] = isset($template['description']) ? $template['description'] : ''; 205 | $template['aliases'] = isset($template['aliases']) ? $template['aliases'] : []; 206 | $template['is_alias'] = false; 207 | 208 | return $template; 209 | } 210 | 211 | /** 212 | * Register template aliases. 213 | * 214 | * @param array $template 215 | * @param array $aliases 216 | * 217 | * @return void 218 | */ 219 | protected function registerTemplateAliases($template, array $aliases = []) 220 | { 221 | foreach ($aliases as $alias) { 222 | $template['is_alias'] = true; 223 | $template['name'] = $alias; 224 | $template['aliases'] = []; 225 | 226 | $this->templates[$template['name']] = $template; 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /templates/add_iblock.template: -------------------------------------------------------------------------------- 1 | add([ 19 | 'NAME' => '__', 20 | 'CODE' => '__', 21 | 'SITE_ID' => 's1', 22 | 'IBLOCK_TYPE_ID' => '__', //символьный код группы инфоблока, 23 | 'VERSION' => 2, 24 | 'GROUP_ID' => ['2' =>'R'], 25 | 'LIST_PAGE_URL' => '__', 26 | 'DETAIL_PAGE_URL' => '__', 27 | ]); 28 | 29 | if (!$iblockId) { 30 | throw new MigrationException('Ошибка при добавлении инфоблока '.$ib->LAST_ERROR); 31 | } 32 | 33 | // свойства 34 | $propId = $this->addIblockElementProperty([ 35 | 'NAME' => '__', 36 | 'SORT' => 500, 37 | 'CODE' => '', 38 | 'PROPERTY_TYPE' => 'L', // Список 39 | 'LIST_TYPE' => 'C', // Тип списка - 'флажки' 40 | 'VALUES' => [ 41 | 'VALUE' => 'да', 42 | ], 43 | 'MULTIPLE' => 'N', 44 | 'IS_REQUIRED' => 'N', 45 | 'IBLOCK_ID' => $iblockId 46 | ]); 47 | } 48 | 49 | /** 50 | * Reverse the migration. 51 | * 52 | * @return mixed 53 | */ 54 | public function down() 55 | { 56 | $this->deleteIblockByCode('__'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /templates/add_iblock_element_property.template: -------------------------------------------------------------------------------- 1 | getIblockIdByCode('__'); 17 | 18 | $propId = $this->addIblockElementProperty([ 19 | 'NAME' => '__', 20 | 'SORT' => 500, 21 | 'CODE' => '__', 22 | 'PROPERTY_TYPE' => 'L', // Список 23 | 'LIST_TYPE' => 'C', // Тип списка - 'флажки' 24 | 'VALUES' => [ 25 | 'VALUE' => 'да', 26 | ], 27 | 'MULTIPLE' => 'N', 28 | 'IS_REQUIRED' => 'N', 29 | 'IBLOCK_ID' => $iblockId 30 | ]); 31 | } 32 | 33 | /** 34 | * Reverse the migration. 35 | * 36 | * @return mixed 37 | */ 38 | public function down() 39 | { 40 | $iblockId = $this->getIblockIdByCode('__'); 41 | 42 | $this->deleteIblockElementPropertyByCode($iblockId, '__'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/add_iblock_type.template: -------------------------------------------------------------------------------- 1 | Add([ 18 | 'ID'=>'__', 19 | 'SECTIONS'=>'Y', 20 | 'IN_RSS'=>'N', 21 | 'SORT'=>100, 22 | 'LANG'=>array( 23 | 'ru'=>array( 24 | 'NAME'=>'__', 25 | ) 26 | ) 27 | ]); 28 | 29 | if (!$cbtRes) { 30 | throw new MigrationException('Ошибка при добавлении типа инфоблока '.$cbt->LAST_ERROR); 31 | } 32 | } 33 | 34 | /** 35 | * Reverse the migration. 36 | * 37 | * @return mixed 38 | */ 39 | public function down() 40 | { 41 | CModule::IncludeModule('iblock'); 42 | 43 | global $DB; 44 | $DB->StartTransaction(); 45 | 46 | if (!CIBlockType::Delete('__')) { 47 | $DB->Rollback(); 48 | throw new MigrationException('Ошибка при удалении типа инфоблока __'); 49 | } 50 | 51 | $DB->Commit(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /templates/add_table.template: -------------------------------------------------------------------------------- 1 | new \Bitrix\Main\Entity\IntegerField("ID", [ 21 | 'primary' => true, 22 | 'unique' => true, 23 | 'required' => true, 24 | ]), 25 | "VALUE" => new \Bitrix\Main\Entity\StringField("VALUE", [ 26 | 'required' => true, 27 | ]), 28 | ]; 29 | $primary = ["ID"]; 30 | $autoincrement = ["ID"]; 31 | 32 | $this->db->createTable($this->table, $fields, $primary, $autoincrement); 33 | } 34 | 35 | /** 36 | * Reverse the migration. 37 | * 38 | * @return mixed 39 | * @throws MigrationException 40 | */ 41 | public function down() 42 | { 43 | $this->db->dropTable($this->table); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /templates/add_uf.template: -------------------------------------------------------------------------------- 1 | addUF([ 17 | /* 18 | * Идентификатор сущности, к которой будет привязано свойство. 19 | * Для секция формат следующий - IBLOCK_{IBLOCK_ID}_SECTION 20 | */ 21 | 'ENTITY_ID' => 'USER', 22 | /* Код поля. Всегда должно начинаться с UF_ */ 23 | 'FIELD_NAME' => '__', 24 | /* Указываем, что тип нового пользовательского свойства строка */ 25 | 'USER_TYPE_ID' => 'string', 26 | /* 27 | * XML_ID пользовательского свойства. 28 | * Используется при выгрузке в качестве названия поля 29 | */ 30 | 'XML_ID' => '', 31 | /* Сортировка */ 32 | 'SORT' => 500, 33 | /* Является поле множественным или нет */ 34 | 'MULTIPLE' => 'N', 35 | /* Обязательное или нет свойство */ 36 | 'MANDATORY' => 'N', 37 | /* 38 | * Показывать в фильтре списка. Возможные значения: 39 | * не показывать = N, точное совпадение = I, 40 | * поиск по маске = E, поиск по подстроке = S 41 | */ 42 | 'SHOW_FILTER' => 'N', 43 | /* 44 | * Не показывать в списке. Если передать какое-либо значение, 45 | * то будет считаться, что флаг выставлен (недоработка разработчиков битрикс). 46 | */ 47 | 'SHOW_IN_LIST' => '', 48 | /* 49 | * Не разрешать редактирование пользователем. 50 | * Если передать какое-либо значение, то будет считаться, 51 | * что флаг выставлен (недоработка разработчиков битрикс). 52 | */ 53 | 'EDIT_IN_LIST' => '', 54 | /* Значения поля участвуют в поиске */ 55 | 'IS_SEARCHABLE' => 'N', 56 | /* 57 | * Дополнительные настройки поля (зависят от типа). 58 | * В нашем случае для типа string 59 | */ 60 | 'SETTINGS' => array( 61 | /* Значение по умолчанию */ 62 | 'DEFAULT_VALUE' => '', 63 | /* Размер поля ввода для отображения */ 64 | 'SIZE' => '20', 65 | /* Количество строчек поля ввода */ 66 | 'ROWS' => '1', 67 | /* Минимальная длина строки (0 - не проверять) */ 68 | 'MIN_LENGTH' => '0', 69 | /* Максимальная длина строки (0 - не проверять) */ 70 | 'MAX_LENGTH' => '0', 71 | /* Регулярное выражение для проверки */ 72 | 'REGEXP' => '', 73 | ), 74 | /* Подпись в форме редактирования */ 75 | 'EDIT_FORM_LABEL' => array( 76 | 'ru' => 'Пользовательское свойство', 77 | 'en' => 'User field', 78 | ), 79 | /* Заголовок в списке */ 80 | 'LIST_COLUMN_LABEL' => array( 81 | 'ru' => 'Пользовательское свойство', 82 | 'en' => 'User field', 83 | ), 84 | /* Подпись фильтра в списке */ 85 | 'LIST_FILTER_LABEL' => array( 86 | 'ru' => 'Пользовательское свойство', 87 | 'en' => 'User field', 88 | ), 89 | /* Сообщение об ошибке (не обязательное) */ 90 | 'ERROR_MESSAGE' => array( 91 | 'ru' => 'Ошибка при заполнении пользовательского свойства', 92 | 'en' => 'An error in completing the user field', 93 | ), 94 | /* Помощь */ 95 | 'HELP_MESSAGE' => [ 96 | 'ru' => '', 97 | 'en' => '', 98 | ], 99 | ]); 100 | } 101 | 102 | /** 103 | * Reverse the migration. 104 | * 105 | * @return mixed 106 | * @throws MigrationException 107 | */ 108 | public function down() 109 | { 110 | $code = '__'; 111 | 112 | $id = $this->getUFIdByCode('USER', $code); 113 | if (!$id) { 114 | throw new MigrationException('Не найдено пользовательское свойство для удаления'); 115 | } 116 | 117 | (new CUserTypeEntity())->delete($id); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /templates/auto/add_group.template: -------------------------------------------------------------------------------- 1 | add($fields); 21 | 22 | if ($group->LAST_ERROR) { 23 | throw new MigrationException('Ошибка при добавлении группы '.$group->LAST_ERROR); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/add_hlblock.template: -------------------------------------------------------------------------------- 1 | isSuccess()) { 22 | $errors = $result->getErrorMessages(); 23 | throw new MigrationException('Ошибка при добавлении hl-блока '.implode(', ', $errors)); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/add_iblock.template: -------------------------------------------------------------------------------- 1 | add($fields); 20 | 21 | if (!$id) { 22 | throw new MigrationException('Ошибка при добавлении инфоблока '.$ib->LAST_ERROR); 23 | } 24 | } 25 | 26 | /** 27 | * Reverse the migration. 28 | * 29 | * @return mixed 30 | * @throws MigrationException 31 | */ 32 | public function down() 33 | { 34 | $id = $this->getIblockIdByCode(__code__); 35 | 36 | $this->db->startTransaction(); 37 | if (!CIBlock::delete($id)) { 38 | $this->db->rollbackTransaction(); 39 | throw new MigrationException('Ошибка при удалении инфоблока'); 40 | } 41 | 42 | $this->db->commitTransaction(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/auto/add_iblock_element_property.template: -------------------------------------------------------------------------------- 1 | add($fields); 20 | 21 | if (!$propId) { 22 | throw new MigrationException('Ошибка при добавлении свойства инфоблока '.$ibp->LAST_ERROR); 23 | } 24 | } 25 | 26 | /** 27 | * Reverse the migration. 28 | * 29 | * @return mixed 30 | * @throws MigrationException 31 | */ 32 | public function down() 33 | { 34 | $id = $this->getIblockPropIdByCode(__code__, __iblockId__); 35 | 36 | $ibp = new CIBlockProperty(); 37 | $deleted = $ibp->delete($id); 38 | 39 | if (!$deleted) { 40 | throw new MigrationException('Ошибка при удалении свойства инфоблока '.$ibp->LAST_ERROR); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/auto/add_uf.template: -------------------------------------------------------------------------------- 1 | addUF($fields); 19 | } 20 | 21 | /** 22 | * Reverse the migration. 23 | * 24 | * @return mixed 25 | * @throws MigrationException 26 | */ 27 | public function down() 28 | { 29 | $id = $this->getUFIdByCode(__entity__, __code__); 30 | if (!$id) { 31 | throw new MigrationException('Не найдено пользовательское свойство для удаления'); 32 | } 33 | 34 | (new CUserTypeEntity())->delete($id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /templates/auto/delete_group.template: -------------------------------------------------------------------------------- 1 | db->startTransaction(); 20 | if (!$group->delete(__id__)) { 21 | $this->db->rollbackTransaction(); 22 | throw new MigrationException('Ошибка при удалении группы '.$group->LAST_ERROR); 23 | } 24 | $this->db->commitTransaction(); 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/delete_hlblock.template: -------------------------------------------------------------------------------- 1 | isSuccess()) { 20 | $errors = $result->getErrorMessages(); 21 | throw new MigrationException('Ошибка при удалении hl-блока '.implode(', ', $errors)); 22 | } 23 | } 24 | 25 | /** 26 | * Reverse the migration. 27 | * 28 | * @return mixed 29 | * @throws MigrationException 30 | */ 31 | public function down() 32 | { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /templates/auto/delete_iblock.template: -------------------------------------------------------------------------------- 1 | getIblockIdByCode(__code__); 17 | 18 | $this->db->startTransaction(); 19 | if (!CIBlock::delete($id)) { 20 | $this->db->rollbackTransaction(); 21 | throw new MigrationException('Ошибка при удалении инфоблока'); 22 | } 23 | 24 | $this->db->commitTransaction(); 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/delete_iblock_element_property.template: -------------------------------------------------------------------------------- 1 | getIblockPropIdByCode(__code__, __iblockId__); 17 | 18 | $ibp = new CIBlockProperty(); 19 | $deleted = $ibp->delete($id); 20 | 21 | if (!$deleted) { 22 | throw new MigrationException('Ошибка при удалении свойства инфоблока '.$ibp->LAST_ERROR); 23 | } 24 | } 25 | 26 | /** 27 | * Reverse the migration. 28 | * 29 | * @return mixed 30 | */ 31 | public function down() 32 | { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /templates/auto/delete_uf.template: -------------------------------------------------------------------------------- 1 | getUFIdByCode(__entity__, __code__); 18 | 19 | $oUserTypeEntity = new CUserTypeEntity(); 20 | 21 | $dbResult = $oUserTypeEntity->delete($id); 22 | if (!$dbResult->result) { 23 | throw new MigrationException("Не удалось обновить удалить свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}"); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/update_group.template: -------------------------------------------------------------------------------- 1 | update(__id__, $fields); 21 | 22 | if ($group->LAST_ERROR) { 23 | throw new MigrationException('Ошибка при обновлении группы '.$group->LAST_ERROR); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/update_hlblock.template: -------------------------------------------------------------------------------- 1 | isSuccess()) { 22 | $errors = $result->getErrorMessages(); 23 | throw new MigrationException('Ошибка при обновлении hl-блока '.implode(', ', $errors)); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/auto/update_iblock.template: -------------------------------------------------------------------------------- 1 | getIblockIdByCode($fields['CODE']); 19 | $fields['ID'] = $iblockId; 20 | 21 | $ib = new CIBlock(); 22 | $updated = $ib->update($iblockId, $fields); 23 | 24 | if (!$updated) { 25 | throw new MigrationException('Ошибка при обновлении инфоблока '.$ib->LAST_ERROR); 26 | } 27 | } 28 | 29 | /** 30 | * Reverse the migration. 31 | * 32 | * @return mixed 33 | * @throws MigrationException 34 | */ 35 | public function down() 36 | { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /templates/auto/update_iblock_element_property.template: -------------------------------------------------------------------------------- 1 | getIblockPropIdByCode(__code__, __iblockId__); 19 | $fields['ID'] = $id; 20 | 21 | $ibp = new CIBlockProperty(); 22 | $updated = $ibp->update($id, $fields); 23 | 24 | if (!$updated) { 25 | throw new MigrationException('Ошибка при изменении свойства инфоблока '.$ibp->LAST_ERROR); 26 | } 27 | } 28 | 29 | /** 30 | * Reverse the migration. 31 | * 32 | * @return mixed 33 | */ 34 | public function down() 35 | { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /templates/auto/update_uf.template: -------------------------------------------------------------------------------- 1 | getUFIdByCode(__entity__, __code__); 18 | 19 | $oUserTypeEntity = new CUserTypeEntity(); 20 | 21 | $result = $oUserTypeEntity->update($id, $fields); 22 | if (!$result) { 23 | throw new MigrationException("Не удалось обновить пользовательское свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}"); 24 | } 25 | } 26 | 27 | /** 28 | * Reverse the migration. 29 | * 30 | * @return mixed 31 | * @throws MigrationException 32 | */ 33 | public function down() 34 | { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/default.template: -------------------------------------------------------------------------------- 1 | db->dropTable($this->table); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/query.template: -------------------------------------------------------------------------------- 1 | db->query($sql); 20 | } 21 | 22 | /** 23 | * Reverse the migration. 24 | * 25 | * @return mixed 26 | * @throws MigrationException 27 | */ 28 | public function down() 29 | { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/CommandTestCase.php: -------------------------------------------------------------------------------- 1 | run(new ArrayInput($input), new NullOutput()); 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | protected function getConfig() 36 | { 37 | return [ 38 | 'table' => 'migrations', 39 | 'dir' => 'migrations', 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/InstallCommandTest.php: -------------------------------------------------------------------------------- 1 | shouldAllowMockingProtectedMethods(); 13 | } 14 | 15 | public function testItCreatesMigrationTable() 16 | { 17 | $database = m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface'); 18 | $database->shouldReceive('checkMigrationTableExistence')->once()->andReturn(false); 19 | $database->shouldReceive('createMigrationTable')->once(); 20 | 21 | $command = $this->mockCommand($database); 22 | 23 | $this->runCommand($command); 24 | } 25 | 26 | public function testItDoesNotCreateATableIfItExists() 27 | { 28 | $database = m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface'); 29 | $database->shouldReceive('checkMigrationTableExistence')->once()->andReturn(true); 30 | $database->shouldReceive('createMigrationTable')->never(); 31 | 32 | $command = $this->mockCommand($database); 33 | $command->shouldReceive('abort')->once()->andThrow('DomainException'); 34 | 35 | $this->runCommand($command); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/MakeCommandTest.php: -------------------------------------------------------------------------------- 1 | shouldAllowMockingProtectedMethods(); 13 | } 14 | 15 | public function testItCreatesAMigrationFile() 16 | { 17 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 18 | $migrator->shouldReceive('createMigration')->once()->andReturn('2015_11_26_162220_bar'); 19 | 20 | $command = $this->mockCommand($migrator); 21 | $command->shouldReceive('message')->once(); 22 | 23 | $this->runCommand($command, ['name' => 'test_migration']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/MigrateCommandTest.php: -------------------------------------------------------------------------------- 1 | shouldAllowMockingProtectedMethods(); 13 | } 14 | 15 | public function testItMigratesNothingIfThereIsNoOutstandingMigrations() 16 | { 17 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 18 | $migrator->shouldReceive('getMigrationsToRun')->once()->andReturn([]); 19 | $migrator->shouldReceive('runMigration')->never(); 20 | 21 | $command = $this->mockCommand($migrator); 22 | $command->shouldReceive('info')->with('Nothing to migrate')->once(); 23 | 24 | $this->runCommand($command); 25 | } 26 | 27 | public function testItMigratesOutstandingMigrations() 28 | { 29 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 30 | $migrator->shouldReceive('getMigrationsToRun')->once()->andReturn([ 31 | '2015_11_26_162220_bar', 32 | ]); 33 | $migrator->shouldReceive('runMigration')->with('2015_11_26_162220_bar')->once(); 34 | 35 | $command = $this->mockCommand($migrator); 36 | $command->shouldReceive('message')->with('Migrated: 2015_11_26_162220_bar.php')->once(); 37 | $command->shouldReceive('info')->with('Nothing to migrate')->never(); 38 | 39 | $this->runCommand($command); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/MigratorTest.php: -------------------------------------------------------------------------------- 1 | mockDatabase(); 22 | $files = $this->mockFiles(); 23 | 24 | $files->shouldReceive('createDirIfItDoesNotExist')->once(); 25 | $files->shouldReceive('getContent')->once()->andReturn('some content'); 26 | $files->shouldReceive('putContent')->once()->andReturn(1000); 27 | 28 | $migrator = $this->createMigrator($database, $files); 29 | 30 | $this->assertRegExp('/[0-9]{4}_[0-9]{2}_[0-9]{2}_[0-9]{6}_[0-9]{6}_test_migration/', $migrator->createMigration('test_migration', null)); 31 | } 32 | 33 | /** 34 | * Create migrator. 35 | */ 36 | protected function createMigrator($database, $files) 37 | { 38 | $config = [ 39 | 'table' => 'migrations', 40 | 'dir' => 'migrations', 41 | ]; 42 | 43 | $templatesCollection = new TemplatesCollection($config); 44 | $templatesCollection->registerBasicTemplates(); 45 | 46 | return new Migrator($config, $templatesCollection, $database, $files); 47 | } 48 | 49 | /** 50 | * @return m\MockInterface 51 | */ 52 | protected function mockDatabase() 53 | { 54 | return m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface'); 55 | } 56 | 57 | /** 58 | * @return m\MockInterface 59 | */ 60 | protected function mockFiles() 61 | { 62 | return m::mock('Arrilot\BitrixMigrations\Interfaces\FileStorageInterface'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/RollbackCommandTest.php: -------------------------------------------------------------------------------- 1 | shouldAllowMockingProtectedMethods(); 14 | } 15 | 16 | public function testItRollbacksNothingIfThereIsNoMigrations() 17 | { 18 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 19 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([]); 20 | $migrator->shouldReceive('rollbackMigration')->never(); 21 | $migrator->shouldReceive('hardRollbackMigration')->never(); 22 | 23 | $command = $this->mockCommand($migrator); 24 | $command->shouldReceive('info')->with('Nothing to rollback')->once(); 25 | 26 | $this->runCommand($command); 27 | } 28 | 29 | public function testItRollsBackTheLastMigration() 30 | { 31 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 32 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([ 33 | '2014_11_26_162220_foo', 34 | '2015_11_26_162220_bar', 35 | ]); 36 | $migrator->shouldReceive('doesMigrationFileExist')->once()->andReturn(true); 37 | $migrator->shouldReceive('rollbackMigration')->once(); 38 | $migrator->shouldReceive('hardRollbackMigration')->never(); 39 | 40 | $command = $this->mockCommand($migrator); 41 | $command->shouldReceive('info')->with('Nothing to rollback')->never(); 42 | $command->shouldReceive('message')->with('Rolled back: 2015_11_26_162220_bar.php')->once(); 43 | 44 | $this->runCommand($command); 45 | } 46 | 47 | public function testItRollbackNonExistingMigration() 48 | { 49 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator'); 50 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([ 51 | '2014_11_26_162220_foo', 52 | '2015_11_26_162220_bar', 53 | ]); 54 | $migrator->shouldReceive('doesMigrationFileExist')->once()->andReturn(false); 55 | $migrator->shouldReceive('rollbackMigration')->never(); 56 | $migrator->shouldReceive('hardRollbackMigration')->never(); 57 | 58 | $command = $this->mockCommand($migrator); 59 | $command->shouldReceive('markRolledBackWithConfirmation')->with('2015_11_26_162220_bar')->once(); 60 | $command->shouldReceive('info')->with('Nothing to rollback')->never(); 61 | $command->shouldReceive('message')->with('Rolled back: 2015_11_26_162220_bar.php')->once(); 62 | 63 | $this->runCommand($command); 64 | } 65 | } 66 | --------------------------------------------------------------------------------