diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 4c7a71eb2..f99e19d07 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -20,9 +20,6 @@ * */ -use Combodo\iTop\Config\Validator\iTopConfigAstValidator; -use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator; - define('ITOP_APPLICATION', 'iTop'); define('ITOP_APPLICATION_SHORT', 'iTop'); @@ -1173,8 +1170,8 @@ class Config 'tracking_level_linked_set_default' => [ 'type' => 'integer', 'description' => 'Default tracking level if not explicitly set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)', - 'default' => LINKSET_TRACKING_LIST, - 'value' => LINKSET_TRACKING_LIST, + 'default' => LINKSET_TRACKING_NONE, + 'value' => LINKSET_TRACKING_NONE, 'source_of_value' => '', 'show_in_conf_sample' => false, ], diff --git a/datamodels/2.x/itop-core-update/dictionaries/cs.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/cs.dict.itop-core-update.php index 55d49ed70..a630ee904 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/cs.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/cs.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versions History~~', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade~~', - 'iTopUpdate:UI:DoBackup:Label' => 'Záložní soubory a databáze', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space~~', + 'iTopUpdate:UI:Backup:Label' => 'Záložní soubory a databáze', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space~~', 'iTopUpdate:UI:DiskFreeSpace' => 'Volné místo', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space~~', 'iTopUpdate:UI:DBDiskSpace' => 'Prostor obsazený Databází', diff --git a/datamodels/2.x/itop-core-update/dictionaries/da.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/da.dict.itop-core-update.php index 0e358f817..ad3087ee9 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/da.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/da.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versions History~~', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade~~', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database~~', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space~~', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database~~', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space~~', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space~~', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space~~', 'iTopUpdate:UI:DBDiskSpace' => 'Database disk space~~', diff --git a/datamodels/2.x/itop-core-update/dictionaries/de.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/de.dict.itop-core-update.php index 664cc6c92..a3bbad4ce 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/de.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/de.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup', 'iTopUpdate:UI:History' => 'Versionshistorie', 'iTopUpdate:UI:Progress' => 'Upgradefortschritt', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup von Dateien und Datenbank', - 'iTopUpdate:UI:DoBackup:Warning' => 'Wegen geringem verbleibenden Speicherplatz sollte kein Backup mehr erzeugt werden.', + 'iTopUpdate:UI:Backup:Label' => 'Backup von Dateien und Datenbank', + 'iTopUpdate:UI:Backup:Warning' => 'Wegen geringem verbleibenden Speicherplatz sollte kein Backup mehr erzeugt werden.', 'iTopUpdate:UI:DiskFreeSpace' => 'Freier Speicherplatz', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' Speicherplatz', 'iTopUpdate:UI:DBDiskSpace' => 'Datenbankgröße', diff --git a/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php index 03b081061..12b312247 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php @@ -59,8 +59,8 @@ Dict::Add('EN US', 'English', 'English', [ 'iTopUpdate:UI:History' => 'Versions History', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space', diff --git a/datamodels/2.x/itop-core-update/dictionaries/en_gb.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/en_gb.dict.itop-core-update.php index 25eb1a825..33ef89bf0 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/en_gb.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/en_gb.dict.itop-core-update.php @@ -59,8 +59,8 @@ Dict::Add('EN GB', 'British English', 'British English', [ 'iTopUpdate:UI:History' => 'Versions History', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space', diff --git a/datamodels/2.x/itop-core-update/dictionaries/es_cr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/es_cr.dict.itop-core-update.php index 89f6852a1..bdd18f2a3 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/es_cr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/es_cr.dict.itop-core-update.php @@ -42,8 +42,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [ 'iTopUpdate:UI:Setup' => 'Configuración '.ITOP_APPLICATION_SHORT, 'iTopUpdate:UI:History' => 'Historial de versiones', 'iTopUpdate:UI:Progress' => 'Progreso de actualización', - 'iTopUpdate:UI:DoBackup:Label' => 'Respaldo de archivos y base de datos', - 'iTopUpdate:UI:DoBackup:Warning' => 'El respaldo no está recomendado por el limitado espacio en el dispositivo', + 'iTopUpdate:UI:Backup:Label' => 'Respaldo de archivos y base de datos', + 'iTopUpdate:UI:Backup:Warning' => 'El respaldo no está recomendado por el limitado espacio en el dispositivo', 'iTopUpdate:UI:DiskFreeSpace' => 'Espacio libre en el dispositivo', 'iTopUpdate:UI:ItopDiskSpace' => 'Espacio en disco de '.ITOP_APPLICATION_SHORT, 'iTopUpdate:UI:DBDiskSpace' => 'Espacio en disco de base de datos', diff --git a/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php index ec69140c0..94286172f 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('FR FR', 'French', 'Français', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup', 'iTopUpdate:UI:History' => 'Historique des versions', 'iTopUpdate:UI:Progress' => 'Progression de la mise à jour', - 'iTopUpdate:UI:DoBackup:Label' => 'Sauvegarde de la base de données', - 'iTopUpdate:UI:DoBackup:Warning' => 'La sauvegarde n\'est pas conseillée à cause du manque de place disque disponible', + 'iTopUpdate:UI:Backup:Label' => 'Sauvegarde de la base de données', + 'iTopUpdate:UI:Backup:Warning' => 'La sauvegarde n\'est pas conseillée à cause du manque de place disque disponible', 'iTopUpdate:UI:DiskFreeSpace' => 'Taille disque disponible', 'iTopUpdate:UI:ItopDiskSpace' => 'Taille disque utilisée par l\'application', 'iTopUpdate:UI:DBDiskSpace' => 'Taille disque utilisée par la base de données', diff --git a/datamodels/2.x/itop-core-update/dictionaries/hu.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/hu.dict.itop-core-update.php index 0573fa06a..3fd1c1634 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/hu.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/hu.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Verziótörténet', 'iTopUpdate:UI:Progress' => 'A frissítés folyamata', - 'iTopUpdate:UI:DoBackup:Label' => 'Mentés fájlok és adatbázis', - 'iTopUpdate:UI:DoBackup:Warning' => 'A biztonsági mentés nem ajánlott a korlátozottan rendelkezésre álló lemezterület miatt.', + 'iTopUpdate:UI:Backup:Label' => 'Mentés fájlok és adatbázis', + 'iTopUpdate:UI:Backup:Warning' => 'A biztonsági mentés nem ajánlott a korlátozottan rendelkezésre álló lemezterület miatt.', 'iTopUpdate:UI:DiskFreeSpace' => 'Lemez szabad terület', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' lemezterület', 'iTopUpdate:UI:DBDiskSpace' => 'Adatbázis lemezterület', diff --git a/datamodels/2.x/itop-core-update/dictionaries/it.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/it.dict.itop-core-update.php index 5f98580d0..88ce20c31 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/it.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/it.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup', 'iTopUpdate:UI:History' => 'Storia delle Versioni', 'iTopUpdate:UI:Progress' => 'Progresso dell\'aggiornamento', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup dei file e del database', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup non raccomandato a causa dello spazio su disco limitato disponibile', + 'iTopUpdate:UI:Backup:Label' => 'Backup dei file e del database', + 'iTopUpdate:UI:Backup:Warning' => 'Backup non raccomandato a causa dello spazio su disco limitato disponibile', 'iTopUpdate:UI:DiskFreeSpace' => 'Spazio libero su disco', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' spazio su disco', 'iTopUpdate:UI:DBDiskSpace' => 'Spazio su disco del Database', diff --git a/datamodels/2.x/itop-core-update/dictionaries/ja.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/ja.dict.itop-core-update.php index ad9aad2c0..78ba3904f 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/ja.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/ja.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versions History~~', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade~~', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database~~', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space~~', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database~~', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space~~', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space~~', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space~~', 'iTopUpdate:UI:DBDiskSpace' => 'Database disk space~~', diff --git a/datamodels/2.x/itop-core-update/dictionaries/nl.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/nl.dict.itop-core-update.php index 271ea5690..6270d6687 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/nl.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/nl.dict.itop-core-update.php @@ -45,8 +45,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' setup', 'iTopUpdate:UI:History' => 'Versiegeschiedenis', 'iTopUpdate:UI:Progress' => 'Voortgang van de upgrade', - 'iTopUpdate:UI:DoBackup:Label' => 'Maak een backup van de bestanden en database', - 'iTopUpdate:UI:DoBackup:Warning' => 'Een backup maken wordt afgeraden doordat er weinig schijfruimte is', + 'iTopUpdate:UI:Backup:Label' => 'Maak een backup van de bestanden en database', + 'iTopUpdate:UI:Backup:Warning' => 'Een backup maken wordt afgeraden doordat er weinig schijfruimte is', 'iTopUpdate:UI:DiskFreeSpace' => 'Vrije schijfruimte', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' schijfgebruik', 'iTopUpdate:UI:DBDiskSpace' => 'Database schijfgebruik', diff --git a/datamodels/2.x/itop-core-update/dictionaries/pl.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/pl.dict.itop-core-update.php index afcb92c59..376ad37d4 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/pl.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/pl.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('PL PL', 'Polish', 'Polski', [ 'iTopUpdate:UI:Action' => 'Aktualizacja', 'iTopUpdate:UI:Setup' => 'Konfiguracja '.ITOP_APPLICATION_SHORT, 'iTopUpdate:UI:History' => 'Historia wersji', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade', - 'iTopUpdate:UI:DoBackup:Label' => 'Kopie zapasowe plików i bazy danych', - 'iTopUpdate:UI:DoBackup:Warning' => 'Tworzenie kopii zapasowych nie jest zalecane ze względu na ograniczoną ilość wolnego miejsca na dysku', + 'iTopUpdate:UI:Backup:Label' => 'Kopie zapasowe plików i bazy danych', + 'iTopUpdate:UI:Backup:Warning' => 'Tworzenie kopii zapasowych nie jest zalecane ze względu na ograniczoną ilość wolnego miejsca na dysku', 'iTopUpdate:UI:DiskFreeSpace' => 'Wolne miejsce na dysku', 'iTopUpdate:UI:ItopDiskSpace' => 'Przestrzeń dyskowa '.ITOP_APPLICATION_SHORT, 'iTopUpdate:UI:DBDiskSpace' => 'Przestrzeń dyskowa bazy danych', 'iTopUpdate:UI:FileUploadMaxSize' => 'Maksymalny rozmiar przesyłanego pliku', diff --git a/datamodels/2.x/itop-core-update/dictionaries/pt_br.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/pt_br.dict.itop-core-update.php index e8bbcd378..34aa05405 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/pt_br.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/pt_br.dict.itop-core-update.php @@ -46,8 +46,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versões anteriores', 'iTopUpdate:UI:Progress' => 'Progresso da atualização', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup de arquivos e banco de dados', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup não recomendado devido ao espaço em disco limitado', + 'iTopUpdate:UI:Backup:Label' => 'Backup de arquivos e banco de dados', + 'iTopUpdate:UI:Backup:Warning' => 'Backup não recomendado devido ao espaço em disco limitado', 'iTopUpdate:UI:DiskFreeSpace' => 'Espaço em disco disponível', 'iTopUpdate:UI:ItopDiskSpace' => 'Espaço em disco do '.ITOP_APPLICATION_SHORT, 'iTopUpdate:UI:DBDiskSpace' => 'Espaço em disco do banco de dados', diff --git a/datamodels/2.x/itop-core-update/dictionaries/ru.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/ru.dict.itop-core-update.php index 2ab106ec1..c8eec3b3e 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/ru.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/ru.dict.itop-core-update.php @@ -45,8 +45,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'История версий', 'iTopUpdate:UI:Progress' => 'Ход обновления', - 'iTopUpdate:UI:DoBackup:Label' => 'Создать резервную копию базы данных', - 'iTopUpdate:UI:DoBackup:Warning' => 'Резервное копирование не рекомендуется из-за ограниченного свободного места на диске', + 'iTopUpdate:UI:Backup:Label' => 'Создать резервную копию базы данных', + 'iTopUpdate:UI:Backup:Warning' => 'Резервное копирование не рекомендуется из-за ограниченного свободного места на диске', 'iTopUpdate:UI:DiskFreeSpace' => 'Доступное дисковое пространство', 'iTopUpdate:UI:ItopDiskSpace' => 'Размер приложения', 'iTopUpdate:UI:DBDiskSpace' => 'Размер базы данных', diff --git a/datamodels/2.x/itop-core-update/dictionaries/sk.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/sk.dict.itop-core-update.php index b5299012b..18be976b0 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/sk.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/sk.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versions History~~', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade~~', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database~~', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space~~', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database~~', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space~~', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space~~', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space~~', 'iTopUpdate:UI:DBDiskSpace' => 'Database disk space~~', diff --git a/datamodels/2.x/itop-core-update/dictionaries/tr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/tr.dict.itop-core-update.php index ffc058193..5c511200c 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/tr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/tr.dict.itop-core-update.php @@ -44,8 +44,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.' Setup~~', 'iTopUpdate:UI:History' => 'Versions History~~', 'iTopUpdate:UI:Progress' => 'Progress of the upgrade~~', - 'iTopUpdate:UI:DoBackup:Label' => 'Backup files and database~~', - 'iTopUpdate:UI:DoBackup:Warning' => 'Backup is not recommended due to limited available disk space~~', + 'iTopUpdate:UI:Backup:Label' => 'Backup files and database~~', + 'iTopUpdate:UI:Backup:Warning' => 'Backup is not recommended due to limited available disk space~~', 'iTopUpdate:UI:DiskFreeSpace' => 'Disk free space~~', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.' disk space~~', 'iTopUpdate:UI:DBDiskSpace' => 'Database disk space~~', diff --git a/datamodels/2.x/itop-core-update/dictionaries/zh_cn.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/zh_cn.dict.itop-core-update.php index 21dcedc57..433c7f610 100644 --- a/datamodels/2.x/itop-core-update/dictionaries/zh_cn.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/dictionaries/zh_cn.dict.itop-core-update.php @@ -55,8 +55,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [ 'iTopUpdate:UI:Setup' => ITOP_APPLICATION_SHORT.'安装', 'iTopUpdate:UI:History' => '版本历史', 'iTopUpdate:UI:Progress' => '升级进度', - 'iTopUpdate:UI:DoBackup:Label' => '备份文件和数据库', - 'iTopUpdate:UI:DoBackup:Warning' => '由于磁盘空间不足, 不建议备份', + 'iTopUpdate:UI:Backup:Label' => '备份文件和数据库', + 'iTopUpdate:UI:Backup:Warning' => '由于磁盘空间不足, 不建议备份', 'iTopUpdate:UI:DiskFreeSpace' => '磁盘剩余空间', 'iTopUpdate:UI:ItopDiskSpace' => ITOP_APPLICATION_SHORT.'的磁盘空间', 'iTopUpdate:UI:DBDiskSpace' => '数据库的磁盘空间', diff --git a/datamodels/2.x/itop-core-update/templates/SelectUpdateFile.html.twig b/datamodels/2.x/itop-core-update/templates/SelectUpdateFile.html.twig index 33d68114e..389641e68 100644 --- a/datamodels/2.x/itop-core-update/templates/SelectUpdateFile.html.twig +++ b/datamodels/2.x/itop-core-update/templates/SelectUpdateFile.html.twig @@ -65,11 +65,11 @@ {% UIFileSelect Standard {sName: 'file', sId: 'file','AddCSSClass':'ibo-is-hidden'} %} {% UIAlert ForWarning {'sId':'dobackup-warning', 'IsHidden':true} %} - {{ 'iTopUpdate:UI:DoBackup:Warning'|dict_s }} + {{ 'iTopUpdate:UI:Backup:Warning'|dict_s }} {% EndUIAlert %} {% UIContentBlock Standard {'aContainerClasses':['ibo-font-ral-nor-150']} %} - {% UIInput Standard {'sType':'checkbox', 'sId':'doBackup', 'sName':'doBackup', 'sValue':'1', 'IsChecked':true, 'CSSClasses':['ibo-input-checkbox', 'ibo-input--label-left'], 'Label':'iTopUpdate:UI:DoBackup:Label'|dict_s} %} + {% UIInput Standard {'sType':'checkbox', 'sId':'doBackup', 'sName':'doBackup', 'sValue':'1', 'IsChecked':true, 'CSSClasses':['ibo-input-checkbox', 'ibo-input--label-left'], 'Label':'iTopUpdate:UI:Backup:Label'|dict_s} %} {% EndUIContentBlock %} {% UIContentBlock Standard {'aContainerClasses':['ibo-font-ral-nor-150']} %} diff --git a/datamodels/2.x/itop-hub-connector/src/Controller/HubController.php b/datamodels/2.x/itop-hub-connector/src/Controller/HubController.php index f72c2cdec..2cdda81e0 100644 --- a/datamodels/2.x/itop-hub-connector/src/Controller/HubController.php +++ b/datamodels/2.x/itop-hub-connector/src/Controller/HubController.php @@ -13,7 +13,6 @@ use iTopExtensionsMap; use iTopMutex; use LoginWebPage; use MetaModel; -use MFCompiler; use RunTimeEnvironment; use SecurityException; use SetupLog; @@ -187,7 +186,7 @@ class HubController $oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup'); - $oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/); + $oRuntimeEnv->LoadData(false, false /* no sample data*/); $oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad'); diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index a8918b5cd..567353b84 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -35,6 +35,7 @@ require_once APPROOT.'setup/compiler.class.inc.php'; require_once APPROOT.'setup/extensionsmap.class.inc.php'; require_once APPROOT.'setup/moduleinstallation/AnalyzeInstallation.php'; require_once APPROOT.'/setup/moduleinstallation/InstallationChoicesToModuleConverter.php'; +require_once APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php'; define('MODULE_ACTION_OPTIONAL', 1); define('MODULE_ACTION_MANDATORY', 2); @@ -48,6 +49,8 @@ class RunTimeEnvironment "SetupInfo::ModuleIsSelected", ]; + private static bool $bMetamodelStarted = false; + /** * The name of the environment that the caller wants to build * @var string sFinalEnv @@ -75,7 +78,7 @@ class RunTimeEnvironment { // Actually read the modules available for the build environment, // but get the selection from the source environment and finally - // mark as (automatically) chosen alll the "remote" modules present in the + // mark as (automatically) chosen all the "remote" modules present in the // build environment (data/-modules) // The actual choices will be recorded by RecordInstallation below $this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv, $aExtraDirs); @@ -105,11 +108,20 @@ class RunTimeEnvironment * Return the full path to the compiled code (do not use after commit) * @return string */ - public function GetBuildDir() + public function GetBuildDir(): string { return APPROOT.'env-'.$this->sBuildEnv; } + /** + * Return the full path to the compiled code (do not use after commit) + * @return string + */ + public function GetBuildEnv(): string + { + return $this->sBuildEnv; + } + /** * Callback function for logging the queries run by the setup. * According to the documentation the function must be defined before passing it to call_user_func... @@ -126,12 +138,20 @@ class RunTimeEnvironment /** * Helper function to initialize the ORM and load the data model * from the given file + * * @param $oConfig object The configuration (volatile, not necessarily already on disk) * @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB + * @param bool $bUseCache + * + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MySQLException */ public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false): void { - require_once APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php'; +// if (self::$bMetamodelStarted && $bModelOnly) { +// return; +// } $sConfigFile = $oConfig->GetLoadedFile(); if (strlen($sConfigFile) > 0) { @@ -146,12 +166,156 @@ class RunTimeEnvironment } MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sBuildEnv); + self::$bMetamodelStarted = true; if ($this->oExtensionsMap === null) { $this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv); } } + /** + * @param string $sMode + * @param \Config $oConfig + * + * @throws \CoreException + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + */ + public function MigrateDataBeforeUpdateStructure(string $sMode, Config $oConfig): void + { + $this->InitDataModel($oConfig); + $sDBName = $oConfig->Get('db_name'); + $sDBPrefix = $oConfig->Get('db_subname'); + + // In 2.6.0 the 'fields' attribute has been moved from Query to QueryOQL for dependencies reasons + ModuleInstallerAPI::MoveColumnInDB($sDBPrefix.'priv_query', 'fields', $sDBPrefix.'priv_query_oql', 'fields'); + + + // Migrate application data format + // + // priv_internalUser caused troubles because MySQL transforms table names to lower case under Windows + // This becomes an issue when moving your installation data to/from Windows + // Starting 2.0, all table names must be lowercase + if ($sMode != 'install') { + SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)"); + // This command will have no effect under Windows... + // and it has been written in two steps so as to make it work under windows! + CMDBSource::SelectDB($sDBName); + try { + $sRepair = "RENAME TABLE `{$sDBPrefix}priv_internalUser` TO `{$sDBPrefix}priv_internaluser_other`, `{$sDBPrefix}priv_internaluser_other` TO `{$sDBPrefix}priv_internaluser`"; + CMDBSource::Query($sRepair); + } + catch (Exception $e) { + SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)"); + } + + // let's remove the records in priv_change which have no counterpart in priv_changeop + SetupLog::Info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records"); + CMDBSource::SelectDB($sDBName); + try { + $sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`"; + $iTotalCount = (int)CMDBSource::QueryToScalar($sTotalCount); + SetupLog::Info("There is a total of $iTotalCount records in {$sDBPrefix}priv_change."); + + $sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL"; + $iOrphanCount = (int)CMDBSource::QueryToScalar($sOrphanCount); + SetupLog::Info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0 * $iOrphanCount) / $iTotalCount)).'%)'); + if ($iOrphanCount > 0) { + //N°3793 + if ($iOrphanCount > 100000) { + SetupLog::Info("There are too much useless records ($iOrphanCount) in {$sDBPrefix}priv_change. Cleanup cannot be done during setup."); + } else { + SetupLog::Info('Removing the orphan records...'); + $sCleanup = "DELETE FROM `{$sDBPrefix}priv_change` USING `{$sDBPrefix}priv_change` LEFT JOIN `{$sDBPrefix}priv_changeop` ON `{$sDBPrefix}priv_change`.id = `{$sDBPrefix}priv_changeop`.changeid WHERE `{$sDBPrefix}priv_changeop`.id IS NULL;"; + CMDBSource::Query($sCleanup); + SetupLog::Info('Cleanup completed successfully.'); + } + } else { + SetupLog::Info('Ok, nothing to cleanup.'); + } + } + catch (Exception $e) { + SetupLog::Info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage()); + } + } + } + + public function MigrateDataAfterUpdateStructure(string $sMode, Config $oConfig): void + { + $sDBName = $oConfig->Get('db_name'); + $sDBPrefix = $oConfig->Get('db_subname'); + + // priv_change now has an 'origin' field to distinguish between the various input sources + // Let's initialize the field with 'interactive' for all records where it's null + // Then check if some records should hold a different value, based on a pattern matching in the userinfo field + CMDBSource::SelectDB($sDBName); + try { + $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change` WHERE `origin` IS NULL"; + $iCount = (int)CMDBSource::QueryToScalar($sCount); + if ($iCount > 0) { + SetupLog::Info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)"); + + // By default all uninitialized values are considered as 'interactive' + $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'interactive' WHERE `origin` IS NULL"; + CMDBSource::Query($sInit); + + // CSV Import was identified by the comment at the end + $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-import.php' WHERE `userinfo` LIKE '%Web Service (CSV)'"; + CMDBSource::Query($sInit); + + // CSV Import was identified by the comment at the end + $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'"; + CMDBSource::Query($sInit); + + // Syncho data sources were identified by the comment at the end + // Unfortunately the comment is localized, so we have to search for all possible patterns + $sCurrentLanguage = Dict::GetUserLanguage(); + $aSuffixes = []; + foreach (array_keys(Dict::GetLanguages()) as $sLangCode) { + Dict::SetUserLanguage($sLangCode); + $sSuffix = CMDBSource::Quote('%'.Dict::S('Core:SyncDataExchangeComment')); + $aSuffixes[$sSuffix] = true; + } + Dict::SetUserLanguage($sCurrentLanguage); + $sCondition = '`userinfo` LIKE '.implode(' OR `userinfo` LIKE ', array_keys($aSuffixes)); + + $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)"; + CMDBSource::Query($sInit); + + SetupLog::Info("Initialization of '{$sDBPrefix}priv_change.origin' completed."); + } else { + SetupLog::Info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do."); + } + } + catch (Exception $e) { + SetupLog::Error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage()); + } + + // priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns + // Let's initialize the field with 'planned' or 'error' for all records were it's null + CMDBSource::SelectDB($sDBName); + try { + $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_async_task` WHERE `status` IS NULL"; + $iCount = (int)CMDBSource::QueryToScalar($sCount); + if ($iCount > 0) { + SetupLog::Info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)"); + + $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)"; + CMDBSource::Query($sInit); + + $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'error' WHERE (`status` IS NULL) AND (`started` IS NOT NULL)"; + CMDBSource::Query($sInit); + + SetupLog::Info("Initialization of '{$sDBPrefix}priv_async_task.status' completed."); + } else { + SetupLog::Info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do."); + } + } + catch (Exception $e) { + SetupLog::Error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage()); + } + } + /** * Analyzes the current installation and the possibilities * @@ -189,6 +353,36 @@ class RunTimeEnvironment return AnalyzeInstallation::GetInstance()->AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency, $aModulesToLoad); } + /** + * @param \Config $oConfig + * @param string $sDataModelVersion + * @param array $aSelectedModuleCodes + * @param array $aSelectedExtensionCodes + * @param string|null $sInstallComment + * + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MySQLException + * @throws \Exception + */ + public function DoCreateConfig(Config $oConfig, string $sDataModelVersion, array $aSelectedModuleCodes, array $aSelectedExtensionCodes, ?string $sInstallComment = null) + { + $oConfig->Set('access_mode', ACCESS_FULL); + + // Record which modules are installed... + $this->InitDataModel($oConfig, true); // load data model and connect to the database + + if (!$this->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sInstallComment)) { + throw new Exception('Failed to record the installation information'); + } + + $this->WriteConfigFileSafe($oConfig); + + // Ready to go !! + require_once(APPROOT.'core/dict.class.inc.php'); + MetaModel::ResetAllCaches(); + } + /** * @param Config $oConfig * @@ -426,6 +620,72 @@ class RunTimeEnvironment return true; } + /** + * @param \Config $oConfig + * @param string $sMode + * @param array|null $aSelectedModules null means all + * + * @return void + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function UpdateDBSchema(Config $oConfig, string $sMode, ?array $aSelectedModules = null): void + { + $this->InitDataModel($oConfig, true); // load data model only + + // Module specific actions (migrate the data) + $aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir()); + $this->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation', $aSelectedModules); + + if (!$this->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { + throw new Exception("Failed to create/upgrade the database structure for environment"); + } + } + + /** + * @param \Config $oConfig + * @param array|null $aSelectedModules null means all + * + * @return void + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MySQLException + * @throws \Exception + */ + public function AfterDBCreate(Config $oConfig, string $sMode, ?array $aSelectedModules = null, array $aAdminParams = []): void + { + $this->InitDataModel($oConfig); // load data model and connect to the database + + // Perform here additional DB setup... profiles, etc... + // + $aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir()); + $this->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation', $aSelectedModules); + + $this->UpdatePredefinedObjects(); + + + if ($sMode == 'install') { + SetupLog::Info('CreateAdminAccount'); + + $sAdminUser = $aAdminParams['user']; + $sAdminPwd = $aAdminParams['pwd']; + $sAdminLanguage = $aAdminParams['language']; + + if (!UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sAdminLanguage)) { + throw(new Exception("Failed to create the administrator account '$sAdminUser'")); + } else { + SetupLog::Info("Administrator account '$sAdminUser' created."); + } + } + + // Perform final setup tasks here + // + $this->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup', $aSelectedModules); + } + public function UpdatePredefinedObjects() { // Have it work fine even if the DB has been set in read-only mode for the users @@ -897,12 +1157,28 @@ class RunTimeEnvironment } } + public function DoLoadData(Config $oConfig, bool $bSampleData = false, $aSelectedModules = null) + { + $this->InitDataModel($oConfig, false); // load data model and connect to the database + + // Perform here additional DB setup... profiles, etc... + // + $aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir()); + + $this->LoadData($aAvailableModules, $bSampleData, $aSelectedModules); + + $this->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad', $aSelectedModules); + + } + /** * Load data from XML files for the selected modules (structural data and/or sample data) * - * @param array[] $aAvailableModules All available modules and their definition - * @param bool $bSampleData Wether or not to load sample data - * @param null|string[] $aSelectedModules List of selected modules + * @param array $aAvailableModules + * @param bool $bSampleData Whether or not to load sample data + * @param null $aSelectedModules List of selected modules + * + * @throws \CoreException */ public function LoadData($aAvailableModules, $bSampleData, $aSelectedModules = null) { @@ -1233,6 +1509,34 @@ class RunTimeEnvironment } } + /** + * @param \Config $oConfig + * @param string $sBackupFileFormat + * @param string $sSourceConfigFile + * @param string|null $sMySQLBinDir + * + * @throws \BackupException + * @throws \CoreException + * @throws \MySQLException + * @since 2.5.0 uses a {@link Config} object to store DB parameters + */ + public function Backup(Config $oConfig, string $sBackupFileFormat, string $sSourceConfigFile, ?string $sMySQLBinDir = null): void + { + $oBackup = new SetupDBBackup($oConfig); + $sTargetFile = $oBackup->MakeName($sBackupFileFormat); + if (!empty($sMySQLBinDir)) { + $oBackup->SetMySQLBinDir($sMySQLBinDir); + } + + CMDBSource::InitFromConfig($oConfig); + $oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile); + } + + public function GetFinalEnv(): string + { + return $this->sFinalEnv; + } + protected function IsInItop(string $sPath): bool { $sFileRealPath = realpath($sPath); diff --git a/setup/sequencers/ApplicationInstallSequencer.php b/setup/sequencers/ApplicationInstallSequencer.php index 580cc78e3..dbb134781 100644 --- a/setup/sequencers/ApplicationInstallSequencer.php +++ b/setup/sequencers/ApplicationInstallSequencer.php @@ -40,314 +40,118 @@ require_once(APPROOT.'setup/SetupDBBackup.php'); */ class ApplicationInstallSequencer extends StepSequencer { - protected Parameters $oParams; - protected static bool $bMetaModelStarted = false; - - protected Config $oConfig; - - protected RunTimeEnvironment $oRunTimeEnvironment; - /** - * @param \Parameters $oParams - * - * @throws \ConfigException - * @throws \CoreException + * @inherit */ - public function __construct(Parameters $oParams, ?RunTimeEnvironment $oRunTimeEnvironment = null) - { - if (is_null($oRunTimeEnvironment)) { - $sEnvironment = $oParams->Get('target_env', 'production'); - $oRunTimeEnvironment = new RunTimeEnvironment($sEnvironment, false); - } - $this->oRunTimeEnvironment = $oRunTimeEnvironment; - - $this->oParams = $oParams; - - $aParamValues = $oParams->GetParamForConfigArray(); - $this->oConfig = new Config(); - $this->oConfig->UpdateFromParams($aParamValues); - utils::SetConfig($this->oConfig); - } - - /** - * @return string - */ - protected function GetTargetEnv() - { - $sTargetEnvironment = $this->oParams->Get('target_env', ''); - if ($sTargetEnvironment !== '') { - return $sTargetEnvironment; - } - - return 'production'; - } - - /** - * @return string - */ - protected function GetTargetDir() - { - $sTargetEnv = $this->GetTargetEnv(); - return 'env-'.$sTargetEnv; - } - - protected function GetConfig() - { - $sTargetEnvironment = $this->GetTargetEnv(); - $sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; - try { - $oConfig = new Config($sConfigFile); - } catch (Exception $e) { - return null; - } - - $aParamValues = $this->oParams->GetParamForConfigArray(); - $oConfig->UpdateFromParams($aParamValues); - - return $oConfig; - } - - - - /** - * Executes the next step of the installation and reports about the progress - * and the next step to perform - * - * @param string $sStep The identifier of the step to execute - * @param string|null $sInstallComment - * - * @return array (status => , message => , percentage-completed => , next-step => , next-step-label => ) - */ - public function ExecuteStep($sStep = '', $sInstallComment = null) + public function ExecuteStep($sStep = '', $sInstallComment = null): array { try { + /** + * @since 3.2.0 move the ContextTag init at the very beginning of the method + * @noinspection PhpUnusedLocalVariableInspection + */ + $oContextTag = new ContextTag(ContextTag::TAG_SETUP); $fStart = microtime(true); SetupLog::Info("##### STEP {$sStep} start"); $this->EnterReadOnlyMode(); switch ($sStep) { case '': - if (in_array('log-parameters', $this->oParams->Get('optional_steps', []))) { - return $this->GetNextStep('log-parameters', 'Log parameters', 0); - } - return $this->GetNextStep('copy', 'Copying data model files', 5); + return $this->GetNextStep('log-parameters', 'Log parameters', 0); case 'log-parameters': - $this->DoLogParameters('data-audit-', 'Data Audit'); - return $this->GetNextStep('copy', 'Copying data model files', 5); - - $this->DoLogParameters(); - - $aResult = [ - 'status' => self::OK, - 'message' => '', - 'percentage-completed' => 0, - 'next-step' => 'copy', - 'next-step-label' => 'Copying data model files', - ]; - break; - - case 'copy': - $aPreinstall = $this->oParams->Get('preinstall'); - $aCopies = $aPreinstall['copies'] ?? []; - - $this->DoCopy($aCopies); - $sReport = "Copying..."; - - $aResult = [ - 'status' => self::OK, - 'message' => $sReport, - ]; - if (isset($aPreinstall['backup'])) { - $aResult['next-step'] = 'backup'; - $aResult['next-step-label'] = 'Performing a backup of the database'; - $aResult['percentage-completed'] = 20; - } else { - $aResult['next-step'] = 'compile'; - $aResult['next-step-label'] = 'Compiling the data model'; - $aResult['percentage-completed'] = 20; + if (array_key_exists('log-parameters', $this->oParams->Get('optional_steps', []))) { + $this->DoLogParameters(); } - break; + + return $this->GetNextStep('backup', 'Performing a backup of the database', 20); case 'backup': - $aPreinstall = $this->oParams->Get('preinstall'); - // __DB__-%Y-%m-%d - $sDestination = $aPreinstall['backup']['destination']; - $sSourceConfigFile = $aPreinstall['backup']['configuration_file']; - $sMySQLBinDir = $this->oParams->Get('mysql_bindir', null); - $this->DoBackup($sDestination, $sSourceConfigFile, $sMySQLBinDir); + if (array_key_exists('backup', $this->oParams->Get('optional_steps', []))) { - $aResult = [ - 'status' => self::OK, - 'message' => "Created backup", - 'next-step' => 'compile', - 'next-step-label' => 'Compiling the data model', - 'percentage-completed' => 20, - ]; - break; - - case 'compile': - $aSelectedModules = $this->oParams->Get('selected_modules'); - $sSourceDir = $this->oParams->Get('source_dir', 'datamodels/latest'); - $sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions'); - $aMiscOptions = $this->oParams->Get('options', []); - $aRemovedExtensionCodes = $this->oParams->Get('removed_extensions', []); - - $bUseSymbolicLinks = null; - if ((isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'])) { - if (function_exists('symlink')) { - $bUseSymbolicLinks = true; - SetupLog::Info("Using symbolic links instead of copying data model files (for developers only!)"); - } else { - SetupLog::Info("Symbolic links (function symlinks) does not seem to be supported on this platform (OS/PHP version)."); - } + $aBackupOptions = $this->oParams->Get('optional_steps')['backup']; + // __DB__-%Y-%m-%d + $sDestination = $aBackupOptions['destination']; + $sSourceConfigFile = $aBackupOptions['configuration_file']; + $sMySQLBinDir = $this->oParams->Get('mysql_bindir', null); + $this->oRunTimeEnvironment->Backup($this->oConfig, $sDestination, $sSourceConfigFile, $sMySQLBinDir); } - $this->DoCompile( - $aRemovedExtensionCodes, - $aSelectedModules, - $sSourceDir, - $sExtensionDir, - $bUseSymbolicLinks - ); + return $this->GetNextStep('migrate-before', 'Migrate data before database upgrade', 30); - $sNextStep = 'db-schema'; - $sNextStepLabel = 'Updating database schema'; - - $aResult = [ - 'status' => self::OK, - 'message' => '', - 'next-step' => $sNextStep, - 'next-step-label' => $sNextStepLabel, - 'percentage-completed' => 40, - ]; - break; + case 'migrate-before': + if (array_key_exists('migrate-before', $this->oParams->Get('optional_steps', []))) { + $this->oRunTimeEnvironment->MigrateDataBeforeUpdateStructure($this->oParams->Get('mode'), $this->GetConfig()); + } + return $this->GetNextStep('db-schema', 'Updating database schema', 40); case 'db-schema': $aSelectedModules = $this->oParams->Get('selected_modules', []); + $this->DoUpdateDBSchema($this->GetConfig(), $aSelectedModules); - $this->DoUpdateDBSchema( - $aSelectedModules - ); + return $this->GetNextStep('migrate-after', 'Migrate data after database upgrade', 50); - $aResult = [ - 'status' => self::OK, - 'message' => '', - 'next-step' => 'after-db-create', - 'next-step-label' => 'Creating profiles', - 'percentage-completed' => 60, - ]; - break; + case 'migrate-after': + if (array_key_exists('migrate-after', $this->oParams->Get('optional_steps', []))) { + $this->oRunTimeEnvironment->MigrateDataAfterUpdateStructure($this->oParams->Get('mode'), $this->GetConfig()); + } + return $this->GetNextStep('after-db-create', 'Load data after database create', 60); case 'after-db-create': $aAdminParams = $this->oParams->Get('admin_account'); $aSelectedModules = $this->oParams->Get('selected_modules', []); + $sMode = $this->oParams->Get('mode'); - $this->AfterDBCreate( - $aAdminParams, - $aSelectedModules - ); + $this->oRunTimeEnvironment->AfterDBCreate($this->GetConfig(), $sMode, $aSelectedModules, $aAdminParams); - $aResult = [ - 'status' => self::OK, - 'message' => '', - 'next-step' => 'load-data', - 'next-step-label' => 'Loading data', - 'percentage-completed' => 80, - ]; - break; + return $this->GetNextStep('load-data', 'Loading data', 70); case 'load-data': $aSelectedModules = $this->oParams->Get('selected_modules', []); $bSampleData = ($this->oParams->Get('sample_data', 0) == 1); - $this->DoLoadFiles( - $aSelectedModules, - $bSampleData - ); + $this->oRunTimeEnvironment->DoLoadData($this->GetConfig(),$bSampleData, $aSelectedModules); - $aResult = [ - 'status' => self::INFO, - 'message' => 'All data loaded', - 'next-step' => 'create-config', - 'next-step-label' => 'Creating the configuration File', - 'percentage-completed' => 99, - ]; - break; + return $this->GetNextStep('create-config', 'Creating the configuration File', 80, 'All data loaded'); case 'create-config': - $sPreviousConfigFile = $this->oParams->Get('previous_configuration_file', ''); $sDataModelVersion = $this->oParams->Get('datamodel_version', '0.0.0'); $aSelectedModuleCodes = $this->oParams->Get('selected_modules', []); $aSelectedExtensionCodes = $this->oParams->Get('selected_extensions', []); - $this->DoCreateConfig( - $sPreviousConfigFile, + $this->oRunTimeEnvironment->DoCreateConfig($this->GetConfig(), $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sInstallComment ); - $aResult = [ - 'status' => self::INFO, - 'message' => 'Configuration file created', - 'next-step' => '', - 'next-step-label' => 'Completed', - 'percentage-completed' => 100, - ]; - break; + return $this->GetNextStep('commit', 'Finalize', 95); + + case 'commit': + $this->oRunTimeEnvironment->Commit(); + return $this->GetNextStep('', 'Completed', 100); default: - $aResult = [ - 'status' => self::ERROR, - 'message' => '', - 'next-step' => '', - 'next-step-label' => "Unknown setup step '$sStep'.", - 'percentage-completed' => 100, - ]; - break; + return $this->GetNextStep('', "Unknown setup step '$sStep'.", 100, '', self::ERROR); } - $this->ExitReadOnlyMode(); - } catch (Exception $e) { - $aResult = [ - 'status' => self::ERROR, - 'message' => $e->getMessage(), - 'next-step' => '', - 'next-step-label' => '', - 'percentage-completed' => 100, - 'error_code' => $e->getCode(), - ]; - - $this->ReportException($e); - } finally { - $fDuration = round(microtime(true) - $fStart, 2); - SetupLog::Info("##### STEP {$sStep} duration: {$fDuration}s"); } + catch (Exception $e) { + $this->ReportException($e); + $aResult = $this->GetNextStep('', '', 100, $e->getMessage(), self::ERROR); + $aResult['error_code'] = $e->getCode(); + return $aResult; + } + finally { + $this->ExitReadOnlyMode(); + $fDuration = round(microtime(true) - $fStart, 2); + SetupLog::Info("##### STEP {$sStep} duration: {$fDuration}s"); + } - return $aResult; - } - - protected function ReportException(Exception $e) - { - SetupLog::Error('An exception occurred: '.$e->getMessage().' at line '.$e->getLine().' in file '.$e->getFile()); - $idx = 0; - // Log the call stack, but not the parameters since they may contain passwords or other sensitive data - SetupLog::Ok("Call stack:"); - foreach ($e->getTrace() as $aTrace) { - $sLine = empty($aTrace['line']) ? "" : $aTrace['line']; - $sFile = empty($aTrace['file']) ? "" : $aTrace['file']; - $sClass = empty($aTrace['class']) ? "" : $aTrace['class']; - $sType = empty($aTrace['type']) ? "" : $aTrace['type']; - $sFunction = empty($aTrace['function']) ? "" : $aTrace['function']; - $sVerb = empty($sClass) ? $sFunction : "$sClass{$sType}$sFunction"; - SetupLog::Ok("#$idx $sFile($sLine): $sVerb(...)"); - $idx++; - } } protected function EnterReadOnlyMode() { - if ($this->GetTargetEnv() != 'production') { + if ($this->oRunTimeEnvironment->GetFinalEnv() != 'production') { return; } @@ -360,7 +164,7 @@ class ApplicationInstallSequencer extends StepSequencer protected function ExitReadOnlyMode() { - if ($this->GetTargetEnv() != 'production') { + if ($this->oRunTimeEnvironment->GetFinalEnv() != 'production') { return; } @@ -371,81 +175,6 @@ class ApplicationInstallSequencer extends StepSequencer SetupUtils::ExitReadOnlyMode(); } - /** - * @param string $sBackupFileFormat - * @param string $sSourceConfigFile - * @param string $sMySQLBinDir - * - * @throws \BackupException - * @throws \CoreException - * @throws \MySQLException - * @since 2.5.0 uses a {@link Config} object to store DB parameters - */ - protected function DoBackup($sBackupFileFormat, $sSourceConfigFile, $sMySQLBinDir = null) - { - $oBackup = new SetupDBBackup($this->oConfig); - $sTargetFile = $oBackup->MakeName($sBackupFileFormat); - if (!empty($sMySQLBinDir)) { - $oBackup->SetMySQLBinDir($sMySQLBinDir); - } - - CMDBSource::InitFromConfig($this->oConfig); - $oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile); - } - - protected function GetModelInfoPath(string $sEnv): string - { - return APPROOT."data/beforecompilation_".$sEnv."_modelinfo.json"; - } - - protected function SaveModelInfo(string $sEnvironment): bool - { - $sModelInfoPath = $this->GetModelInfoPath($sEnvironment); - try { - $aModelInfo = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($sEnvironment); - } catch (Exception $e) { - //logged already - return is_file($sModelInfoPath); - } - - return (bool) file_put_contents($sModelInfoPath, json_encode($aModelInfo)); - } - - protected function GetPreviousModelInfo(string $sEnvironment): array - { - $sContent = file_get_contents($this->GetModelInfoPath($sEnvironment)); - $aModelInfo = json_decode($sContent, true); - - if (false === $aModelInfo) { - throw new Exception("Could not read (before compilation) previous model to audit data"); - } - - return $aModelInfo; - } - - protected function IsSetupDataAuditEnabled($sSkipDataAudit, array $aParamValues): bool - { - if ($sSkipDataAudit === "checked") { - SetupLog::Info("Setup data audit disabled", null, ['skip-data-audit' => $sSkipDataAudit]); - return false; - } - - $sMode = $aParamValues['mode']; - if ($sMode !== "upgrade") { - //first install - return false; - } - - $sPath = APPROOT.$this->GetTargetDir(); - if (!is_dir($sPath)) { - SetupLog::Info("Reinstallation of an iTop from a backup (No ".$this->GetTargetDir()." found). Setup data audit disabled", null, ['skip-data-audit' => $sSkipDataAudit]); - - return false; - } - - return true; - } - /** * @param $aSelectedModules * @@ -453,84 +182,15 @@ class ApplicationInstallSequencer extends StepSequencer * @throws \CoreException * @throws \MySQLException */ - protected function DoUpdateDBSchema($aSelectedModules) + protected function DoUpdateDBSchema(Config $oConfig, $aSelectedModules) { - $sTargetEnvironment = $this->GetTargetEnv(); - $sModulesDir = $this->GetTargetDir(); + $sTargetEnvironment = $this->oRunTimeEnvironment->GetBuildEnv(); $aParamValues = $this->oParams->GetParamForConfigArray(); - /** - * @since 3.2.0 move the ContextTag init at the very beginning of the method - * @noinspection PhpUnusedLocalVariableInspection - */ - $oContextTag = new ContextTag(ContextTag::TAG_SETUP); + SetupLog::Info("Update Database Schema for environment '$sTargetEnvironment'."); $sMode = $aParamValues['mode']; - $sDBPrefix = $aParamValues['db_prefix']; - $sDBName = $aParamValues['db_name']; - $oConfig = new Config(); - $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - - $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - $oProductionEnv->InitDataModel($oConfig, true); // load data model only - - // Migrate columns - self::MoveColumns($sDBPrefix); - - // Migrate application data format - // - // priv_internalUser caused troubles because MySQL transforms table names to lower case under Windows - // This becomes an issue when moving your installation data to/from Windows - // Starting 2.0, all table names must be lowercase - if ($sMode != 'install') { - SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)"); - // This command will have no effect under Windows... - // and it has been written in two steps so as to make it work under windows! - CMDBSource::SelectDB($sDBName); - try { - $sRepair = "RENAME TABLE `{$sDBPrefix}priv_internalUser` TO `{$sDBPrefix}priv_internaluser_other`, `{$sDBPrefix}priv_internaluser_other` TO `{$sDBPrefix}priv_internaluser`"; - CMDBSource::Query($sRepair); - } catch (Exception $e) { - SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)"); - } - - // let's remove the records in priv_change which have no counterpart in priv_changeop - SetupLog::Info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records"); - CMDBSource::SelectDB($sDBName); - try { - $sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`"; - $iTotalCount = (int)CMDBSource::QueryToScalar($sTotalCount); - SetupLog::Info("There is a total of $iTotalCount records in {$sDBPrefix}priv_change."); - - $sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL"; - $iOrphanCount = (int)CMDBSource::QueryToScalar($sOrphanCount); - SetupLog::Info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0 * $iOrphanCount) / $iTotalCount))."%)"); - if ($iOrphanCount > 0) { - //N°3793 - if ($iOrphanCount > 100000) { - SetupLog::Info("There are too much useless records ($iOrphanCount) in {$sDBPrefix}priv_change. Cleanup cannot be done during setup."); - } else { - SetupLog::Info("Removing the orphan records..."); - $sCleanup = "DELETE FROM `{$sDBPrefix}priv_change` USING `{$sDBPrefix}priv_change` LEFT JOIN `{$sDBPrefix}priv_changeop` ON `{$sDBPrefix}priv_change`.id = `{$sDBPrefix}priv_changeop`.changeid WHERE `{$sDBPrefix}priv_changeop`.id IS NULL;"; - CMDBSource::Query($sCleanup); - SetupLog::Info("Cleanup completed successfully."); - } - } else { - SetupLog::Info("Ok, nothing to cleanup."); - } - } catch (Exception $e) { - SetupLog::Info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage()); - } - - } - - // Module specific actions (migrate the data) - $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); - $oProductionEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation', $aSelectedModules); - - if (!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { - throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'"); - } + $this->oRunTimeEnvironment->UpdateDBSchema($oConfig, $sMode, $aSelectedModules); // Set a DBProperty with a unique ID to identify this instance of iTop $sUUID = DBProperty::GetProperty('database_uuid', ''); @@ -539,267 +199,6 @@ class ApplicationInstallSequencer extends StepSequencer DBProperty::SetProperty('database_uuid', $sUUID, 'Installation/upgrade of '.ITOP_APPLICATION, 'Unique ID of this '.ITOP_APPLICATION.' Database'); } - // priv_change now has an 'origin' field to distinguish between the various input sources - // Let's initialize the field with 'interactive' for all records were it's null - // Then check if some records should hold a different value, based on a pattern matching in the userinfo field - CMDBSource::SelectDB($sDBName); - try { - $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change` WHERE `origin` IS NULL"; - $iCount = (int)CMDBSource::QueryToScalar($sCount); - if ($iCount > 0) { - SetupLog::Info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)"); - - // By default all uninitialized values are considered as 'interactive' - $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'interactive' WHERE `origin` IS NULL"; - CMDBSource::Query($sInit); - - // CSV Import was identified by the comment at the end - $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-import.php' WHERE `userinfo` LIKE '%Web Service (CSV)'"; - CMDBSource::Query($sInit); - - // CSV Import was identified by the comment at the end - $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'"; - CMDBSource::Query($sInit); - - // Syncho data sources were identified by the comment at the end - // Unfortunately the comment is localized, so we have to search for all possible patterns - $sCurrentLanguage = Dict::GetUserLanguage(); - $aSuffixes = []; - foreach (array_keys(Dict::GetLanguages()) as $sLangCode) { - Dict::SetUserLanguage($sLangCode); - $sSuffix = CMDBSource::Quote('%'.Dict::S('Core:SyncDataExchangeComment')); - $aSuffixes[$sSuffix] = true; - } - Dict::SetUserLanguage($sCurrentLanguage); - $sCondition = "`userinfo` LIKE ".implode(" OR `userinfo` LIKE ", array_keys($aSuffixes)); - - $sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'synchro-data-source' WHERE ($sCondition)"; - CMDBSource::Query($sInit); - - SetupLog::Info("Initialization of '{$sDBPrefix}priv_change.origin' completed."); - } else { - SetupLog::Info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do."); - } - } catch (Exception $e) { - SetupLog::Error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage()); - } - - // priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns - // Let's initialize the field with 'planned' or 'error' for all records were it's null - CMDBSource::SelectDB($sDBName); - try { - $sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_async_task` WHERE `status` IS NULL"; - $iCount = (int)CMDBSource::QueryToScalar($sCount); - if ($iCount > 0) { - SetupLog::Info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)"); - - $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)"; - CMDBSource::Query($sInit); - - $sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'error' WHERE (`status` IS NULL) AND (`started` IS NOT NULL)"; - CMDBSource::Query($sInit); - - SetupLog::Info("Initialization of '{$sDBPrefix}priv_async_task.status' completed."); - } else { - SetupLog::Info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do."); - } - } catch (Exception $e) { - SetupLog::Error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage()); - } - SetupLog::Info("Database Schema Successfully Updated for environment '$sTargetEnvironment'."); } - - /** - * @param string $sDBPrefix - * - * @throws \CoreException - * @throws \MySQLException - */ - protected static function MoveColumns($sDBPrefix) - { - // In 2.6.0 the 'fields' attribute has been moved from Query to QueryOQL for dependencies reasons - ModuleInstallerAPI::MoveColumnInDB($sDBPrefix.'priv_query', 'fields', $sDBPrefix.'priv_query_oql', 'fields'); - } - - protected function AfterDBCreate( - $aAdminParams, - $aSelectedModules - ) { - - $sAdminUser = $aAdminParams['user']; - $sAdminPwd = $aAdminParams['pwd']; - $sAdminLanguage = $aAdminParams['language']; - - $aParamValues = $this->oParams->GetParamForConfigArray(); - $sTargetEnvironment = $this->GetTargetEnv(); - $sModulesDir = $this->GetTargetDir(); - - /** - * @since 3.2.0 move the ContextTag init at the very beginning of the method - * @noinspection PhpUnusedLocalVariableInspection - */ - $oContextTag = new ContextTag(ContextTag::TAG_SETUP); - SetupLog::Info('After Database Creation'); - - $sMode = $aParamValues['mode']; - $oConfig = new Config(); - $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - - $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - $oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database - $oContextTag = new ContextTag(ContextTag::TAG_SETUP); - self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously - - // Perform here additional DB setup... profiles, etc... - // - $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); - $oProductionEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation', $aSelectedModules); - - $oProductionEnv->UpdatePredefinedObjects(); - - if ($sMode == 'install') { - if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sAdminLanguage)) { - throw(new Exception("Failed to create the administrator account '$sAdminUser'")); - } else { - SetupLog::Info("Administrator account '$sAdminUser' created."); - } - } - - // Perform final setup tasks here - // - $oProductionEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup', $aSelectedModules); - } - - /** - * Helper function to create and administrator account for iTop - * @return boolean true on success, false otherwise - */ - protected static function CreateAdminAccount(Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) - { - SetupLog::Info('CreateAdminAccount'); - - if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) { - return true; - } else { - return false; - } - } - - protected function DoLoadFiles( - $aSelectedModules, - $bSampleData = false - ) { - $aParamValues = $this->oParams->GetParamForConfigArray(); - $sTargetEnvironment = $this->GetTargetEnv(); - $sModulesDir = $this->GetTargetDir(); - - /** - * @since 3.2.0 move the ContextTag init at the very beginning of the method - * @noinspection PhpUnusedLocalVariableInspection - */ - $oContextTag = new ContextTag(ContextTag::TAG_SETUP); - - $oConfig = new Config(); - $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - - $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - - //Load the MetaModel if needed (asynchronous mode) - if (!self::$bMetaModelStarted) { - $oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database - - self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously - } - - $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir); - $oProductionEnv->LoadData($aAvailableModules, $bSampleData, $aSelectedModules); - - // Perform after dbload setup tasks here - // - $oProductionEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad', $aSelectedModules); - } - - /** - * @param string $sPreviousConfigFile - * @param string $sDataModelVersion - * @param array $aSelectedModuleCodes - * @param array $aSelectedExtensionCodes - * @param string|null $sInstallComment - * - * @param null $sInstallComment - * - * @throws \ConfigException - * @throws \CoreException - * @throws \Exception - */ - protected function DoCreateConfig( - $sPreviousConfigFile, - $sDataModelVersion, - $aSelectedModuleCodes, - $aSelectedExtensionCodes, - $sInstallComment = null - ) { - $aParamValues = $this->oParams->GetParamForConfigArray(); - $sTargetEnvironment = $this->GetTargetEnv(); - $sModulesDir = $this->GetTargetDir(); - - /** - * @since 3.2.0 move the ContextTag init at the very beginning of the method - * @noinspection PhpUnusedLocalVariableInspection - */ - $oContextTag = new ContextTag(ContextTag::TAG_SETUP); - - $aParamValues['selected_modules'] = implode(',', $aSelectedModuleCodes); - $sMode = $aParamValues['mode']; - - if ($sMode == 'upgrade') { - try { - $oOldConfig = new Config($sPreviousConfigFile); - $oConfig = clone($oOldConfig); - } catch (Exception $e) { - // In case the previous configuration is corrupted... start with a blank new one - $oConfig = new Config(); - } - } else { - $oConfig = new Config(); - // To preserve backward compatibility while upgrading to 2.0.3 (when tracking_level_linked_set_default has been introduced) - // the default value on upgrade differs from the default value at first install - $oConfig->Set('tracking_level_linked_set_default', LINKSET_TRACKING_NONE, 'first_install'); - } - - $oConfig->Set('access_mode', ACCESS_FULL); - // Final config update: add the modules - $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - - // Record which modules are installed... - $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - $oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database - - if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sInstallComment)) { - throw new Exception("Failed to record the installation information"); - } - - // Make sure the root configuration directory exists - if (!file_exists(APPCONF)) { - mkdir(APPCONF); - chmod(APPCONF, 0770); // RWX for owner and group, nothing for others - SetupLog::Info("Created configuration directory: ".APPCONF); - } - - // Write the final configuration file - $sConfigFile = APPCONF.(($sTargetEnvironment == '') ? 'production' : $sTargetEnvironment).'/'.ITOP_CONFIG_FILE; - $sConfigDir = dirname($sConfigFile); - @mkdir($sConfigDir); - @chmod($sConfigDir, 0770); // RWX for owner and group, nothing for others - - $oConfig->WriteToFile($sConfigFile); - - // try to make the final config file read-only - @chmod($sConfigFile, 0440); // Read-only for owner and group, nothing for others - - // Ready to go !! - require_once(APPROOT.'core/dict.class.inc.php'); - MetaModel::ResetAllCaches(); - } } diff --git a/setup/sequencers/DataAuditSequencer.php b/setup/sequencers/DataAuditSequencer.php index 1bc6fcfd0..9248534b1 100644 --- a/setup/sequencers/DataAuditSequencer.php +++ b/setup/sequencers/DataAuditSequencer.php @@ -23,36 +23,15 @@ require_once(APPROOT.'setup/xmldataloader.class.inc.php'); require_once APPROOT.'setup/feature_removal/SetupAudit.php'; require_once(APPROOT.'setup/sequencers/StepSequencer.php'); -require_once(APPROOT.'setup/sequencers/ApplicationInstallSequencer.php'); -class DataAuditSequencer extends ApplicationInstallSequencer +class DataAuditSequencer extends StepSequencer { public const DATA_AUDIT_FAILED = 100; - protected function GetTempEnv() - { - $sTargetEnv = $this->GetTargetEnv(); - - return $sTargetEnv.'-build'; - } - - protected function GetTargetDir() - { - $sTargetEnv = $this->GetTempEnv(); - - return 'env-'.$sTargetEnv; - } - /** - * Executes the next step of the installation and reports about the progress - * and the next step to perform - * - * @param string $sStep The identifier of the step to execute - * @param string|null $sInstallComment - * - * @return array (status => , message => , percentage-completed => , next-step => , next-step-label => ) + * @inherit */ - public function ExecuteStep($sStep = '', $sInstallComment = null) + public function ExecuteStep($sStep = '', $sInstallComment = null): array { try { /** @@ -106,21 +85,4 @@ class DataAuditSequencer extends ApplicationInstallSequencer SetupLog::Info("##### STEP {$sStep} duration: {$fDuration}s"); } } - - protected function DoWriteConfig() - { - $sConfigFilePath = utils::GetConfigFilePath($this->GetTargetEnv()); - if (is_file($sConfigFilePath)) { - $oConfig = new Config($sConfigFilePath); - - $sTempConfigFileName = utils::GetConfigFilePath($this->GetTempEnv()); - $sConfigDir = dirname($sTempConfigFileName); - @mkdir($sConfigDir); - @chmod($sConfigDir, 0770); // RWX for owner and group, nothing for others - - return $oConfig->WriteToFile($sTempConfigFileName); - } - - return false; - } } diff --git a/setup/sequencers/StepSequencer.php b/setup/sequencers/StepSequencer.php index 53f72666f..5f3d5664e 100644 --- a/setup/sequencers/StepSequencer.php +++ b/setup/sequencers/StepSequencer.php @@ -24,6 +24,31 @@ abstract class StepSequencer public const WARNING = 3; public const INFO = 4; protected array $aStepsHistory = []; + protected Parameters $oParams; + protected Config $oConfig; + protected RunTimeEnvironment $oRunTimeEnvironment; + + /** + * @param \Parameters $oParams + * @param \RunTimeEnvironment|null $oRunTimeEnvironment + * + * @throws \CoreException + */ + public function __construct(Parameters $oParams, ?RunTimeEnvironment $oRunTimeEnvironment = null) + { + if (is_null($oRunTimeEnvironment)) { + $sEnvironment = $oParams->Get('target_env', 'production'); + $oRunTimeEnvironment = new RunTimeEnvironment($sEnvironment, false); + } + $this->oRunTimeEnvironment = $oRunTimeEnvironment; + + $this->oParams = $oParams; + + $aParamValues = $oParams->GetParamForConfigArray(); + $this->oConfig = new Config(); + $this->oConfig->UpdateFromParams($aParamValues); + utils::SetConfig($this->oConfig); + } public function LogStep($sStep, $aResult) { @@ -134,5 +159,48 @@ abstract class StepSequencer file_put_contents(APPROOT.'log/'.$sFileName.'.xml', $sSafeXml); } - abstract public function ExecuteStep($sStep = '', $sComment = null); + protected function ReportException(Exception $e) + { + SetupLog::Error('An exception occurred: '.$e->getMessage().' at line '.$e->getLine().' in file '.$e->getFile()); + $idx = 0; + // Log the call stack, but not the parameters since they may contain passwords or other sensitive data + SetupLog::Ok('Call stack:'); + foreach ($e->getTrace() as $aTrace) { + $sLine = empty($aTrace['line']) ? '' : $aTrace['line']; + $sFile = empty($aTrace['file']) ? '' : $aTrace['file']; + $sClass = empty($aTrace['class']) ? '' : $aTrace['class']; + $sType = empty($aTrace['type']) ? '' : $aTrace['type']; + $sFunction = empty($aTrace['function']) ? '' : $aTrace['function']; + $sVerb = empty($sClass) ? $sFunction : "$sClass{$sType}$sFunction"; + SetupLog::Ok("#$idx $sFile($sLine): $sVerb(...)"); + $idx++; + } + } + + protected function GetConfig() + { + $sTargetEnvironment = $this->oRunTimeEnvironment->GetBuildEnv(); + $sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; + try { + $oConfig = new Config($sConfigFile); + } catch (Exception $e) { + return null; + } + + $aParamValues = $this->oParams->GetParamForConfigArray(); + $oConfig->UpdateFromParams($aParamValues); + + return $oConfig; + } + + /** + * Executes the next step of the installation and reports about the progress + * and the next step to perform + * + * @param string $sStep The identifier of the step to execute + * @param string|null $sInstallComment + * + * @return array (status => , message => , percentage-completed => , next-step => , next-step-label => ) + */ + abstract public function ExecuteStep($sStep = '', $sInstallComment = null): array; } diff --git a/setup/wizardsteps/AbstractWizStepInstall.php b/setup/wizardsteps/AbstractWizStepInstall.php index febc309e2..31ec559c9 100644 --- a/setup/wizardsteps/AbstractWizStepInstall.php +++ b/setup/wizardsteps/AbstractWizStepInstall.php @@ -63,12 +63,10 @@ abstract class AbstractWizStepInstall extends WizardStep $aInstallParams = [ 'mode' => $sMode, - 'preinstall' => [ - 'copies' => $aCopies, - // 'backup' => see below - ], 'optional_steps' => [ - '' + 'log-parameters' => true, + 'migrate-before' => true, + 'migrate-after' => true, // 'backup' => see below ], 'source_dir' => str_replace(APPROOT, '', $sSourceDir), @@ -103,7 +101,7 @@ abstract class AbstractWizStepInstall extends WizardStep ]; if ($sBackupDestination != '') { - $aInstallParams['preinstall']['backup'] = [ + $aInstallParams['optional_steps']['backup'] = [ 'destination' => $sBackupDestination, 'configuration_file' => $sPreviousConfigurationFile, ];