mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-26 21:54:13 +01:00
Compare commits
83 Commits
feature/re
...
issue/9161
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6d1b469e | ||
|
|
f0629724ea | ||
|
|
9b6d321ab0 | ||
|
|
329191aa47 | ||
|
|
6915df1409 | ||
|
|
fd68558d1c | ||
|
|
bd9129dee2 | ||
|
|
fe34a6f9c3 | ||
|
|
24aec0d08d | ||
|
|
a287527d29 | ||
|
|
a42b061f19 | ||
|
|
c4d7c89553 | ||
|
|
5b58e40fc9 | ||
|
|
2b21556c76 | ||
|
|
77626f8159 | ||
|
|
bb6248a6e7 | ||
|
|
130d98aa3f | ||
|
|
00c590232a | ||
|
|
97828225db | ||
|
|
03e59c9749 | ||
|
|
985a49dc9f | ||
|
|
adae35ccc4 | ||
|
|
f0c9629f5f | ||
|
|
4e96b297c2 | ||
|
|
ae620c6663 | ||
|
|
b5c51a2983 | ||
|
|
3b2d845c00 | ||
|
|
36c545a6c4 | ||
|
|
5ecb4936f0 | ||
|
|
af2c3b02bc | ||
|
|
cfc933b92b | ||
|
|
0582ae1038 | ||
|
|
13c18b611c | ||
|
|
8d2e0761e0 | ||
|
|
1e15eb1161 | ||
|
|
7df59427cb | ||
|
|
d647d92acf | ||
|
|
693e40b9c7 | ||
|
|
c3c2135ecc | ||
|
|
63e473e6d0 | ||
|
|
3a3f5736c0 | ||
|
|
0996e8fae0 | ||
|
|
43bd621731 | ||
|
|
d1e91087b9 | ||
|
|
73cb58a131 | ||
|
|
d1fde89129 | ||
|
|
1f4b96798a | ||
|
|
9768ffb19d | ||
|
|
e55bbf728b | ||
|
|
5f2604c610 | ||
|
|
57b3610100 | ||
|
|
9f3b8ec964 | ||
|
|
193c980057 | ||
|
|
fdfe9224c3 | ||
|
|
774fe22ece | ||
|
|
27b0f64328 | ||
|
|
8ad4670a2f | ||
|
|
f2e682c07c | ||
|
|
83973d102f | ||
|
|
85e28931f5 | ||
|
|
d33ca81198 | ||
|
|
1ef4462517 | ||
|
|
a2b0ad6c11 | ||
|
|
7844c9ffd0 | ||
|
|
1c45a4c49e | ||
|
|
8f47ca00a7 | ||
|
|
f9a1b444ab | ||
|
|
3b38dac8c6 | ||
|
|
b967fb7f20 | ||
|
|
195038c941 | ||
|
|
9a8d87e2b5 | ||
|
|
385302c44c | ||
|
|
b563f113d0 | ||
|
|
715d9d3b1c | ||
|
|
7a6c2bc6a4 | ||
|
|
8919184ef9 | ||
|
|
7bfaebe4db | ||
|
|
8e10cf9b72 | ||
|
|
9f3d7d2c36 | ||
|
|
ae980e365d | ||
|
|
a2b01b3ed4 | ||
|
|
9cdc707bc5 | ||
|
|
76178c16b8 |
@@ -16,5 +16,5 @@ require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.domain.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
@@ -24,7 +24,7 @@ MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
@@ -2685,14 +2685,13 @@ class Config
|
||||
*
|
||||
* @param array $aParamValues
|
||||
* @param ?string $sModulesDir
|
||||
* @param bool $bPreserveModuleSettings
|
||||
*
|
||||
* @return void The current object is modified directly
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false)
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null)
|
||||
{
|
||||
if (isset($aParamValues['application_path'])) {
|
||||
$this->Set('app_root_url', $aParamValues['application_path']);
|
||||
@@ -2740,7 +2739,10 @@ class Config
|
||||
} else {
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
|
||||
if (! is_null($sModulesDir)) {
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
}
|
||||
|
||||
if (isset($aParamValues['source_dir'])) {
|
||||
$this->Set('source_dir', $aParamValues['source_dir']);
|
||||
@@ -2758,17 +2760,13 @@ class Config
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
|
||||
public function UpdateIncludes(string $sModulesDir, $aSelectedModules = null)
|
||||
{
|
||||
if ($sModulesDir === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the arrays below with default values for the application...
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
|
||||
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
|
||||
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {
|
||||
|
||||
@@ -22,6 +22,8 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
@@ -462,6 +464,43 @@ abstract class MetaModel
|
||||
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetModuleName($sClass)
|
||||
{
|
||||
try {
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sDir = realpath(dirname($oReflectionClass->getFileName()));
|
||||
$sApproot = realpath(APPROOT);
|
||||
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
|
||||
$aFiles = glob("$sDir/module.*.php");
|
||||
if (count($aFiles) > 1) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
if (count($aFiles) == 0) {
|
||||
$sDir = realpath(dirname($sDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
$sModuleFilePath = $aFiles[0];
|
||||
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
return $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new CoreException("Cannot find class module", ['class' => $sClass], '', $e);
|
||||
}
|
||||
|
||||
return 'core';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -523,7 +523,12 @@ body {
|
||||
}
|
||||
|
||||
.ibo-setup-summary-title {
|
||||
font-size: $ibo-font-size-150;
|
||||
font-size: $ibo-font-size-150;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ibo-setup-summary-title:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#ibo-setup-licenses--components-list {
|
||||
@@ -605,6 +610,7 @@ body {
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
display: inline-flex;
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
@@ -681,9 +687,6 @@ body {
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
}
|
||||
#installation_progress {
|
||||
display: none;
|
||||
}
|
||||
#fresh_content{
|
||||
border: 0;
|
||||
min-height: 300px;
|
||||
|
||||
12
datamodels/2.x/combodo-data-feature-removal/.idea/combodo-data-feature-removal.iml
generated
Normal file
12
datamodels/2.x/combodo-data-feature-removal/.idea/combodo-data-feature-removal.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Combodo\iTop\DataFeatureRemoval\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/NoNamespace" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
datamodels/2.x/combodo-data-feature-removal/.idea/modules.xml
generated
Normal file
8
datamodels/2.x/combodo-data-feature-removal/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/combodo-data-feature-removal.iml" filepath="$PROJECT_DIR$/.idea/combodo-data-feature-removal.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
24
datamodels/2.x/combodo-data-feature-removal/.idea/php.xml
generated
Normal file
24
datamodels/2.x/combodo-data-feature-removal/.idea/php.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
datamodels/2.x/combodo-data-feature-removal/.idea/vcs.xml
generated
Normal file
6
datamodels/2.x/combodo-data-feature-removal/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
53
datamodels/2.x/combodo-data-feature-removal/.idea/workspace.xml
generated
Normal file
53
datamodels/2.x/combodo-data-feature-removal/.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="0556f797-a2a3-4617-8eb0-c7985d4d9530" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ComposerSettings" synchronizationState="SYNCHRONIZE">
|
||||
<pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
|
||||
<execution />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="PhpWorkspaceProjectConfiguration" interpreter_name="PHP 7.2">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 1
|
||||
}</component>
|
||||
<component name="ProjectId" id="38XKfHC46lRTrwwYc7IxUdzfXo1" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"last_opened_file_path": "/home/combodo/workspaceHUB/HubInstallation",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* CSS of the template page
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Javascript file loaded in template page
|
||||
*/
|
||||
|
||||
17
datamodels/2.x/combodo-data-feature-removal/composer.json
Normal file
17
datamodels/2.x/combodo-data-feature-removal/composer.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Combodo\\iTop\\DataFeatureRemoval\\": "src",
|
||||
"": "src/NoNamespace"
|
||||
}
|
||||
},
|
||||
"name": "combodo/combodo-data-feature-removal",
|
||||
"type": "itop-extension",
|
||||
"description": "iTop Data Feature Removal",
|
||||
"require": {
|
||||
"composer-runtime-api": "^2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<classes>
|
||||
<class id="DataFeatureRemoverExtension" _delta="define">
|
||||
<properties>
|
||||
<category>grant_by_profile</category>
|
||||
<db_table>data_feature_removal_extension</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="extension_code"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules/>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="extension_code" xsi:type="AttributeString">
|
||||
<sql>extension_code</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="label" xsi:type="AttributeString">
|
||||
<sql>label</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="version" xsi:type="AttributeString">
|
||||
<sql>version</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="module_names" xsi:type="AttributeText">
|
||||
<sql>module_names</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="status" xsi:type="AttributeString">
|
||||
<sql>status</sql>
|
||||
<default_value>none</default_value>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<presentation>
|
||||
<list>
|
||||
<items>
|
||||
<item id="extension_code">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
<search>
|
||||
<item id="extension_code">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</search>
|
||||
<details>
|
||||
<item id="extension_code">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</details>
|
||||
</presentation>
|
||||
<methods/>
|
||||
</class>
|
||||
<class id="DataFeatureRemoverAuditRule" _delta="define">
|
||||
<properties>
|
||||
<category>grant_by_profile</category>
|
||||
<db_table>data_feature_removal_auditrule</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="rule_name"/>
|
||||
<attribute id="extension_code"/>
|
||||
<attribute id="class_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules/>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="rule_name" xsi:type="AttributeString">
|
||||
<sql>rule_name</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extension_code" xsi:type="AttributeString">
|
||||
<sql>extension_code</sql>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="class_name" xsi:type="AttributeText">
|
||||
<sql>class_name</sql>
|
||||
<default_value>none</default_value>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="count" xsi:type="AttributeInteger">
|
||||
<sql>count</sql>
|
||||
<default_value>0</default_value>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<presentation>
|
||||
<list>
|
||||
<items>
|
||||
<item id="rule_name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extension_code">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="class_name">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
<search>
|
||||
<item id="rule_name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extension_code">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="class_name">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</search>
|
||||
<details>
|
||||
<item id="rule_name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extension_code">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="class_name">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</details>
|
||||
</presentation>
|
||||
<methods/>
|
||||
</class>
|
||||
</classes>
|
||||
<menus>
|
||||
<menu id="DataFeatureRemovalMenu" xsi:type="WebPageMenuNode" _delta="define">
|
||||
<rank>30</rank>
|
||||
<parent>SystemTools</parent>
|
||||
<url>$pages/exec.php?exec_module=combodo-data-feature-removal&exec_page=index.php&c[menu]=DataFeatureRemovalMenu</url>
|
||||
<enable_admin_only>1</enable_admin_only>
|
||||
</menu>
|
||||
</menus>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Features Removal',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Features Removal',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Features Removal',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'Prepare features you want to enable/disable in a future setup',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Feature dry removal errors',
|
||||
'DataFeatureRemoval:Helper:Title' => 'This utilitary allows you to enable or disable features that are installed in your iTop.',
|
||||
'DataFeatureRemoval:Helper:Desc1' => 'It will prepare the setup step that proceeds to feature enabling or disabling.',
|
||||
'DataFeatureRemoval:Helper:Desc2' => 'You will need to analyze if there are any data or dependency preventing you from enabling/disabling a feature.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
||||
'DataFeatureRemoval:Table:Analysis:RemovalType' => 'Type of element',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurence' => 'Occurence',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyze',
|
||||
'UI:Button:ModifyChoices' => 'Modify Choices',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Force uninstall',
|
||||
'UI:Action:MoreInfo' => 'More information',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:RemovalType:FINAL_CLASS' => 'Final class',
|
||||
|
||||
]);
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Features Removal',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Features Removal',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Features Removal',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'Prepare features you want to enable/disable in a future setup',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Feature dry removal errors',
|
||||
'DataFeatureRemoval:Helper:Title' => 'This utilitary allows you to enable or disable features that are installed in your iTop.',
|
||||
'DataFeatureRemoval:Helper:Desc1' => 'It will prepare the setup step that proceeds to feature enabling or disabling.',
|
||||
'DataFeatureRemoval:Helper:Desc2' => 'You will need to analyze if there are any data or dependency preventing you from enabling/disabling a feature.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
||||
'DataFeatureRemoval:Table:Analysis:RemovalType' => 'Type of element',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurence' => 'Occurence',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyze',
|
||||
'UI:Button:ModifyChoices' => 'Modify Choices',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Force uninstall',
|
||||
'UI:Action:MoreInfo' => 'More information',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:RemovalType:FINAL_CLASS' => 'Final class',
|
||||
]);
|
||||
20
datamodels/2.x/combodo-data-feature-removal/index.php
Normal file
20
datamodels/2.x/combodo-data-feature-removal/index.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Controller\DataFeatureRemovalController;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
|
||||
DataFeatureRemovalLog::Enable();
|
||||
|
||||
$oController = new DataFeatureRemovalController(MODULESROOT.DataFeatureRemovalHelper::MODULE_NAME.'/templates', DataFeatureRemovalHelper::MODULE_NAME);
|
||||
$oController->SetDefaultOperation('Main');
|
||||
$oController->HandleOperation();
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
// PHP Data Model definition file
|
||||
|
||||
// WARNING - WARNING - WARNING
|
||||
// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
//
|
||||
// If you provide a datamodel.xxxx.xml file with your module,
|
||||
// this file WILL BE overwritten by the compilation of the
|
||||
// module (during the setup) if the datamodel.xxxx.xml file
|
||||
// contains the definition of new classes or menus.
|
||||
//
|
||||
// The recommended way to define new classes (for iTop 2.0 and later) is via the XML definition.
|
||||
// This file remains in the module's template only for the cases where there is:
|
||||
// - either no new class or menu defined in the XML file
|
||||
// - or no XML file at all supplied by the module
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-data-feature-removal/3.3.0',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'iTop Data Feature Removal',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
|
||||
],
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'vendor/autoload.php',
|
||||
'model.combodo-data-feature-removal.php', // Contains the PHP code generated by the "compilation" of datamodel.combodo-data-feature-removal.xml
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [
|
||||
// add your 'structure' definition XML files here,
|
||||
],
|
||||
'data.sample' => [
|
||||
// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [
|
||||
// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Controller;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/SetupAudit.php';
|
||||
require_once APPROOT.'setup/feature_removal/DryRemovalRuntimeEnvironment.php';
|
||||
|
||||
use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
||||
use Combodo\iTop\AuthentToken\Helper\TokenAuthLog;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverAuditRuleService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverExtensionService;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
class DataFeatureRemovalController extends Controller
|
||||
{
|
||||
private array $aSelectedExtensionsForCheck = [];
|
||||
|
||||
public function OperationMain($sErrorMessage = null)
|
||||
{
|
||||
$aParams = [];
|
||||
|
||||
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
|
||||
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
|
||||
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$this->AddFeatureParams($aParams);
|
||||
$this->AddAnalyzeParams($aParams);
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage;
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
|
||||
public function AddFeatureParams(array &$aParams)
|
||||
{
|
||||
$aParams['aExtensions'] = $this->GetExtensionsTable();
|
||||
$aParams['sModule'] = DataFeatureRemovalHelper::MODULE_NAME;
|
||||
}
|
||||
|
||||
public function AddAnalyzeParams(array &$aParams)
|
||||
{
|
||||
$iTotalCount = 0;
|
||||
$aData = [];
|
||||
$aColumns = [];
|
||||
foreach (DataFeatureRemoverAuditRuleService::GetInstance()->ReadCheckRules() as $oRule) {
|
||||
$sContent = $oRule->Get('class_name');
|
||||
$sModuleName = MetaModel::GetModuleName($sContent);
|
||||
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
|
||||
$sExtensions = implode(' ', $aExtensions);
|
||||
$sTypeName = $oRule->Get('rule_name');
|
||||
$sTypeDesc = \Dict::S("DataFeatureRemoval:Table:Analysis:RemovalType:$sTypeName");
|
||||
$iCount = $oRule->Get('count');
|
||||
$iTotalCount += $iCount;
|
||||
$aColumns = ['ClassName', 'RemovalType','FeatureName','Occurence'];
|
||||
$aData[] = [
|
||||
<<<HTML
|
||||
<label>$sContent</label>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label>$sTypeDesc</label>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label title="$sModuleName">$sExtensions</label>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label>$iCount</label>
|
||||
HTML,
|
||||
];
|
||||
}
|
||||
|
||||
$aParams['aCheckRules'] = $this->GetTableData('Analysis', $aColumns, $aData);
|
||||
$aParams['rule_count'] = $iTotalCount;
|
||||
}
|
||||
|
||||
public function OperationAnalyze()
|
||||
{
|
||||
$aSelectedExtensionsFromUI = utils::ReadPostedParam('aExtensions', []);
|
||||
$this->aSelectedExtensionsForCheck = [];
|
||||
foreach ($aSelectedExtensionsFromUI as $sCode => $aData) {
|
||||
$sValue = $aData['enable'] ?? 'off';
|
||||
if (($sValue) === 'on') {
|
||||
$this->aSelectedExtensionsForCheck[] = $sCode;
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_sOperation = 'Main';
|
||||
|
||||
try {
|
||||
$this->Analyze();
|
||||
$this->OperationMain();
|
||||
} catch (Exception $e) {
|
||||
\IssueLog::Error(__METHOD__, null, ['stack' => $e->getTraceAsString(), 'exception' => $e->getMessage()]);
|
||||
$this->OperationMain($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function GetExtensionsTable(): array
|
||||
{
|
||||
$aExtensions = [];
|
||||
$aColumns = ['', 'Version', 'Name', 'Code'];
|
||||
$this->aSelectedExtensionsForCheck = DataFeatureRemoverExtensionService::GetInstance()->ReadAuditedExtensions();
|
||||
|
||||
foreach (DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions() as $sCode => $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
|
||||
$sChecked = "checked";
|
||||
$sDisabledHtml = '';
|
||||
if ($oExtension->bRemovedFromDisk) {
|
||||
$sDisabledHtml = 'disabled=""';
|
||||
} elseif (! array_key_exists($sCode, $this->aSelectedExtensionsForCheck)) {
|
||||
$sChecked = "";
|
||||
}
|
||||
|
||||
$sLabel = $oExtension->sLabel;
|
||||
$sVersion = $oExtension->sVersion;
|
||||
$sIdEnable = "aExtensions[$sCode][enable]";
|
||||
|
||||
$aExtensions[] = [
|
||||
<<<HTML
|
||||
<input type="checkbox" $sDisabledHtml class="extension_check" $sChecked id="$sIdEnable" name="$sIdEnable"/>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label>$sVersion</label>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label for="$sIdEnable">$sLabel</label>
|
||||
HTML,
|
||||
<<<HTML
|
||||
<label for="$sIdEnable">$sCode</label>
|
||||
HTML,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->GetTableData('Extensions', $aColumns, $aExtensions);
|
||||
|
||||
}
|
||||
|
||||
public function GetTableData(string $sTableName, array $aColumns, array $aData): array
|
||||
{
|
||||
if (empty($aData)) {
|
||||
return [
|
||||
'Type' => 'Table',
|
||||
'Columns' => [['label' => '']],
|
||||
'Data' => [[ Dict::S('DbCleaner:Table:Empty')]],
|
||||
];
|
||||
}
|
||||
|
||||
$aNewColumns = [];
|
||||
foreach ($aColumns as $sColumn) {
|
||||
$aNewColumns[] = ['label' => Dict::S("DataFeatureRemoval:Table:$sTableName:$sColumn", $sColumn)];
|
||||
}
|
||||
$aColumns = $aNewColumns;
|
||||
|
||||
return [
|
||||
'Type' => 'Table',
|
||||
'Columns' => $aColumns,
|
||||
'Data' => $aData,
|
||||
];
|
||||
}
|
||||
|
||||
private function Analyze()
|
||||
{
|
||||
DataFeatureRemoverExtensionService::GetInstance()->SaveExtensions($this->aSelectedExtensionsForCheck);
|
||||
|
||||
$sSourceEnvt = \MetaModel::GetEnvironment();
|
||||
$oDryRemovalRuntimeEnvironment = new DryRemovalRuntimeEnvironment();
|
||||
$oDryRemovalRuntimeEnvironment->Prepare($sSourceEnvt, $this->aSelectedExtensionsForCheck);
|
||||
$oDryRemovalRuntimeEnvironment->CompileFrom($sSourceEnvt);
|
||||
|
||||
$oSetupAudit = new SetupAudit($sSourceEnvt, DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV);
|
||||
$this->Save($oSetupAudit->GetIssues());
|
||||
}
|
||||
|
||||
private function Save(array $aGetRemovedClasses)
|
||||
{
|
||||
\IssueLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
|
||||
|
||||
DataFeatureRemoverAuditRuleService::GetInstance()->SaveChecks($aGetRemovedClasses);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DataFeatureRemovalException extends Exception
|
||||
{
|
||||
public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null, array $aContext = [])
|
||||
{
|
||||
if (!is_null($previous)) {
|
||||
$sStack = $previous->getTraceAsString();
|
||||
$sError = $previous->getMessage();
|
||||
} else {
|
||||
$sStack = $this->getTraceAsString();
|
||||
$sError = '';
|
||||
}
|
||||
|
||||
$aContext['error'] = $sError;
|
||||
$aContext['stack'] = $sStack;
|
||||
DataFeatureRemovalLog::Error($message, null, $aContext);
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
class DataFeatureRemovalHelper
|
||||
{
|
||||
public const MODULE_NAME = 'combodo-data-feature-removal';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use LogAPI;
|
||||
|
||||
class DataFeatureRemovalLog extends LogAPI
|
||||
{
|
||||
public const CHANNEL_DEFAULT = 'DataFeatureRemoval';
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
public static function Enable($sTargetFile = null)
|
||||
{
|
||||
if (empty($sTargetFile)) {
|
||||
$sTargetFile = APPROOT.'log/error.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Model;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use DataFeatureRemoverAuditRule;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Exception;
|
||||
|
||||
class DataFeatureRemoverAuditRuleService
|
||||
{
|
||||
private static DataFeatureRemoverAuditRuleService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemoverAuditRuleService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DataFeatureRemoverAuditRuleService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?DataFeatureRemoverAuditRuleService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function SaveChecks(array $aGetRemovedClasses)
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverAuditRule', []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
while (null != ($oObj = $oSet->Fetch())) {
|
||||
$oObj->DBDelete();
|
||||
}
|
||||
|
||||
foreach ($aGetRemovedClasses as $sClass => $iCount) {
|
||||
$oObj = new DataFeatureRemoverAuditRule();
|
||||
$oObj->Set('rule_name', 'FINAL_CLASS');
|
||||
$oObj->Set('extension_code', $sClass);
|
||||
$oObj->Set('class_name', $sClass);
|
||||
$oObj->Set('count', $iCount);
|
||||
$oObj->DBWrite();
|
||||
}
|
||||
}
|
||||
|
||||
public function ReadCheckRules(): array
|
||||
{
|
||||
try {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverAuditRule', []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
$aRes = [];
|
||||
while (null != ($oObj = $oSet->Fetch())) {
|
||||
$aRes[] = $oObj;
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
} catch (Exception $e) {
|
||||
throw new DataFeatureRemovalException(__FUNCTION__.' failed', 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Model;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use DataFeatureRemoverExtension;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Exception;
|
||||
use iTopExtension;
|
||||
use iTopExtensionsMap;
|
||||
use MetaModel;
|
||||
|
||||
class DataFeatureRemoverExtensionService
|
||||
{
|
||||
private static DataFeatureRemoverExtensionService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemoverExtensionService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DataFeatureRemoverExtensionService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?DataFeatureRemoverExtensionService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function SaveExtensions(array $aSelectedExtensionsForCheck)
|
||||
{
|
||||
$this->ReadItopExtensions();
|
||||
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverExtension', []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
while (null != ($oObj = $oSet->Fetch())) {
|
||||
$oObj->DBDelete();
|
||||
}
|
||||
|
||||
foreach ($aSelectedExtensionsForCheck as $i => $sCode) {
|
||||
$oObj = new DataFeatureRemoverExtension();
|
||||
$oObj->Set('extension_code', $sCode);
|
||||
/** @var iTopExtension $oExtension */
|
||||
$oExtension = $this->aItopExtensions[$sCode];
|
||||
$oObj->Set('module_names', json_encode($oExtension->aModules));
|
||||
$oObj->Set('label', $oExtension->sLabel);
|
||||
$oObj->Set('version', $oExtension->sVersion);
|
||||
$oObj->DBWrite();
|
||||
}
|
||||
}
|
||||
|
||||
private array $aSelectedExtensions = [];
|
||||
private array $aItopExtensions = [];
|
||||
private array $aIncludingExtensionsByModuleName = [];
|
||||
public function ReadAuditedExtensions(): array
|
||||
{
|
||||
if (count($this->aSelectedExtensions) == 0) {
|
||||
try {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverExtension', []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
while (null != ($oObj = $oSet->Fetch())) {
|
||||
$sCode = $oObj->Get('extension_code');
|
||||
$sLabel = $oObj->Get('label');
|
||||
$sVersion = $oObj->Get('version');
|
||||
|
||||
$sModuleNames = $oObj->Get('module_names');
|
||||
$aModuleNames = json_decode($sModuleNames, true);
|
||||
if (is_array($aModuleNames) && count($aModuleNames) > 0) {
|
||||
foreach ($aModuleNames as $sModuleName) {
|
||||
$aExtensions = $this->aIncludingExtensionsByModuleName[$sModuleName] ?? [];
|
||||
$aExtensions[] = "$sLabel / $sVersion";
|
||||
$this->aIncludingExtensionsByModuleName[$sModuleName] = $aExtensions;
|
||||
|
||||
}
|
||||
}
|
||||
$this->aSelectedExtensions[$sCode] = $oObj;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new DataFeatureRemovalException(__FUNCTION__.' failed', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
\IssueLog::Debug(__METHOD__, null, ['aSelectedExtensionsForCheck' => $this->aSelectedExtensions]);
|
||||
\IssueLog::Debug(__METHOD__, null, ['aIncludingExtensionsByModuleName' => $this->aIncludingExtensionsByModuleName]);
|
||||
|
||||
return $this->aSelectedExtensions;
|
||||
}
|
||||
|
||||
public function GetIncludingExtensions(string $sModuleName): array
|
||||
{
|
||||
$this->ReadAuditedExtensions();
|
||||
return $this->aIncludingExtensionsByModuleName[$sModuleName] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iTopExtension[]
|
||||
*/
|
||||
public function ReadItopExtensions(): array
|
||||
{
|
||||
if (count($this->aItopExtensions) == 0) {
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap->LoadInstalledExtensionsFromDatabase(MetaModel::GetConfig());
|
||||
$this->aItopExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(true);
|
||||
|
||||
uasort($this->aItopExtensions, function (iTopExtension $oiTopExtension1, iTopExtension $oiTopExtension2) {
|
||||
return strcmp($oiTopExtension1->sLabel, $oiTopExtension2->sLabel);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->aItopExtensions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:SubTitle'|dict_format(rule_count) } %}
|
||||
{% UIDataTable ForForm { sRef:'aCheckRules', aColumns:aCheckRules.Columns, aData:aCheckRules.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIPanel %}
|
||||
{% EndUIForm %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden {sName:'operation', sValue:'Analyze'} %}
|
||||
{% UIInput ForHidden {sName:'transaction_id', sValue:sTransactionId} %}
|
||||
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Features:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aExtensions', aColumns:aExtensions.Columns, aData:aExtensions.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
|
||||
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Analyze'|dict_s, sName:'btn_apply', sId:'btn_apply', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
|
||||
{% EndUIForm %}
|
||||
@@ -0,0 +1,28 @@
|
||||
{# @copyright Copyright (C) 2010-2025 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{# Usable variables: #}
|
||||
{# * sTitle => page title #}
|
||||
{# * sMessage => success message #}
|
||||
{# * sError => error message #}
|
||||
|
||||
{# DataFeatureRemoval #}
|
||||
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Main:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Main:SubTitle'|dict_s } %}
|
||||
|
||||
{% UIAlert ForInformation { sTitle:'DataFeatureRemoval:Helper:Title'|dict_s } %}
|
||||
{{ 'DataFeatureRemoval:Helper:Desc1'|dict_s }}<BR>
|
||||
{{ 'DataFeatureRemoval:Helper:Desc2'|dict_s }}
|
||||
{% EndUIAlert %}
|
||||
|
||||
|
||||
{% if null != DataFeatureRemovalErrorMessage %}
|
||||
<div id="feature_removal_error_msg_div" style="display:block">
|
||||
{% UIAlert ForFailure { sTitle:'DataFeatureRemoval:Failure:Title'|dict_s, sId: 'feature_removal_error_msg', sContent:DataFeatureRemovalErrorMessage } %}
|
||||
{% EndUIAlert %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'FeaturesTab.html.twig' %}
|
||||
{% include 'ExtensionRemovalDataTab.html.twig' %}
|
||||
{% EndUIPanel %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
$(document).on('click', '#checkAllExtensions', function() {
|
||||
var bChecked = this.checked;
|
||||
$('.extension_check').each( function() { this.checked = bChecked });
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
25
datamodels/2.x/combodo-data-feature-removal/vendor/autoload.php
vendored
Normal file
25
datamodels/2.x/combodo-data-feature-removal/vendor/autoload.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464::getLoader();
|
||||
579
datamodels/2.x/combodo-data-feature-removal/vendor/composer/ClassLoader.php
vendored
Normal file
579
datamodels/2.x/combodo-data-feature-removal/vendor/composer/ClassLoader.php
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
21
datamodels/2.x/combodo-data-feature-removal/vendor/composer/LICENSE
vendored
Normal file
21
datamodels/2.x/combodo-data-feature-removal/vendor/composer/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
17
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_classmap.php
vendored
Normal file
17
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_classmap.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => $baseDir . '/src/Model/DataFeatureRemoverAuditRuleService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Model/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => $baseDir . '/src/Service/SetupAudit.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
9
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_namespaces.php
vendored
Normal file
9
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_namespaces.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
11
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_psr4.php
vendored
Normal file
11
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_psr4.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' => array($baseDir . '/src'),
|
||||
'' => array($baseDir . '/src/NoNamespace'),
|
||||
);
|
||||
37
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_real.php
vendored
Normal file
37
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_real.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit4f96a7199e2c0d90e547333758b26464::getInitializer($loader));
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
48
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_static.php
vendored
Normal file
48
datamodels/2.x/combodo-data-feature-removal/vendor/composer/autoload_static.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'C' =>
|
||||
array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' => 32,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $fallbackDirsPsr4 = array (
|
||||
0 => __DIR__ . '/../..' . '/src/NoNamespace',
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverAuditRuleService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => __DIR__ . '/../..' . '/src/Service/SetupAudit.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixDirsPsr4;
|
||||
$loader->fallbackDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$fallbackDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace Combodo\iTop\DBTools\Service;
|
||||
use CMDBSource;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use IssueLog;
|
||||
|
||||
class DBToolsUtils
|
||||
{
|
||||
|
||||
@@ -92,7 +92,7 @@ final class CoreUpdater
|
||||
$sFinalEnv = 'production';
|
||||
$oRuntimeEnv = new RunTimeEnvironmentCoreUpdater($sFinalEnv, false);
|
||||
$oRuntimeEnv->CheckDirectories($sFinalEnv);
|
||||
$oRuntimeEnv->CompileFrom('production');
|
||||
$oRuntimeEnv->CompileFrom($sFinalEnv);
|
||||
|
||||
$oRuntimeEnv->Rollback();
|
||||
|
||||
@@ -155,21 +155,13 @@ final class CoreUpdater
|
||||
APPROOT.'extensions',
|
||||
];
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $aDirsToScanForModules);
|
||||
$aSelectedModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
} else {
|
||||
$aSelectedModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation');
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation');
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup');
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
// Default choices = as before
|
||||
@@ -187,7 +179,7 @@ final class CoreUpdater
|
||||
$oRuntimeEnv->RecordInstallation(
|
||||
$oConfig,
|
||||
$sDataModelVersion,
|
||||
$aSelectedModules,
|
||||
array_keys($aAvailableModules),
|
||||
$aSelectedExtensionCodes,
|
||||
'Done by the iTop Core Updater'
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
|
||||
$aAvailableModules[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
// TODO check the auto-selected modules here
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
foreach ($oExtension->aModules as $sModuleName) {
|
||||
if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) {
|
||||
|
||||
@@ -24,129 +24,13 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use Combodo\iTop\HubConnector\Controller\HubController;
|
||||
|
||||
require_once(APPROOT.'application/utils.inc.php');
|
||||
require_once(APPROOT.'core/log.class.inc.php');
|
||||
IssueLog::Enable(APPROOT.'log/error.log');
|
||||
|
||||
require_once(APPROOT.'setup/runtimeenv.class.inc.php');
|
||||
require_once(APPROOT.'setup/backup.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'core/dict.class.inc.php');
|
||||
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
|
||||
require_once(__DIR__.'/hubruntimeenvironment.class.inc.php');
|
||||
|
||||
/**
|
||||
* Overload of DBBackup to handle logging
|
||||
*/
|
||||
class DBBackupWithErrorReporting extends DBBackup
|
||||
{
|
||||
protected $aInfos = [];
|
||||
|
||||
protected $aErrors = [];
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
$aInfos[] = $sMsg;
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
IssueLog::Error($sMsg);
|
||||
$aErrors[] = $sMsg;
|
||||
}
|
||||
|
||||
public function GetInfos()
|
||||
{
|
||||
return $this->aInfos;
|
||||
}
|
||||
|
||||
public function GetErrors()
|
||||
{
|
||||
return $this->aErrors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sTargetFile
|
||||
* @throws Exception
|
||||
* @return DBBackupWithErrorReporting
|
||||
*/
|
||||
function DoBackup($sTargetFile)
|
||||
{
|
||||
// Make sure the target directory exists
|
||||
$sBackupDir = dirname($sTargetFile);
|
||||
SetupUtils::builddir($sBackupDir);
|
||||
|
||||
$oBackup = new DBBackupWithErrorReporting();
|
||||
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
|
||||
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||
|
||||
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
|
||||
$oMutex->Lock();
|
||||
try {
|
||||
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
|
||||
} catch (Exception $e) {
|
||||
$oMutex->Unlock();
|
||||
throw $e;
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
return $oBackup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the status of the current ajax execution (as a JSON structure)
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param bool $bSuccess
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = [])
|
||||
{
|
||||
// Do not use AjaxPage during setup phases, because it uses InterfaceDiscovery in Twig compilation
|
||||
$oPage = new JsonPage();
|
||||
$aResult = [
|
||||
'code' => $iErrorCode,
|
||||
'message' => $sMessage,
|
||||
'fields' => $aMoreFields,
|
||||
];
|
||||
$oPage->SetData($aResult);
|
||||
$oPage->SetOutputDataOnly(true);
|
||||
$oPage->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a successful execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportSuccess($sMessage, $aMoreFields = [])
|
||||
{
|
||||
ReportStatus($sMessage, true, 0, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a failed execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportError($sMessage, $iErrorCode, $aMoreFields = [])
|
||||
{
|
||||
if ($iErrorCode == 0) {
|
||||
// 0 means no error, so change it if no meaningful error code is supplied
|
||||
$iErrorCode = -1;
|
||||
}
|
||||
ReportStatus($sMessage, false, $iErrorCode, $aMoreFields);
|
||||
}
|
||||
require_once(__DIR__.'/src/Controller/HubController.php');
|
||||
|
||||
try {
|
||||
SetupUtils::ExitMaintenanceMode(false); // Reset maintenance mode in case of problem
|
||||
@@ -183,7 +67,7 @@ try {
|
||||
foreach ($aChecks as $oCheckResult) {
|
||||
if ($oCheckResult->iSeverity == CheckResult::ERROR) {
|
||||
$bFailed = true;
|
||||
ReportError($oCheckResult->sLabel, -2);
|
||||
HubController::GetInstance()->ReportError($oCheckResult->sLabel, -2);
|
||||
}
|
||||
}
|
||||
if (!$bFailed) {
|
||||
@@ -191,169 +75,27 @@ try {
|
||||
$fFreeSpace = SetupUtils::CheckDiskSpace($sDBBackupPath);
|
||||
if ($fFreeSpace !== false) {
|
||||
$sMessage = Dict::Format('iTopHub:BackupFreeDiskSpaceIn', SetupUtils::HumanReadableSize($fFreeSpace), dirname($sDBBackupPath));
|
||||
ReportSuccess($sMessage);
|
||||
HubController::GetInstance()->ReportSuccess($sMessage);
|
||||
} else {
|
||||
ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
|
||||
HubController::GetInstance()->ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'do_backup':
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
|
||||
|
||||
try {
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
SetupLog::Info('Backup starts...');
|
||||
set_time_limit(0);
|
||||
$sBackupPath = APPROOT.'/data/backups/manual/backup-';
|
||||
$iSuffix = 1;
|
||||
$sSuffix = '';
|
||||
// Generate a unique name...
|
||||
do {
|
||||
$sBackupFile = $sBackupPath.date('Y-m-d-His').$sSuffix;
|
||||
$sSuffix = '-'.$iSuffix;
|
||||
$iSuffix++ ;
|
||||
} while (file_exists($sBackupFile));
|
||||
|
||||
$oBackup = DoBackup($sBackupFile);
|
||||
$aErrors = $oBackup->GetErrors();
|
||||
if (count($aErrors) > 0) {
|
||||
SetupLog::Error('Backup failed.');
|
||||
SetupLog::Error(implode("\n", $aErrors));
|
||||
ReportError(Dict::S('iTopHub:BackupFailed'), -1, $aErrors);
|
||||
} else {
|
||||
SetupLog::Info('Backup successfully completed.');
|
||||
ReportSuccess(Dict::S('iTopHub:BackupOk'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error($e->getMessage());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
HubController::GetInstance()->LaunchBackup();
|
||||
break;
|
||||
|
||||
case 'compile':
|
||||
SetupLog::Info('Deployment starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
// First step: prepare the datamodel, if it fails, roll-back
|
||||
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', []);
|
||||
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', []);
|
||||
|
||||
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
|
||||
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
|
||||
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
if ($oConfig->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
|
||||
$aSelectModules = $oRuntimeEnv->CompileFrom('production', false); // WARNING symlinks does not seem to be compatible with manual Commit
|
||||
|
||||
$oRuntimeEnv->UpdateIncludes($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
// Safety check: check the inter dependencies, will throw an exception in case of inconsistency
|
||||
$oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CheckMetaModel(); // Will throw an exception if a problem is detected
|
||||
|
||||
// Everything seems Ok so far, commit in env-production!
|
||||
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
|
||||
$oRuntimeEnv->Commit();
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Compilation completed...');
|
||||
ReportSuccess('Ok'); // No access to Dict::S here
|
||||
HubController::GetInstance()->LaunchCompile();
|
||||
break;
|
||||
|
||||
case 'move_to_production':
|
||||
// Second step: update the schema and the data
|
||||
// Everything happening below is based on env-production
|
||||
$oRuntimeEnv = new RunTimeEnvironment('production', true);
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
// Load the "production" config file to clone & update it
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
SetupUtils::EnterReadOnlyMode($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$aSelectedModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
} else {
|
||||
$aSelectedModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
|
||||
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
|
||||
|
||||
// Record the installation so that the "about box" knows about the installed modules
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
// Plus all "remote" extensions
|
||||
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) {
|
||||
$oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
$aSelectedExtensionCodes = [];
|
||||
foreach ($oExtensionsMap->GetChoices() as $oExtension) {
|
||||
$aSelectedExtensionCodes[] = $oExtension->sCode;
|
||||
}
|
||||
$aSelectedExtensions = $oExtensionsMap->GetChoices();
|
||||
$oRuntimeEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModules, $aSelectedExtensionCodes, 'Done by the iTop Hub Connector');
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Deployment successfully completed.');
|
||||
ReportSuccess(Dict::S('iTopHub:CompiledOK'));
|
||||
} catch (Exception $e) {
|
||||
if (file_exists(utils::GetDataPath().'hub/compile_authent')) {
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
}
|
||||
// Note: at this point, the dictionnary is not necessarily loaded
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
} finally {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
}
|
||||
HubController::GetInstance()->LaunchDeploy();
|
||||
break;
|
||||
|
||||
default:
|
||||
ReportError("Invalid operation: '$sOperation'", -1);
|
||||
HubController::GetInstance()->ReportError("Invalid operation: '$sOperation'", -1);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
@@ -361,5 +103,5 @@ try {
|
||||
|
||||
utils::PopArchiveMode();
|
||||
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
HubController::GetInstance()->ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
@@ -186,9 +186,7 @@ function collect_configuration()
|
||||
|
||||
// iTop modules
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$aInstalledModules = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install WHERE installed = '".$sLatestInstallationDate."' AND parent_id != 0");
|
||||
$aInstalledModules = ModuleInstallationRepository::GetInstance()->ReadFromDB($oConfig);
|
||||
|
||||
foreach ($aInstalledModules as $aDBInfo) {
|
||||
$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\Controller;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use Combodo\iTop\HubConnector\Model\DBBackupWithErrorReporting;
|
||||
use Combodo\iTop\HubConnector\setup\HubRunTimeEnvironment;
|
||||
use Config;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use iTopExtension;
|
||||
use iTopExtensionsMap;
|
||||
use iTopMutex;
|
||||
use LoginWebPage;
|
||||
use MetaModel;
|
||||
use MFCompiler;
|
||||
use RunTimeEnvironment;
|
||||
use SecurityException;
|
||||
use SetupLog;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
require_once(APPROOT.'setup/runtimeenv.class.inc.php');
|
||||
require_once(APPROOT.'setup/backup.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'core/dict.class.inc.php');
|
||||
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
|
||||
require_once(__DIR__.'/../setup/hubruntimeenvironment.class.inc.php');
|
||||
|
||||
class HubController
|
||||
{
|
||||
private static HubController $oInstance;
|
||||
protected $bOutputHeaders = false;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): HubController
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new HubController();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?HubController $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function LaunchBackup()
|
||||
{
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
|
||||
|
||||
try {
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
SetupLog::Info('Backup starts...');
|
||||
set_time_limit(0);
|
||||
$sBackupPath = APPROOT.'/data/backups/manual/backup-';
|
||||
$iSuffix = 1;
|
||||
$sSuffix = '';
|
||||
// Generate a unique name...
|
||||
do {
|
||||
$sBackupFile = $sBackupPath.date('Y-m-d-His').$sSuffix;
|
||||
$sSuffix = '-'.$iSuffix;
|
||||
$iSuffix++ ;
|
||||
} while (file_exists($sBackupFile));
|
||||
|
||||
$oBackup = $this->DoBackup($sBackupFile);
|
||||
$aErrors = $oBackup->GetErrors();
|
||||
if (count($aErrors) > 0) {
|
||||
SetupLog::Error('Backup failed.');
|
||||
SetupLog::Error(implode("\n", $aErrors));
|
||||
$this->ReportError(Dict::S('iTopHub:BackupFailed'), -1, $aErrors);
|
||||
} else {
|
||||
SetupLog::Info('Backup successfully completed.');
|
||||
$this->ReportSuccess(Dict::S('iTopHub:BackupOk'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error($e->getMessage());
|
||||
$this->ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sTargetFile
|
||||
* @throws Exception
|
||||
* @return DBBackupWithErrorReporting
|
||||
*/
|
||||
public function DoBackup($sTargetFile): DBBackupWithErrorReporting
|
||||
{
|
||||
// Make sure the target directory exists
|
||||
$sBackupDir = dirname($sTargetFile);
|
||||
SetupUtils::builddir($sBackupDir);
|
||||
|
||||
$oBackup = new DBBackupWithErrorReporting();
|
||||
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
|
||||
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||
|
||||
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
|
||||
$oMutex->Lock();
|
||||
try {
|
||||
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
|
||||
} catch (Exception $e) {
|
||||
$oMutex->Unlock();
|
||||
throw $e;
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
return $oBackup;
|
||||
}
|
||||
|
||||
public function LaunchCompile()
|
||||
{
|
||||
SetupLog::Info('Deployment starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
// First step: prepare the datamodel, if it fails, roll-back
|
||||
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', []);
|
||||
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', []);
|
||||
|
||||
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
|
||||
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
|
||||
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
if ($oConfig->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
|
||||
$aSelectModules = $oRuntimeEnv->CompileFrom('production'); // WARNING symlinks does not seem to be compatible with manual Commit
|
||||
|
||||
$oRuntimeEnv->UpdateIncludes($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
// Safety check: check the inter dependencies, will throw an exception in case of inconsistency
|
||||
$oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CheckMetaModel(); // Will throw an exception if a problem is detected
|
||||
|
||||
// Everything seems Ok so far, commit in env-production!
|
||||
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
|
||||
$oRuntimeEnv->Commit();
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Compilation completed...');
|
||||
|
||||
$this->ReportSuccess('Ok'); // No access to Dict::S here
|
||||
}
|
||||
|
||||
public function LaunchDeploy()
|
||||
{
|
||||
// Second step: update the schema and the data
|
||||
// Everything happening below is based on env-production
|
||||
$oRuntimeEnv = new RunTimeEnvironment('production', true);
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
// Load the "production" config file to clone & update it
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
SetupUtils::EnterReadOnlyMode($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup');
|
||||
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
|
||||
|
||||
// Record the installation so that the "about box" knows about the installed modules
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
// Plus all "remote" extensions
|
||||
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) {
|
||||
$oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
$aSelectedExtensionCodes = [];
|
||||
foreach ($oExtensionsMap->GetChoices() as $oExtension) {
|
||||
$aSelectedExtensionCodes[] = $oExtension->sCode;
|
||||
}
|
||||
$aSelectedExtensions = $oExtensionsMap->GetChoices();
|
||||
$oRuntimeEnv->RecordInstallation($oConfig, $sDataModelVersion, array_keys($aAvailableModules), $aSelectedExtensionCodes, 'Done by the iTop Hub Connector');
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Deployment successfully completed.');
|
||||
$this->ReportSuccess(Dict::S('iTopHub:CompiledOK'));
|
||||
} catch (Exception $e) {
|
||||
if (file_exists(utils::GetDataPath().'hub/compile_authent')) {
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
}
|
||||
// Note: at this point, the dictionnary is not necessarily loaded
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
|
||||
$this->ReportError($e->getMessage(), $e->getCode());
|
||||
} finally {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the status of the current ajax execution (as a JSON structure)
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param bool $bSuccess
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = [])
|
||||
{
|
||||
// Do not use AjaxPage during setup phases, because it uses InterfaceDiscovery in Twig compilation
|
||||
$this->oLastJsonPage = new JsonPage();
|
||||
$this->oLastJsonPage->SetOutputHeaders($this->bOutputHeaders);
|
||||
$aResult = [
|
||||
'code' => $iErrorCode,
|
||||
'message' => $sMessage,
|
||||
'fields' => $aMoreFields,
|
||||
];
|
||||
$this->oLastJsonPage->SetData($aResult);
|
||||
$this->oLastJsonPage->SetOutputDataOnly(true);
|
||||
$this->oLastJsonPage->output();
|
||||
}
|
||||
|
||||
private ?JsonPage $oLastJsonPage = null;
|
||||
|
||||
public function GetLastJsonPage(): ?JsonPage
|
||||
{
|
||||
return $this->oLastJsonPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a successful execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportSuccess($sMessage, $aMoreFields = [])
|
||||
{
|
||||
$this->ReportStatus($sMessage, true, 0, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a failed execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportError($sMessage, $iErrorCode, $aMoreFields = [])
|
||||
{
|
||||
if ($iErrorCode == 0) {
|
||||
// 0 means no error, so change it if no meaningful error code is supplied
|
||||
$iErrorCode = -1;
|
||||
}
|
||||
$this->ReportStatus($sMessage, false, $iErrorCode, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dont print headers for testing purpose mainly
|
||||
* @param bool bOutputHeaders
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetOutputHeaders(bool $bOutputHeaders): void
|
||||
{
|
||||
$this->bOutputHeaders = $bOutputHeaders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\Model;
|
||||
|
||||
use DBBackup;
|
||||
use IssueLog;
|
||||
|
||||
/**
|
||||
* Overload of DBBackup to handle logging
|
||||
*/
|
||||
class DBBackupWithErrorReporting extends DBBackup
|
||||
{
|
||||
protected $aInfos = [];
|
||||
|
||||
protected $aErrors = [];
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
$this->aInfos[] = $sMsg;
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
IssueLog::Error($sMsg);
|
||||
$this->aErrors[] = $sMsg;
|
||||
}
|
||||
|
||||
public function GetInfos(): array
|
||||
{
|
||||
return $this->aInfos;
|
||||
}
|
||||
|
||||
public function GetErrors(): array
|
||||
{
|
||||
return $this->aErrors;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\setup;
|
||||
|
||||
use Config;
|
||||
use Exception;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $sEnvironment
|
||||
* @param string $bAutoCommit
|
||||
*/
|
||||
@@ -24,6 +32,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
/**
|
||||
* Update the includes for the target environment
|
||||
*
|
||||
* @param Config $oConfig
|
||||
*/
|
||||
public function UpdateIncludes(Config $oConfig)
|
||||
@@ -33,7 +42,9 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
/**
|
||||
* Move an extension (path to folder of this extension) to the target environment
|
||||
*
|
||||
* @param string $sExtensionDirectory The folder of the extension
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function MoveExtension($sExtensionDirectory)
|
||||
@@ -57,8 +68,10 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
|
||||
/**
|
||||
* Move the selected extensions located in the given directory in data/<target-env>-modules
|
||||
*
|
||||
* @param string $sDownloadedExtensionsDir The directory to scan
|
||||
* @param string[] $aSelectedExtensionDirs The list of folders to move
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function MoveSelectedExtensions($sDownloadedExtensionsDir, $aSelectedExtensionDirs)
|
||||
@@ -12,8 +12,7 @@ SetupWebPage::AddModule(
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/2.7.1',
|
||||
'itop-portal/3.0.0', // module_design_itop_design->module_designs->itop-portal
|
||||
'itop-structure/2.7.1 || itop-portal/3.0.0', // itop-portal : module_design_itop_design->module_designs->itop-portal
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -26,23 +26,12 @@ use Composer\Semver\VersionParser;
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
@@ -320,24 +309,6 @@ class InstalledVersions
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,9 +325,7 @@ class InstalledVersions
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
@@ -364,14 +333,11 @@ class InstalledVersions
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'combodo/itop',
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -22,7 +22,7 @@
|
||||
'combodo/itop' => array(
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '19d062aa830b6d6c7d17ac4046fc9ee2c5e3fab1',
|
||||
'reference' => '469afdb2f9aea1b6e078a2a5bb12f09a969d60e0',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
||||
14
setup/SetupDBBackup.php
Normal file
14
setup/SetupDBBackup.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class SetupDBBackup extends DBBackup
|
||||
{
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
SetupLog::Ok('Info - '.$sMsg);
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
SetupLog::Ok('Error - '.$sMsg);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
|
||||
/**
|
||||
* Main program
|
||||
*/
|
||||
$sOperation = Utils::ReadParam('operation', '');
|
||||
$sOperation = utils::ReadParam('operation', '');
|
||||
try {
|
||||
SetupUtils::CheckSetupToken();
|
||||
|
||||
@@ -138,8 +138,7 @@ try {
|
||||
ini_set('display_errors', true);
|
||||
ini_set('display_startup_errors', true);
|
||||
|
||||
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps_autoload.php');
|
||||
|
||||
$sClass = utils::ReadParam('step_class', '');
|
||||
$sState = utils::ReadParam('step_state', '');
|
||||
@@ -164,7 +163,7 @@ try {
|
||||
break;
|
||||
|
||||
case 'toggle_use_symbolic_links':
|
||||
$sUseSymbolicLinks = Utils::ReadParam('bUseSymbolicLinks', false);
|
||||
$sUseSymbolicLinks = utils::ReadParam('bUseSymbolicLinks', false);
|
||||
$bUseSymbolicLinks = ($sUseSymbolicLinks === 'true');
|
||||
MFCompiler::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
|
||||
echo "toggle useSymbolicLinks flag : $bUseSymbolicLinks";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,8 @@
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\WebPage\iTopWebPage;
|
||||
use Combodo\iTop\Application\WebPage\Page;
|
||||
use Combodo\iTop\DesignElement;
|
||||
use Combodo\iTop\DesignDocument;
|
||||
use Combodo\iTop\DesignElement;
|
||||
|
||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
||||
@@ -3357,6 +3357,8 @@ EOF;
|
||||
|
||||
$bDataXmlPrecompiledFileExists = false;
|
||||
clearstatcache();
|
||||
|
||||
$iDataXmlFileLastModified = 0;
|
||||
if (!empty($sPrecompiledFileUri)) {
|
||||
$sDataXmlProvidedPrecompiledFile = $sTempTargetDir.DIRECTORY_SEPARATOR.$sPrecompiledFileUri;
|
||||
$bDataXmlPrecompiledFileExists = file_exists($sDataXmlProvidedPrecompiledFile) ;
|
||||
|
||||
@@ -34,7 +34,7 @@ require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
|
||||
|
||||
$sOperation = Utils::ReadParam('operation', 'step1');
|
||||
$sOperation = utils::ReadParam('operation', 'step1');
|
||||
$oP = new SetupPage('iTop email test utility');
|
||||
|
||||
// Although this page doesn't expose sensitive info, with it we can send multiple emails
|
||||
@@ -208,7 +208,7 @@ function DisplayStep2(SetupPage $oP, $sFrom, $sTo)
|
||||
$oP->add("<p>Sending an email to '".htmlentities($sTo, ENT_QUOTES, 'utf-8')."'... (From: '".htmlentities($sFrom, ENT_QUOTES, 'utf-8')."')</p>\n");
|
||||
$oP->add("<form method=\"post\">\n");
|
||||
|
||||
$oEmail = new Email();
|
||||
$oEmail = new EMail();
|
||||
$oEmail->SetRecipientTO($sTo);
|
||||
$oEmail->SetRecipientFrom($sFrom);
|
||||
$oEmail->SetSubject("Test iTop");
|
||||
@@ -256,8 +256,8 @@ try {
|
||||
|
||||
case 'step2':
|
||||
$oP->no_cache();
|
||||
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
|
||||
$sFrom = Utils::ReadParam('from', '', false, 'raw_data');
|
||||
$sTo = utils::ReadParam('to', '', false, 'raw_data');
|
||||
$sFrom = utils::ReadParam('from', '', false, 'raw_data');
|
||||
DisplayStep2($oP, $sFrom, $sTo);
|
||||
break;
|
||||
|
||||
|
||||
@@ -3,143 +3,11 @@
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'/setup/itopextension.class.inc.php');
|
||||
require_once(APPROOT.'/setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
|
||||
require_once(APPROOT.'/setup/modulediscovery.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
/**
|
||||
* Basic helper class to describe an extension, with some characteristics and a list of modules
|
||||
*/
|
||||
class iTopExtension
|
||||
{
|
||||
public const SOURCE_WIZARD = 'datamodels';
|
||||
public const SOURCE_MANUAL = 'extensions';
|
||||
public const SOURCE_REMOTE = 'data';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sCode;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sVersion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sInstalledVersion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sLabel;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sDescription;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sSource;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bMandatory;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sMoreInfoUrl;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bMarkedAsChosen;
|
||||
/**
|
||||
* If null, check if at least one module cannot be uninstalled
|
||||
* @var bool|null
|
||||
*/
|
||||
public ?bool $bCanBeUninstalled = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bVisible;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModules;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModuleVersion;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModuleInfo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sSourceDir;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $aMissingDependencies;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bInstalled = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bRemovedFromDisk = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->sCode = '';
|
||||
$this->sLabel = '';
|
||||
$this->sDescription = '';
|
||||
$this->sSource = self::SOURCE_WIZARD;
|
||||
$this->bMandatory = false;
|
||||
$this->sMoreInfoUrl = '';
|
||||
$this->bMarkedAsChosen = false;
|
||||
$this->sVersion = ITOP_VERSION;
|
||||
$this->sInstalledVersion = '';
|
||||
$this->aModules = [];
|
||||
$this->aModuleVersion = [];
|
||||
$this->aModuleInfo = [];
|
||||
$this->sSourceDir = '';
|
||||
$this->bVisible = true;
|
||||
$this->aMissingDependencies = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeUninstalled(): bool
|
||||
{
|
||||
if (!is_null($this->bCanBeUninstalled)) {
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
|
||||
$this->bCanBeUninstalled = $aModuleInfo['uninstallable'] === 'yes';
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to discover all available extensions on a given iTop system
|
||||
@@ -148,32 +16,38 @@ class iTopExtensionsMap
|
||||
{
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
* @param string $sFromEnvironment The environment to scan
|
||||
* @param bool $bNormailizeOldExtension true to "magically" convert some well-known old extensions (i.e. a set of modules) to the new iTopExtension format
|
||||
* @return void
|
||||
* @var array $aExtensions
|
||||
*/
|
||||
protected $aExtensions;
|
||||
/**
|
||||
* The list of all currently installed extensions
|
||||
* @var array|null
|
||||
* @var array $aInstalledExtensions
|
||||
*/
|
||||
protected ?array $aInstalledExtensions = null;
|
||||
protected array $aInstalledExtensions;
|
||||
|
||||
protected array $aExtensionsByCode;
|
||||
/**
|
||||
* The list of directories browsed using the ReadDir method when building the map
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aScannedDirs;
|
||||
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
* @param string $sFromEnvironment The environment to scan
|
||||
* @param bool $bNormailizeOldExtension true to "magically" convert some well-known old extensions (i.e. a set of modules) to the new iTopExtension format
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aExtensionsByCode = [];
|
||||
$this->aScannedDirs = [];
|
||||
$this->ScanDisk($sFromEnvironment);
|
||||
foreach ($aExtraDirs as $sDir) {
|
||||
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$this->CheckDependencies($sFromEnvironment);
|
||||
$this->CheckDependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,8 +58,10 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected function ScanDisk($sEnvironment)
|
||||
{
|
||||
if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x') && !$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) {
|
||||
if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) {
|
||||
//no installation xml found in 2.x: let's read all extensions in 2.x first
|
||||
if (!$this->ReadDir(APPROOT.'/datamodels/2.x', iTopExtension::SOURCE_WIZARD)) {
|
||||
//nothing found in 2.x : fallback read in 1.x (flat structure)
|
||||
$this->ReadDir(APPROOT.'/datamodels/1.x', iTopExtension::SOURCE_WIZARD);
|
||||
}
|
||||
}
|
||||
@@ -261,6 +137,7 @@ class iTopExtensionsMap
|
||||
// This "new" extension is "newer" than the previous one, let's replace the previous one
|
||||
unset($this->aExtensions[$key]);
|
||||
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
|
||||
$this->aExtensionsByCode[$oNewExtension->sCode] = $oNewExtension;
|
||||
return;
|
||||
} else {
|
||||
// This "new" extension is not "newer" than the previous one, let's ignore it
|
||||
@@ -270,6 +147,22 @@ class iTopExtensionsMap
|
||||
}
|
||||
// Finally it's not a duplicate, let's add it to the list
|
||||
$this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension;
|
||||
$this->aExtensionsByCode[$oNewExtension->sCode] = $oNewExtension;
|
||||
}
|
||||
|
||||
public function RemoveExtension(string $sCode): void
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sCode);
|
||||
if (is_null($oExtension)) {
|
||||
\IssueLog::Error(__METHOD__.": cannot find extension to remove", null, [$sCode]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
\IssueLog::Debug(__METHOD__.": remove extension from map", null, [$oExtension->sCode => $oExtension->sSourceDir]);
|
||||
|
||||
unset($this->aExtensions[$oExtension->sCode.'/'.$oExtension->sVersion]);
|
||||
unset($this->aExtensionsByCode[$sCode]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,12 +173,28 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function GetFromExtensionCode(string $sExtensionCode): ?iTopExtension
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode === $sExtensionCode) {
|
||||
return $oExtension;
|
||||
return $this->aExtensionsByCode[$sExtensionCode] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $aExtensionCodes
|
||||
* @return void
|
||||
*/
|
||||
public function DeclareExtensionAsRemoved(array $aExtensionCodes): void
|
||||
{
|
||||
$aRemovedExtension = [];
|
||||
foreach ($aExtensionCodes as $sCode) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
$oExtension = $this->GetFromExtensionCode($sCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$aRemovedExtension [] = $oExtension;
|
||||
\IssueLog::Info(__METHOD__.": remove extension locally", null, ['extension_code' => $oExtension->sCode]);
|
||||
} else {
|
||||
\IssueLog::Warning(__METHOD__." cannot find extensions", null, ['code' => $sCode]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
ModuleDiscovery::DeclareRemovedExtensions($aRemovedExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,19 +255,15 @@ class iTopExtensionsMap
|
||||
// to this extension
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleVersion == '') {
|
||||
// Provide a default module version since version is mandatory when recording ExtensionInstallation
|
||||
$sModuleVersion = '0.0.1';
|
||||
}
|
||||
$aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG]['uninstallable'] ??= 'yes';
|
||||
|
||||
if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) {
|
||||
// Already inside an extension, let's add this module the list of modules belonging to this extension
|
||||
$this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
} else {
|
||||
// Not already inside a folder containing an 'extension.xml' file
|
||||
$oExtension = null;
|
||||
if ($sParentExtensionId !== null) {
|
||||
$oExtension = $this->aExtensions[$sParentExtensionId] ?? null;
|
||||
}
|
||||
|
||||
if (is_null($oExtension)) {
|
||||
// Not already inside an folder containing an 'extension.xml' file
|
||||
|
||||
// Ignore non-visible modules and auto-select ones, since these are never prompted
|
||||
// as a choice to the end-user
|
||||
@@ -382,6 +287,13 @@ class iTopExtensionsMap
|
||||
$oExtension->sSourceDir = $sSearchDir;
|
||||
$oExtension->bVisible = $bVisible;
|
||||
$this->AddExtension($oExtension);
|
||||
} else {
|
||||
$oExtension->aModules[] = $sModuleName;
|
||||
$oExtension->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$oExtension->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
|
||||
$this->aExtensions[$sParentExtensionId] = $oExtension;
|
||||
$this->aExtensionsByCode[$oExtension->sCode] = $oExtension;
|
||||
}
|
||||
|
||||
closedir($hDir);
|
||||
@@ -403,10 +315,9 @@ class iTopExtensionsMap
|
||||
/**
|
||||
* Check if some extension contains a module with missing dependencies...
|
||||
* If so, populate the aMissingDepenencies array
|
||||
* @param string $sFromEnvironment
|
||||
* @return void
|
||||
*/
|
||||
protected function CheckDependencies($sFromEnvironment)
|
||||
protected function CheckDependencies()
|
||||
{
|
||||
$aSearchDirs = [];
|
||||
|
||||
@@ -418,7 +329,7 @@ class iTopExtensionsMap
|
||||
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
|
||||
|
||||
try {
|
||||
$aAllModules = ModuleDiscovery::GetAvailableModules($aSearchDirs, true);
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
|
||||
} catch (MissingDependencyException $e) {
|
||||
// Some modules have missing dependencies
|
||||
// Let's check what is the impact at the "extensions" level
|
||||
@@ -458,6 +369,75 @@ class iTopExtensionsMap
|
||||
return array_merge($this->aInstalledExtensions ?? [], $this->aExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bKeepMissingDependencyExtensions
|
||||
*
|
||||
* @return array<\iTopExtension>>
|
||||
*/
|
||||
|
||||
public function GetAllExtensionsToDisplayInSetup(bool $bKeepMissingDependencyExtensions = false): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible)) {
|
||||
if ($bKeepMissingDependencyExtensions || (count($oExtension->aMissingDependencies) == 0)) {
|
||||
if (!$oExtension->bMandatory) {
|
||||
$oExtension->bMandatory = ($oExtension->sSource === iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$aRes[$oExtension->sCode] = $oExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
public function GetAllExtensionsOptionInfo(): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->GetAllExtensionsToDisplayInSetup() as $sCode => $oExtension) {
|
||||
$aRes[] = [
|
||||
'extension_code' => $oExtension->sCode,
|
||||
'title' => $oExtension->sLabel,
|
||||
'description' => $oExtension->sDescription,
|
||||
'more_info' => $oExtension->sMoreInfoUrl,
|
||||
'default' => true, // by default offer to install all modules
|
||||
'modules' => $oExtension->aModules,
|
||||
'mandatory' => $oExtension->bMandatory,
|
||||
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
];
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
protected function GetExtensionSourceLabel($sSource)
|
||||
{
|
||||
$sDecorationClass = '';
|
||||
switch ($sSource) {
|
||||
case iTopExtension::SOURCE_MANUAL:
|
||||
$sResult = 'Local extensions folder';
|
||||
$sDecorationClass = 'fas fa-folder';
|
||||
break;
|
||||
|
||||
case iTopExtension::SOURCE_REMOTE:
|
||||
$sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer';
|
||||
$sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler';
|
||||
break;
|
||||
|
||||
default:
|
||||
$sResult = '';
|
||||
}
|
||||
if ($sResult == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given extension as chosen
|
||||
* @param string $sExtensionCode The code of the extension (code without version number)
|
||||
@@ -466,11 +446,17 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function MarkAsChosen($sExtensionCode, $bMark = true)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
$oExtension->bMarkedAsChosen = $bMark;
|
||||
break;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->bMarkedAsChosen = $bMark;
|
||||
}
|
||||
}
|
||||
|
||||
public function MarkAsUninstallable($sExtensionCode, $bMark = true)
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->bUninstallable = $bMark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,11 +467,11 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function IsMarkedAsChosen($sExtensionCode)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
return $oExtension->bMarkedAsChosen;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
return $oExtension->bMarkedAsChosen;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -497,11 +483,9 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected function SetInstalledVersion($sExtensionCode, $sInstalledVersion)
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode == $sExtensionCode) {
|
||||
$oExtension->sInstalledVersion = $sInstalledVersion;
|
||||
break;
|
||||
}
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->sInstalledVersion = $sInstalledVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,14 +511,19 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function LoadChoicesFromDatabase(Config $oConfig)
|
||||
{
|
||||
foreach ($this->LoadInstalledExtensionsFromDatabase($oConfig) as $oExtension) {
|
||||
$aLoadInstalledExtensionsFromDatabase = $this->LoadInstalledExtensionsFromDatabase($oConfig);
|
||||
if (false === $aLoadInstalledExtensionsFromDatabase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($aLoadInstalledExtensionsFromDatabase as $oExtension) {
|
||||
$this->MarkAsChosen($oExtension->sCode);
|
||||
$this->SetInstalledVersion($oExtension->sCode, $oExtension->sVersion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
public function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
try {
|
||||
if (CMDBSource::DBName() === null) {
|
||||
@@ -577,6 +566,27 @@ class iTopExtensionsMap
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetChoicesFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
try {
|
||||
if (CMDBSource::DBName() === null) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
|
||||
$aDBInfo = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
|
||||
$aChoices = [];
|
||||
foreach ($aDBInfo as $aExtensionInfo) {
|
||||
$aChoices[] = $aExtensionInfo['code'];
|
||||
}
|
||||
|
||||
return $aChoices;
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir)
|
||||
* @param string $sModuleNameToFind
|
||||
@@ -585,8 +595,6 @@ class iTopExtensionsMap
|
||||
*/
|
||||
public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE)
|
||||
{
|
||||
$bChosen = false;
|
||||
|
||||
foreach ($this->GetAllExtensions() as $oExtension) {
|
||||
if (($oExtension->sSource == $sInSourceOnly) &&
|
||||
($oExtension->bMarkedAsChosen == true) &&
|
||||
|
||||
105
setup/feature_removal/AbstractSetupAudit.php
Normal file
105
setup/feature_removal/AbstractSetupAudit.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use ContextTag;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use SetupLog;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
abstract class AbstractSetupAudit
|
||||
{
|
||||
protected bool $bClassesInitialized = false;
|
||||
protected array $aClassesBefore = [];
|
||||
protected array $aClassesAfter = [];
|
||||
protected array $aRemovedClasses = [];
|
||||
protected array $aFinalClassesToCleanup = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
abstract public function ComputeClasses(): void;
|
||||
|
||||
public function GetRemovedClasses(): array
|
||||
{
|
||||
$this->ComputeClasses();
|
||||
|
||||
if (count($this->aRemovedClasses) == 0) {
|
||||
if (count($this->aClassesBefore) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
if (count($this->aClassesAfter) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
$aExtensionsNames = array_diff($this->aClassesBefore, $this->aClassesAfter);
|
||||
$this->aRemovedClasses = [];
|
||||
$aClasses = array_values($aExtensionsNames);
|
||||
sort($aClasses);
|
||||
|
||||
foreach ($aClasses as $i => $sClass) {
|
||||
$this->aRemovedClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
public function GetIssues(bool $bStopDataCheckAtFirstIssue = false): array
|
||||
{
|
||||
$this->aFinalClassesToCleanup = [];
|
||||
|
||||
foreach ($this->GetRemovedClasses() as $sClass) {
|
||||
if (MetaModel::IsAbstract($sClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MetaModel::IsStandaloneClass($sClass)) {
|
||||
$iCount = $this->Count($sClass);
|
||||
$this->aFinalClassesToCleanup[$sClass] = $iCount;
|
||||
if ($bStopDataCheckAtFirstIssue && $iCount > 0) {
|
||||
//setup envt: should raise issue ASAP
|
||||
$this->LogInfoWithProperLogger("Setup audit found data to cleanup", null, $this->aFinalClassesToCleanup);
|
||||
return $this->aFinalClassesToCleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->LogInfoWithProperLogger("Setup audit found data to cleanup", null, ['data_to_cleanup' => $this->aFinalClassesToCleanup]);
|
||||
return $this->aFinalClassesToCleanup;
|
||||
}
|
||||
|
||||
public function GetDataToCleanupCount(): int
|
||||
{
|
||||
$res = 0;
|
||||
foreach ($this->aFinalClassesToCleanup as $sClass => $iCount) {
|
||||
$res += $iCount;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
private function Count($sClass): int
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sClass", []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
return $oSet->Count();
|
||||
}
|
||||
|
||||
//could be shared with others in log APIs ?
|
||||
private function LogInfoWithProperLogger($sMessage, $sChannel = null, $aContext = []): void
|
||||
{
|
||||
if (ContextTag::Check(ContextTag::TAG_SETUP)) {
|
||||
SetupLog::Info($sMessage, $sChannel, $aContext);
|
||||
} else {
|
||||
IssueLog::Info($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
104
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
104
setup/feature_removal/DryRemovalRuntimeEnvironment.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Config;
|
||||
use InstallationChoicesToModuleConverter;
|
||||
use iTopExtensionsMap;
|
||||
use MetaModel;
|
||||
use ModuleDiscovery;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
public const DRY_REMOVAL_AUDIT_ENV = "extension-removal";
|
||||
|
||||
protected array $aExtensionsByCode;
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
*
|
||||
* @param string $sEnvironment (e.g. 'test')
|
||||
* @param bool $bAutoCommit (make the target environment directly, or build a temporary one)
|
||||
*/
|
||||
public function __construct($sEnvironment = self::DRY_REMOVAL_AUDIT_ENV, $bAutoCommit = true)
|
||||
{
|
||||
parent::__construct($sEnvironment, $bAutoCommit);
|
||||
$this->aExtensionsByCode = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSourceEnv
|
||||
* @param array $aExtensionCodesToRemove
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function Prepare(string $sSourceEnv, array $aExtensionCodesToRemove)
|
||||
{
|
||||
|
||||
$sEnv = $this->sFinalEnv;
|
||||
$this->aExtensionsByCode = $aExtensionCodesToRemove;
|
||||
|
||||
$this->Cleanup();
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sEnv-modules");
|
||||
|
||||
$this->DeclareExtensionAsRemoved($aExtensionCodesToRemove);
|
||||
|
||||
$oDryRemovalConfig = clone(MetaModel::GetConfig());
|
||||
$oDryRemovalConfig->ChangeModulesPath($sSourceEnv, $this->sFinalEnv);
|
||||
$this->WriteConfigFileSafe($oDryRemovalConfig);
|
||||
|
||||
$sSourceDir = $oDryRemovalConfig->Get('source_dir');
|
||||
$aSearchDirs = $this->GetExtraDirsToCompile($sSourceDir);
|
||||
|
||||
$aModulesToLoad = $this->GetModulesToLoad($sSourceEnv, $aSearchDirs);
|
||||
|
||||
try {
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true, $aModulesToLoad);
|
||||
} catch (\MissingDependencyException $e) {
|
||||
\IssueLog::Error("Cannot prepare setup due to dependency issue", null, ['msg' => $e->getMessage(), 'modules_to_load' => $aModulesToLoad]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function DeclareExtensionAsRemoved(array $aExtensionCodes): void
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap($this->sFinalEnv);
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes);
|
||||
}
|
||||
|
||||
private function GetModulesToLoad(string $sSourceEnv, $aSearchDirs): array
|
||||
{
|
||||
$oSourceConfig = new Config(utils::GetConfigFilePath($sSourceEnv));
|
||||
$aChoices = iTopExtensionsMap::GetChoicesFromDatabase($oSourceConfig);
|
||||
$sSourceDir = $oSourceConfig->Get('source_dir');
|
||||
|
||||
$sInstallFilePath = APPROOT.$sSourceDir.'/installation.xml';
|
||||
if (! is_file($sInstallFilePath)) {
|
||||
$sInstallFilePath = null;
|
||||
}
|
||||
|
||||
$aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath);
|
||||
$aModulesToLoad = [];
|
||||
foreach ($aModuleIdsToLoad as $sModuleId) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
$aModulesToLoad[] = $sModuleName;
|
||||
}
|
||||
return $aModulesToLoad;
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
$sEnv = $this->sFinalEnv;
|
||||
SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
SetupUtils::rrmdir(APPROOT."/data/cache-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/env-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/conf/$sEnv");
|
||||
@unlink(APPROOT."/data/datamodel-$sEnv.xml");
|
||||
}
|
||||
}
|
||||
40
setup/feature_removal/InplaceSetupAudit.php
Normal file
40
setup/feature_removal/InplaceSetupAudit.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use MetaModel;
|
||||
|
||||
require_once __DIR__.'/AbstractSetupAudit.php';
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
class InplaceSetupAudit extends AbstractSetupAudit
|
||||
{
|
||||
//file used when present to trigger audit exception when testing specific setups
|
||||
public const GETISSUE_ERROR_MSG_FILE_FORTESTONLY = '.setup_audit_error_msg.txt';
|
||||
|
||||
private string $sEnvAfter;
|
||||
|
||||
public function __construct(array $aClassesBefore, string $sEnvAfter)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->aClassesBefore = $aClassesBefore;
|
||||
$this->sEnvAfter = $sEnvAfter;
|
||||
}
|
||||
|
||||
public function ComputeClasses(): void
|
||||
{
|
||||
if ($this->bClassesInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sCurrentEnvt = MetaModel::GetEnvironment();
|
||||
|
||||
if ($sCurrentEnvt === $this->sEnvAfter) {
|
||||
$this->aClassesAfter = MetaModel::GetClasses();
|
||||
} else {
|
||||
$this->aClassesAfter = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvAfter);
|
||||
}
|
||||
|
||||
$this->bClassesInitialized = true;
|
||||
}
|
||||
}
|
||||
86
setup/feature_removal/ModelReflectionSerializer.php
Normal file
86
setup/feature_removal/ModelReflectionSerializer.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use ContextTag;
|
||||
use CoreException;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use SetupLog;
|
||||
use utils;
|
||||
|
||||
class ModelReflectionSerializer
|
||||
{
|
||||
private static ModelReflectionSerializer $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModelReflectionSerializer
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModelReflectionSerializer();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModelReflectionSerializer $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function GetModelFromEnvironment(string $sEnv): array
|
||||
{
|
||||
IssueLog::Info(__METHOD__, null, ['env' => $sEnv]);
|
||||
|
||||
$sCurrentEnvt = MetaModel::GetEnvironment();
|
||||
if ($sCurrentEnvt === $sEnv) {
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
if (count($aClasses) === 0) {
|
||||
//MetaModel not started yet
|
||||
$sConfFile = utils::GetConfigFilePath($sEnv);
|
||||
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, false /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
}
|
||||
return $aClasses;
|
||||
}
|
||||
|
||||
$sPHPExec = trim(utils::GetConfig()->Get('php_path'));
|
||||
$sOutput = "";
|
||||
$iRes = 0;
|
||||
|
||||
$sCommandLine = sprintf("$sPHPExec %s/get_model_reflection.php --env=%s", __DIR__, escapeshellarg($sEnv));
|
||||
exec($sCommandLine, $sOutput, $iRes);
|
||||
if ($iRes != 0) {
|
||||
$this->LogErrorWithProperLogger("Cannot get classes", null, ['env' => $sEnv, 'code' => $iRes, "output" => $sOutput]);
|
||||
throw new CoreException("Cannot get classes from env ".$sEnv);
|
||||
}
|
||||
|
||||
$aClasses = json_decode($sOutput[0] ?? null, true);
|
||||
if (false === $aClasses) {
|
||||
$this->LogErrorWithProperLogger("Invalid JSON", null, ['env' => $sEnv, "output" => $sOutput]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
if (!is_array($aClasses)) {
|
||||
$this->LogErrorWithProperLogger("not an array", null, ['env' => $sEnv, "classes" => $aClasses, "output" => $sOutput]);
|
||||
throw new Exception("cannot get classes from $sEnv");
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
|
||||
//could be shared with others in log APIs ?
|
||||
private function LogErrorWithProperLogger($sMessage, $sChannel = null, $aContext = []): void
|
||||
{
|
||||
if (ContextTag::Check(ContextTag::TAG_SETUP)) {
|
||||
SetupLog::Error($sMessage, $sChannel, $aContext);
|
||||
} else {
|
||||
IssueLog::Error($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
setup/feature_removal/SetupAudit.php
Normal file
60
setup/feature_removal/SetupAudit.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
require_once __DIR__.'/AbstractSetupAudit.php';
|
||||
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
|
||||
|
||||
class SetupAudit extends AbstractSetupAudit
|
||||
{
|
||||
//file used when present to trigger audit exception when testing specific setups
|
||||
public const GETISSUE_ERROR_MSG_FILE_FORTESTONLY = '.setup_audit_error_msg.txt';
|
||||
|
||||
private string $sEnvBefore;
|
||||
private string $sEnvAfter;
|
||||
|
||||
public function __construct(string $sEnvBefore, string $sEnvAfter)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->sEnvBefore = $sEnvBefore;
|
||||
$this->sEnvAfter = $sEnvAfter;
|
||||
}
|
||||
|
||||
public function ComputeClasses(): void
|
||||
{
|
||||
if ($this->bClassesInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->aClassesBefore = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvBefore);
|
||||
$this->aClassesAfter = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->sEnvAfter);
|
||||
|
||||
$this->bClassesInitialized = true;
|
||||
}
|
||||
|
||||
public function GetRemovedClasses(): array
|
||||
{
|
||||
$this->ComputeClasses();
|
||||
|
||||
if (count($this->aRemovedClasses) == 0) {
|
||||
if (count($this->aClassesBefore) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
if (count($this->aClassesAfter) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
$aExtensionsNames = array_diff($this->aClassesBefore, $this->aClassesAfter);
|
||||
$this->aRemovedClasses = [];
|
||||
$aClasses = array_values($aExtensionsNames);
|
||||
sort($aClasses);
|
||||
|
||||
foreach ($aClasses as $i => $sClass) {
|
||||
$this->aRemovedClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
}
|
||||
41
setup/feature_removal/get_model_reflection.php
Normal file
41
setup/feature_removal/get_model_reflection.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
require_once(dirname(__DIR__, 2).'/approot.inc.php');
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
$sEnv = null;
|
||||
if (isset($argv)) {
|
||||
foreach ($argv as $iArg => $sArg) {
|
||||
if (preg_match('/^--env=(.*)$/', $sArg, $aMatches)) {
|
||||
$sEnv = $aMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($sEnv)) {
|
||||
echo "No environment provided (--env) to read datamodel.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sConfFile = utils::GetConfigFilePath($sEnv);
|
||||
|
||||
try {
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, false /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
echo $e->getTraceAsString();
|
||||
\SetupLog::Error(
|
||||
"Cannot read model from provided environment",
|
||||
null,
|
||||
[
|
||||
'env' => $sEnv,
|
||||
'error' => $e->getMessage(),
|
||||
'stack' => $e->getTraceAsString(),
|
||||
]
|
||||
);
|
||||
echo "Cannot read model from provided environment";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
|
||||
echo json_encode($aClasses);
|
||||
144
setup/itopextension.class.inc.php
Normal file
144
setup/itopextension.class.inc.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'/setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
|
||||
require_once(APPROOT.'/setup/modulediscovery.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
|
||||
/**
|
||||
* Basic helper class to describe an extension, with some characteristics and a list of modules
|
||||
*/
|
||||
class iTopExtension
|
||||
{
|
||||
public const SOURCE_WIZARD = 'datamodels';
|
||||
public const SOURCE_MANUAL = 'extensions';
|
||||
public const SOURCE_REMOTE = 'data';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sCode;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sVersion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sInstalledVersion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sLabel;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sDescription;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sSource;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bMandatory;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sMoreInfoUrl;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bMarkedAsChosen;
|
||||
/**
|
||||
* If null, check if at least one module cannot be uninstalled
|
||||
* @var bool|null
|
||||
*/
|
||||
public ?bool $bCanBeUninstalled = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bVisible;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModules;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModuleVersion;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $aModuleInfo;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sSourceDir;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $aMissingDependencies;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bInstalled = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bRemovedFromDisk = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->sCode = '';
|
||||
$this->sLabel = '';
|
||||
$this->sDescription = '';
|
||||
$this->sSource = self::SOURCE_WIZARD;
|
||||
$this->bMandatory = false;
|
||||
$this->sMoreInfoUrl = '';
|
||||
$this->bMarkedAsChosen = false;
|
||||
$this->sVersion = ITOP_VERSION;
|
||||
$this->sInstalledVersion = '';
|
||||
$this->aModules = [];
|
||||
$this->aModuleVersion = [];
|
||||
$this->aModuleInfo = [];
|
||||
$this->sSourceDir = '';
|
||||
$this->bVisible = true;
|
||||
$this->aMissingDependencies = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeUninstalled(): bool
|
||||
{
|
||||
if (!is_null($this->bCanBeUninstalled)) {
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
|
||||
if ($aModuleInfo['uninstallable'] !== 'yes') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1801,7 +1801,7 @@ EOF
|
||||
*/
|
||||
public function FindModules()
|
||||
{
|
||||
$aAvailableModules = ModuleDiscovery::GetAvailableModules($this->aRootDirs);
|
||||
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($this->aRootDirs);
|
||||
$aResult = [];
|
||||
foreach ($aAvailableModules as $sId => $aModule) {
|
||||
$oModule = new MFModule($sId, $aModule['root_dir'], $aModule['label'], isset($aModule['auto_select']));
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
require_once(APPROOT.'/setup/runtimeenv.class.inc.php');
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use ModuleFileReaderException;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
use RunTimeEnvironment;
|
||||
|
||||
/**
|
||||
@@ -61,19 +61,19 @@ class DependencyExpression
|
||||
}
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
public static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(static::$oPhpExpressionEvaluator)) {
|
||||
static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
if (!isset(self::$oPhpExpressionEvaluator)) {
|
||||
self::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
}
|
||||
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
return self::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return module names potentially required by current dependency
|
||||
*
|
||||
* @return array
|
||||
* @return array<string>
|
||||
*/
|
||||
public function GetRemainingModuleNamesToResolve(): array
|
||||
{
|
||||
|
||||
@@ -114,7 +114,7 @@ class Module
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array: list of unique module names
|
||||
* @return array<string> list of unique module names
|
||||
*/
|
||||
public function GetUnresolvedDependencyModuleNames(): array
|
||||
{
|
||||
|
||||
@@ -19,16 +19,16 @@ class ModuleDependencySort
|
||||
|
||||
final public static function GetInstance(): ModuleDependencySort
|
||||
{
|
||||
if (!isset(static::$oInstance)) {
|
||||
static::$oInstance = new static();
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModuleDependencySort();
|
||||
}
|
||||
|
||||
return static::$oInstance;
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleDependencySort $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +168,7 @@ class ModuleDependencySort
|
||||
foreach ($aCountDepsByModuleId as $sModuleId => $iInDegreeCounter) {
|
||||
$oModule = $aUnresolvedDependencyModules[$sModuleId];
|
||||
|
||||
if ($bOneLoopAtLeast && $iInDegreeCounter > 0) {
|
||||
if ($bOneLoopAtLeast && ($iInDegreeCounter > 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');
|
||||
require_once(__DIR__.'/moduledependency/moduledependencysort.class.inc.php');
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
require_once(__DIR__.'/itopextension.class.inc.php');
|
||||
|
||||
class MissingDependencyException extends CoreException
|
||||
{
|
||||
@@ -95,6 +95,9 @@ class ModuleDiscovery
|
||||
protected static $m_aModules = [];
|
||||
protected static $m_aModuleVersionByName = [];
|
||||
|
||||
/** @var array<\iTopExtension> $m_aRemovedExtensions */
|
||||
protected static array $m_aRemovedExtensions = [];
|
||||
|
||||
// All the entries below are list of file paths relative to the module directory
|
||||
protected static $m_aFilesList = ['datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample'];
|
||||
|
||||
@@ -120,10 +123,6 @@ class ModuleDiscovery
|
||||
if (is_null($aArgs) || ! is_array($aArgs)) {
|
||||
throw new ModuleFileReaderException("Error parsing module file args", 0, null, $sFilePath);
|
||||
}
|
||||
if (!array_key_exists('itop_version', $aArgs)) {
|
||||
// Assume 1.0.2
|
||||
$aArgs['itop_version'] = '1.0.2';
|
||||
}
|
||||
foreach (array_keys(self::$m_aModuleArgs) as $sArgName) {
|
||||
if (!array_key_exists($sArgName, $aArgs)) {
|
||||
throw new Exception("Module '$sId': missing argument '$sArgName'");
|
||||
@@ -135,6 +134,10 @@ class ModuleDiscovery
|
||||
|
||||
list($sModuleName, $sModuleVersion) = static::GetModuleName($sId);
|
||||
|
||||
if (self::IsModuleInExtensionList(self::$m_aRemovedExtensions, $sModuleName, $sModuleVersion, $aArgs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, self::$m_aModuleVersionByName)) {
|
||||
if (version_compare($sModuleVersion, self::$m_aModuleVersionByName[$sModuleName]['version'], '>')) {
|
||||
// Newer version, let's upgrade
|
||||
@@ -193,21 +196,6 @@ class ModuleDiscovery
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of "discovered" modules, ordered based on their (inter) dependencies
|
||||
*
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
protected static function GetModules($bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
// Order the modules to take into account their inter-dependencies
|
||||
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrange an list of modules, based on their (inter) dependencies
|
||||
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
|
||||
@@ -218,15 +206,20 @@ class ModuleDiscovery
|
||||
*/
|
||||
public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
if (is_null($aModulesToLoad)) {
|
||||
if (is_null($aModulesToLoad) && count(self::$m_aRemovedExtensions) === 0) {
|
||||
$aFilteredModules = $aModules;
|
||||
} else {
|
||||
$aFilteredModules = [];
|
||||
foreach ($aModules as $sModuleId => $aModule) {
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
if (in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aFilteredModules[$sModuleId] = $aModule;
|
||||
|
||||
if (self::IsModuleInExtensionList(self::$m_aRemovedExtensions, $sModuleName, $oModule->GetVersion(), $aModuleInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aFilteredModules[$sModuleId] = $aModuleInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,27 +227,19 @@ class ModuleDiscovery
|
||||
return ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aFilteredModules, $bAbortOnMissingDependency);
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
/**
|
||||
* @param array<\iTopExtension> $aRemovedExtension
|
||||
* @return void
|
||||
*/
|
||||
public static function DeclareRemovedExtensions(array $aRemovedExtension): void
|
||||
{
|
||||
if (!isset(static::$oPhpExpressionEvaluator)) {
|
||||
static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
if (self::$m_aRemovedExtensions != $aRemovedExtension) {
|
||||
self::ResetCache();
|
||||
}
|
||||
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
self::$m_aRemovedExtensions = $aRemovedExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
|
||||
* of the possible iTop modules to install
|
||||
*
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
private static function Init($aSearchDirs): void
|
||||
{
|
||||
if (self::$m_aSearchDirs != $aSearchDirs) {
|
||||
self::ResetCache();
|
||||
@@ -273,13 +258,60 @@ class ModuleDiscovery
|
||||
clearstatcache();
|
||||
self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir));
|
||||
}
|
||||
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
|
||||
} else {
|
||||
// Reuse the previous results
|
||||
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all modules found on disk ordered by dependencies. Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
self::Init($aSearchDirs);
|
||||
|
||||
return self::OrderModulesByDependencies(self::$m_aModules, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use \ModuleDiscovery::GetModulesOrderedByDependencies instead
|
||||
*/
|
||||
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
return ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all modules found on disk (without any dependency consideration). Skipping modules coming from extensions declared as removed (@see ModuleDiscovery::DeclareRemovedExtensions)
|
||||
*
|
||||
* @param $aSearchDirs array of directories to search (absolute paths)
|
||||
*
|
||||
* @return array A big array moduleID => ModuleData
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetAllModules($aSearchDirs)
|
||||
{
|
||||
self::Init($aSearchDirs);
|
||||
|
||||
$aNonRemovedModules = [];
|
||||
foreach (self::$m_aModules as $sModuleId => $aModuleInfo) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
|
||||
if (self::IsModuleInExtensionList(self::$m_aRemovedExtensions, $sModuleName, $oModule->GetVersion(), $aModuleInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aNonRemovedModules[$sModuleId] = $aModuleInfo;
|
||||
}
|
||||
|
||||
return $aNonRemovedModules;
|
||||
}
|
||||
|
||||
public static function ResetCache()
|
||||
{
|
||||
self::$m_aSearchDirs = null;
|
||||
@@ -289,10 +321,12 @@ class ModuleDiscovery
|
||||
|
||||
/**
|
||||
* Helper function to interpret the name of a module
|
||||
*
|
||||
* @param $sModuleId string Identifier of the module, in the form 'name/version'
|
||||
* @return array(name, version)
|
||||
*
|
||||
* @return array of 2 elements (name, version)
|
||||
*/
|
||||
public static function GetModuleName($sModuleId)
|
||||
public static function GetModuleName($sModuleId): array
|
||||
{
|
||||
$aMatches = [];
|
||||
if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches)) {
|
||||
@@ -346,6 +380,59 @@ class ModuleDiscovery
|
||||
throw new Exception("Data directory (".$sDirectory.") not found or not readable.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<\iTopExtension> $aExtensions
|
||||
* @param string $sModuleName
|
||||
* @param string $sModuleVersion
|
||||
* @param array $aModuleInfo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function IsModuleInExtensionList(array $aExtensions, string $sModuleName, string $sModuleVersion, array $aModuleInfo): bool
|
||||
{
|
||||
if (count($aExtensions) === 0) {
|
||||
return false;
|
||||
}
|
||||
$aNonMatchingPaths = [];
|
||||
$sModuleFilePath = $aModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
|
||||
/** @var \iTopExtension $oExtension */
|
||||
foreach ($aExtensions as $oExtension) {
|
||||
$sCurrentVersion = $oExtension->aModuleVersion[$sModuleName] ?? null;
|
||||
if (is_null($sCurrentVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sModuleVersion !== $sCurrentVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aCurrentModuleInfo = $oExtension->aModuleInfo[$sModuleName] ?? null;
|
||||
if (is_null($aCurrentModuleInfo)) {
|
||||
SetupLog::Warning("Missing $sModuleName in ".$oExtension->sLabel.". it should not happen");
|
||||
continue;
|
||||
}
|
||||
|
||||
// use case: same module coming from 2 different extensions
|
||||
// we remove only the one coming from removed extensions
|
||||
$sCurrentModuleFilePath = $aCurrentModuleInfo[ModuleFileReader::MODULE_FILE_PATH];
|
||||
if (realpath($sModuleFilePath) !== realpath($sCurrentModuleFilePath)) {
|
||||
$aNonMatchingPaths[] = $sCurrentModuleFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupLog::Info("Module considered as removed", null, ['extension_code' => $oExtension->sCode, 'module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sCurrentModuleFilePath]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (count($aNonMatchingPaths) > 0) {
|
||||
//add log for support
|
||||
SetupLog::Debug("Module kept as it came from non removed extensions", null, ['module_name' => $sModuleName, 'module_version' => $sModuleVersion, ModuleFileReader::MODULE_FILE_PATH => $sModuleFilePath, 'non_matching_paths' => $aNonMatchingPaths]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of class
|
||||
|
||||
/** Alias for backward compatibility with old module files in which
|
||||
|
||||
@@ -7,15 +7,15 @@ use CoreException;
|
||||
use Exception;
|
||||
use ParseError;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\ElseIf_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Stmt\ElseIf_;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Arg;
|
||||
|
||||
require_once __DIR__.'/ModuleFileReaderException.php';
|
||||
require_once APPROOT.'sources/PhpParser/Evaluation/PhpExpressionEvaluator.php';
|
||||
@@ -36,6 +36,7 @@ class ModuleFileReader
|
||||
public const MODULE_INFO_PATH = 0;
|
||||
public const MODULE_INFO_ID = 1;
|
||||
public const MODULE_INFO_CONFIG = 2;
|
||||
public const MODULE_FILE_PATH = "module_file_path";
|
||||
|
||||
public const STATIC_CALLWHITELIST = [
|
||||
"utils::GetItopVersionWikiSyntax",
|
||||
@@ -48,21 +49,23 @@ class ModuleFileReader
|
||||
|
||||
final public static function GetInstance(): ModuleFileReader
|
||||
{
|
||||
if (!isset(static::$oInstance)) {
|
||||
static::$oInstance = new static();
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModuleFileReader();
|
||||
}
|
||||
|
||||
return static::$oInstance;
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleFileReader $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the information from a module file (module.xxx.php)
|
||||
* @param string $sModuleFile
|
||||
*
|
||||
* @param string $sModuleFilePath
|
||||
*
|
||||
* @return array
|
||||
* @throws ModuleFileReaderException
|
||||
*/
|
||||
@@ -108,7 +111,9 @@ class ModuleFileReader
|
||||
* Read the information from a module file (module.xxx.php)
|
||||
* Warning: this method is using eval() function to load the ModuleInstallerAPI classes.
|
||||
* Current method is never called at design/runtime. It is acceptable to use it during setup only.
|
||||
* @param string $sModuleFile
|
||||
*
|
||||
* @param string $sModuleFilePath
|
||||
*
|
||||
* @return array
|
||||
* @throws ModuleFileReaderException
|
||||
*/
|
||||
@@ -164,7 +169,7 @@ class ModuleFileReader
|
||||
private function CompleteModuleInfoWithFilePath(array &$aModuleInfo)
|
||||
{
|
||||
if (count($aModuleInfo) == 3) {
|
||||
$aModuleInfo[static::MODULE_INFO_CONFIG]['module_file_path'] = $aModuleInfo[static::MODULE_INFO_PATH];
|
||||
$aModuleInfo[static::MODULE_INFO_CONFIG][self::MODULE_FILE_PATH] = $aModuleInfo[static::MODULE_INFO_PATH];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,15 +180,21 @@ class ModuleFileReader
|
||||
}
|
||||
|
||||
$sModuleInstallerClass = $aModuleInfo['installer'];
|
||||
if (strlen($sModuleInstallerClass) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!class_exists($sModuleInstallerClass)) {
|
||||
$sModuleFilePath = $aModuleInfo['module_file_path'];
|
||||
$sModuleFilePath = $aModuleInfo[self::MODULE_FILE_PATH];
|
||||
$this->ReadModuleFileInformationUnsafe($sModuleFilePath);
|
||||
}
|
||||
|
||||
if (!class_exists($sModuleInstallerClass)) {
|
||||
\IssueLog::Error(__METHOD__, null, $aModuleInfo);
|
||||
throw new CoreException("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']);
|
||||
}
|
||||
if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) {
|
||||
\IssueLog::Error(__METHOD__, null, $aModuleInfo);
|
||||
throw new CoreException("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
|
||||
}
|
||||
|
||||
@@ -192,7 +203,7 @@ class ModuleFileReader
|
||||
|
||||
/**
|
||||
* @param string $sModuleFilePath
|
||||
* @param \PhpParser\Node\Expr\Assign $oAssignation
|
||||
* @param \PhpParser\Node\Stmt\Expression $oExpression
|
||||
*
|
||||
* @return array|null
|
||||
* @throws ModuleFileReaderException
|
||||
|
||||
130
setup/moduleinstallation/AnalyzeInstallation.php
Normal file
130
setup/moduleinstallation/AnalyzeInstallation.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/ModuleInstallationRepository.php';
|
||||
|
||||
class AnalyzeInstallation
|
||||
{
|
||||
private static AnalyzeInstallation $oInstance;
|
||||
private ?array $aAvailableModules = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): AnalyzeInstallation
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new AnalyzeInstallation();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?AnalyzeInstallation $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the current installation and the possibilities
|
||||
*
|
||||
* @param null|Config $oConfig Defines the target environment (DB)
|
||||
* @param mixed $modulesPath Either a single string or an array of absolute paths
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
*
|
||||
* @return array Array with the following format:
|
||||
* array =>
|
||||
* 'iTop' => array(
|
||||
* 'installed_version' => ... (could be empty in case of a fresh install)
|
||||
* 'available_version => ...
|
||||
* )
|
||||
* <module_name> => array(
|
||||
* 'installed_version' => ...
|
||||
* 'available_version' => ...
|
||||
* 'install' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'uninstall' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
* )
|
||||
* 'label' => ...
|
||||
* 'dependencies' => array(<module1>, <module2>, ...)
|
||||
* 'visible' => true | false
|
||||
* )
|
||||
* )
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
||||
public function AnalyzeInstallation(?Config $oConfig, mixed $modulesPath, bool $bAbortOnMissingDependency = false, ?array $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'installed_version' => '',
|
||||
'available_version' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
if (! is_null($this->aAvailableModules)) {
|
||||
//test only
|
||||
$aAvailableModules = $this->aAvailableModules;
|
||||
} else {
|
||||
$aAvailableModules = ModuleDiscovery::GetModulesOrderedByDependencies($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
foreach ($aAvailableModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
$aModuleInfo['installed_version'] = '';
|
||||
$aModuleInfo['available_version'] = $sModuleVersion;
|
||||
|
||||
if ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
$aCurrentlyInstalledModules = ModuleInstallationRepository::GetInstance()->ReadComputeInstalledModules($oConfig);
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
foreach ($aCurrentlyInstalledModules as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
|
||||
$aRes[$sModuleName]['installed_version'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['mandatory']) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\DependencyExpression;
|
||||
|
||||
require_once __DIR__.'/ModuleInstallationException.php';
|
||||
require_once(APPROOT.'/setup/moduledependency/module.class.inc.php');
|
||||
|
||||
class InstallationChoicesToModuleConverter
|
||||
{
|
||||
private static ?InstallationChoicesToModuleConverter $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): InstallationChoicesToModuleConverter
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new InstallationChoicesToModuleConverter();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?InstallationChoicesToModuleConverter $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aInstallationChoices
|
||||
* @param array $aSearchDirs
|
||||
*
|
||||
* @return array
|
||||
* @throws \ModuleInstallationException
|
||||
*/
|
||||
public function GetModules(array $aInstallationChoices, array $aSearchDirs, ?string $sInstallationFilePath = null): array
|
||||
{
|
||||
$aPackageModules = ModuleDiscovery::GetAllModules($aSearchDirs);
|
||||
|
||||
$bInstallationFileProvided = ! is_null($sInstallationFilePath) && is_file($sInstallationFilePath);
|
||||
|
||||
if ($bInstallationFileProvided) {
|
||||
$oXMLParameters = new XMLParameters($sInstallationFilePath);
|
||||
$aSteps = $oXMLParameters->Get('steps', []);
|
||||
if (!is_array($aSteps)) {
|
||||
return [];
|
||||
}
|
||||
$aInstalledModuleNames = $this->FindInstalledPackageModules($aPackageModules, $aInstallationChoices, $aSteps);
|
||||
} else {
|
||||
$aInstalledModuleNames = $this->FindInstalledPackageModules($aPackageModules, $aInstallationChoices);
|
||||
}
|
||||
|
||||
$aInstalledModules = [];
|
||||
foreach (array_keys($aPackageModules) as $sModuleId) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (in_array($sModuleName, $aInstalledModuleNames)) {
|
||||
$aInstalledModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
|
||||
return $aInstalledModules;
|
||||
}
|
||||
|
||||
private function FindInstalledPackageModules(array $aPackageModules, array $aInstallationChoices, array $aInstallationDescription = null): array
|
||||
{
|
||||
$aInstalledModules = [];
|
||||
|
||||
$this->ProcessDefaultModules($aPackageModules, $aInstalledModules);
|
||||
|
||||
if (is_null($aInstallationDescription)) {
|
||||
//in legacy usecase: choices are flat modules list already
|
||||
foreach ($aInstallationChoices as $sModuleName) {
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
}
|
||||
} else {
|
||||
$this->GetModuleNamesFromInstallationChoices($aInstallationChoices, $aInstallationDescription, $aInstalledModules);
|
||||
}
|
||||
|
||||
$this->ProcessAutoSelectModules($aPackageModules, $aInstalledModules);
|
||||
|
||||
return array_keys($aInstalledModules);
|
||||
}
|
||||
|
||||
private function IsDefaultModule(string $sModuleId, array $aModule): bool
|
||||
{
|
||||
if (($sModuleId === ROOT_MODULE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($aModule['auto_select'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($aModule['category'] === 'authentication') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !$aModule['visible'];
|
||||
}
|
||||
|
||||
private function ProcessDefaultModules(array &$aPackageModules, array &$aInstalledModules): void
|
||||
{
|
||||
foreach ($aPackageModules as $sModuleId => $aModule) {
|
||||
if ($this->IsDefaultModule($sModuleId, $aModule)) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
unset($aPackageModules[$sModuleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function IsAutoSelectedModule(array $aInstalledModules, string $sModuleId, array $aModule): bool
|
||||
{
|
||||
if (($sModuleId === ROOT_MODULE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($aModule['auto_select'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupInfo::SetSelectedModules($aInstalledModules);
|
||||
return DependencyExpression::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aModule['auto_select']);
|
||||
} catch (Exception $e) {
|
||||
IssueLog::Error('Error evaluating module auto-select', null, [
|
||||
'module' => $sModuleId,
|
||||
'error' => $e->getMessage(),
|
||||
'evaluated code' => $aModule['auto_select'],
|
||||
'stacktrace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function ProcessAutoSelectModules(array $aPackageModules, array &$aInstalledModules): void
|
||||
{
|
||||
foreach ($aPackageModules as $sModuleId => $aModule) {
|
||||
if ($this->IsAutoSelectedModule($aInstalledModules, $sModuleId, $aModule)) {
|
||||
list($sModuleName) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$aInstalledModules[$sModuleName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function GetModuleNamesFromInstallationChoices(array $aInstallationChoices, array $aInstallationDescription, array &$aModuleNames): void
|
||||
{
|
||||
foreach ($aInstallationDescription as $aStepInfo) {
|
||||
$aOptions = $aStepInfo['options'] ?? null;
|
||||
if (is_array($aOptions)) {
|
||||
foreach ($aOptions as $aChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aModuleNames);
|
||||
}
|
||||
}
|
||||
$aOptions = $aStepInfo['alternatives'] ?? null;
|
||||
if (is_array($aOptions)) {
|
||||
foreach ($aOptions as $aChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aChoiceInfo, $aModuleNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ProcessSelectedChoice(array $aInstallationChoices, array $aChoiceInfo, array &$aInstalledModules)
|
||||
{
|
||||
if (!is_array($aChoiceInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sMandatory = $aChoiceInfo['mandatory'] ?? 'false';
|
||||
|
||||
$aCurrentModules = $aChoiceInfo['modules'] ?? [];
|
||||
$sExtensionCode = $aChoiceInfo['extension_code'];
|
||||
|
||||
$bSelected = ($sMandatory === 'true') || in_array($sExtensionCode, $aInstallationChoices);
|
||||
|
||||
if (!$bSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($aCurrentModules as $sModuleId) {
|
||||
$aInstalledModules[$sModuleId] = true;
|
||||
}
|
||||
|
||||
$aAlternatives = $aChoiceInfo['alternatives'] ?? null;
|
||||
if (is_array($aAlternatives)) {
|
||||
foreach ($aAlternatives as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
|
||||
$aSubOptionsChoiceInfo = $aChoiceInfo['sub_options'] ?? null;
|
||||
if (is_array($aSubOptionsChoiceInfo)) {
|
||||
$aSubOptions = $aSubOptionsChoiceInfo['options'] ?? null;
|
||||
if (is_array($aSubOptions)) {
|
||||
foreach ($aSubOptions as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
$aSubAlternatives = $aSubOptionsChoiceInfo['alternatives'] ?? null;
|
||||
if (is_array($aSubAlternatives)) {
|
||||
foreach ($aSubAlternatives as $aSubChoiceInfo) {
|
||||
$this->ProcessSelectedChoice($aInstallationChoices, $aSubChoiceInfo, $aInstalledModules);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
setup/moduleinstallation/ModuleInstallationException.php
Normal file
5
setup/moduleinstallation/ModuleInstallationException.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class ModuleInstallationException extends Exception
|
||||
{
|
||||
}
|
||||
238
setup/moduleinstallation/ModuleInstallationRepository.php
Normal file
238
setup/moduleinstallation/ModuleInstallationRepository.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
class ModuleInstallationRepository
|
||||
{
|
||||
private static ModuleInstallationRepository $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModuleInstallationRepository
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new ModuleInstallationRepository();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleInstallationRepository $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
private ?array $aSelectInstall = null;
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
*/
|
||||
public function ReadComputeInstalledModules(?Config $oConfig): array
|
||||
{
|
||||
$aSelectInstall = [];
|
||||
try {
|
||||
$aSelectInstall = $this->ReadFromDB($oConfig);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
return $this->ComputeInstalledModules($aSelectInstall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config|null $oConfig
|
||||
* @return array
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
public function ReadFromDB(?Config $oConfig): array
|
||||
{
|
||||
if (is_null($oConfig)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! is_null($this->aSelectInstall)) {
|
||||
//test only
|
||||
return $this->aSelectInstall;
|
||||
}
|
||||
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
//read db module installations
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$iRootId = CMDBSource::QueryToScalar("SELECT max(parent_id) FROM $tableWithPrefix");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$sSQL = <<<SQL
|
||||
SELECT * FROM $tableWithPrefix
|
||||
WHERE
|
||||
parent_id='$iRootId'
|
||||
OR id='$iRootId'
|
||||
SQL;
|
||||
return CMDBSource::QueryToArray($sSQL);
|
||||
}
|
||||
|
||||
private function GetTableWithPrefix(Config $oConfig)
|
||||
{
|
||||
$sPrefix = $oConfig->Get('db_subname');
|
||||
if (utils::IsNullOrEmptyString($sPrefix)) {
|
||||
return "priv_module_install";
|
||||
}
|
||||
|
||||
return "{$sPrefix}priv_module_install";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Config $oConfig
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$tableWithPrefix = $this->GetTableWithPrefix($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM $tableWithPrefix";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
SetupLog::Error(
|
||||
'Can not connect to the database',
|
||||
null,
|
||||
[
|
||||
'host' => $oConfig->Get('db_host'),
|
||||
'user' => $oConfig->Get('db_user'),
|
||||
'pwd:' => $oConfig->Get('db_pwd'),
|
||||
'db name' => $oConfig->Get('db_name'),
|
||||
'msg' => $e->getMessage(),
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$aResult = [];
|
||||
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE) {
|
||||
$aResult['datamodel_version'] = $sModuleVersion;
|
||||
$aComments = json_decode($aInstall['comment'], true);
|
||||
if (is_array($aComments)) {
|
||||
$aResult = array_merge($aResult, $aComments);
|
||||
}
|
||||
} else {
|
||||
$aResult['product_name'] = $aInstall['name'];
|
||||
$aResult['product_version'] = $sModuleVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('datamodel_version', $aResult)) {
|
||||
// Versions prior to 2.0 did not record the version of the datamodel
|
||||
// so assume that the datamodel version is equal to the application version
|
||||
$aResult['datamodel_version'] = $aResult['product_version'];
|
||||
}
|
||||
|
||||
SetupLog::Info(__METHOD__, null, ["product_name" => $aResult['product_name'], "product_version" => $aResult['product_version']]);
|
||||
|
||||
return count($aResult) == 0 ? false : $aResult;
|
||||
}
|
||||
|
||||
private function ComputeInstalledModules(array $aSelectInstall): array
|
||||
{
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
|
||||
//module installation datetime is mostly the same for all modules
|
||||
//unless there was issue recording things in DB
|
||||
$sFirstDatetime = null;
|
||||
$iFirstTime = -1;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$sDatetime = $aInstall['installed'];
|
||||
|
||||
if (is_null($sFirstDatetime)) {
|
||||
$sFirstDatetime = $sDatetime;
|
||||
$iFirstTime = strtotime($sDatetime);
|
||||
$iInstalled = $iFirstTime;
|
||||
} elseif ($sDatetime === $sFirstDatetime) {
|
||||
$iInstalled = $iFirstTime;
|
||||
} else {
|
||||
$sDatetime = $aInstall['installed'];
|
||||
$iInstalled = strtotime($sDatetime);
|
||||
}
|
||||
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aInstallByModule[ROOT_MODULE] = [
|
||||
'installed_version' => $sModuleVersion,
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
} else {
|
||||
$aInstallByModule[$sModuleName] = [
|
||||
'installed' => $iInstalled,
|
||||
'version' => $sModuleVersion,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aInstallByModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous module installation. offset is applied on parent_id.
|
||||
* @param $iOffset: by default (offset=0) returns current installation
|
||||
* @return array
|
||||
*/
|
||||
public static function GetPreviousModuleInstallationsByOffset(int $iOffset = 0): array
|
||||
{
|
||||
$oFilter = DBObjectSearch::FromOQL('SELECT ModuleInstallation AS mi WHERE mi.parent_id=0 AND mi.name!="datamodel"');
|
||||
$oSet = new DBObjectSet($oFilter, ['installed' => false]); // Most recent first
|
||||
$oSet->SetLimit($iOffset + 1);
|
||||
|
||||
$iParentId = 0;
|
||||
while (!is_null($oModuleInstallation = $oSet->Fetch())) {
|
||||
/** @var \DBObject $oModuleInstallation */
|
||||
if ($iOffset == 0) {
|
||||
$iParentId = $oModuleInstallation->Get('id');
|
||||
break;
|
||||
}
|
||||
$iOffset--;
|
||||
}
|
||||
|
||||
if ($iParentId === 0) {
|
||||
IssueLog::Error("no ITOP_APPLICATION ModuleInstallation found", null, ['offset' => $iOffset]);
|
||||
throw new \Exception("no ITOP_APPLICATION ModuleInstallation found");
|
||||
}
|
||||
|
||||
$oFilter = DBObjectSearch::FromOQL("SELECT ModuleInstallation AS mi WHERE mi.id=$iParentId OR mi.parent_id=$iParentId");
|
||||
$oSet = new DBObjectSet($oFilter); // Most recent first
|
||||
$aRawValues = $oSet->ToArrayOfValues();
|
||||
$aValues = [];
|
||||
foreach ($aRawValues as $aRawValue) {
|
||||
$aValue = [];
|
||||
foreach ($aRawValue as $sAliasAttCode => $sValue) {
|
||||
// remove 'mi.' from AttCode
|
||||
$sAttCode = substr($sAliasAttCode, 3);
|
||||
$aValue[$sAttCode] = $sValue;
|
||||
}
|
||||
|
||||
$aValues[] = $aValue;
|
||||
}
|
||||
return $aValues;
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@ abstract class ModuleInstallerAPI
|
||||
if (in_array($sTo, $aNewValues)) {
|
||||
$sEnumCol = $oAttDef->Get("sql");
|
||||
$aFields = CMDBSource::QueryToArray("SHOW COLUMNS FROM `$sTableName` WHERE Field = '$sEnumCol'");
|
||||
$aCurrentValues = [];
|
||||
if (isset($aFields[0]['Type'])) {
|
||||
$sColType = $aFields[0]['Type'];
|
||||
// Note: the parsing should rely on str_getcsv (requires PHP 5.3) to cope with escaped string
|
||||
|
||||
@@ -7,6 +7,7 @@ class InvalidParameterException extends Exception
|
||||
abstract class Parameters
|
||||
{
|
||||
public $aData = null;
|
||||
private ?array $aParamValues = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -26,24 +27,26 @@ abstract class Parameters
|
||||
*/
|
||||
public function GetParamForConfigArray()
|
||||
{
|
||||
$aDBParams = $this->Get('database');
|
||||
$aParamValues = [
|
||||
'mode' => $this->Get('mode'),
|
||||
'db_server' => $aDBParams['server'],
|
||||
'db_user' => $aDBParams['user'],
|
||||
'db_pwd' => $aDBParams['pwd'],
|
||||
'db_name' => $aDBParams['name'],
|
||||
'new_db_name' => $aDBParams['name'],
|
||||
'db_prefix' => $aDBParams['prefix'],
|
||||
'db_tls_enabled' => $aDBParams['db_tls_enabled'],
|
||||
'db_tls_ca' => $aDBParams['db_tls_ca'],
|
||||
'application_path' => $this->Get('url', ''),
|
||||
'language' => $this->Get('language', ''),
|
||||
'graphviz_path' => $this->Get('graphviz_path', ''),
|
||||
'source_dir' => $this->Get('source_dir', ''),
|
||||
];
|
||||
if (is_null($this->aParamValues)) {
|
||||
$aDBParams = $this->Get('database');
|
||||
$this->aParamValues = [
|
||||
'mode' => $this->Get('mode'),
|
||||
'db_server' => $aDBParams['server'],
|
||||
'db_user' => $aDBParams['user'],
|
||||
'db_pwd' => $aDBParams['pwd'],
|
||||
'db_name' => $aDBParams['name'],
|
||||
'new_db_name' => $aDBParams['name'],
|
||||
'db_prefix' => $aDBParams['prefix'],
|
||||
'db_tls_enabled' => $aDBParams['db_tls_enabled'],
|
||||
'db_tls_ca' => $aDBParams['db_tls_ca'],
|
||||
'application_path' => $this->Get('url', ''),
|
||||
'language' => $this->Get('language', ''),
|
||||
'graphviz_path' => $this->Get('graphviz_path', ''),
|
||||
'source_dir' => $this->Get('source_dir', ''),
|
||||
];
|
||||
}
|
||||
|
||||
return $aParamValues;
|
||||
return $this->aParamValues;
|
||||
}
|
||||
|
||||
public function Set($sCode, $value)
|
||||
@@ -104,7 +107,9 @@ class PHPParameters extends Parameters
|
||||
{
|
||||
if ($this->aData == null) {
|
||||
require_once($sParametersFile);
|
||||
$this->aData = $ITOP_PARAMS; // Defined in the file loaded just above
|
||||
if (isset($ITOP_PARAMS)) {
|
||||
$this->aData = $ITOP_PARAMS; // Defined in the file loaded just above
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ require_once APPROOT."setup/modulediscovery.class.inc.php";
|
||||
require_once APPROOT.'setup/modelfactory.class.inc.php';
|
||||
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';
|
||||
|
||||
define('MODULE_ACTION_OPTIONAL', 1);
|
||||
define('MODULE_ACTION_MANDATORY', 2);
|
||||
@@ -61,7 +63,23 @@ class RunTimeEnvironment
|
||||
* Extensions map of the source environment
|
||||
* @var iTopExtensionsMap
|
||||
*/
|
||||
protected $oExtensionsMap;
|
||||
protected ?iTopExtensionsMap $oExtensionsMap;
|
||||
|
||||
protected function GetExtensionMap(): ?iTopExtensionsMap
|
||||
{
|
||||
return $this->oExtensionsMap;
|
||||
}
|
||||
|
||||
public function InitExtensionMap($aExtraDirs, $oSourceConfig)
|
||||
{
|
||||
// Actually read the modules available for the target environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen alll the "remote" modules present in the
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
@@ -109,11 +127,10 @@ class RunTimeEnvironment
|
||||
* 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
|
||||
* @return none
|
||||
*/
|
||||
public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false)
|
||||
public function InitDataModel($oConfig, $bModelOnly = true, $bUseCache = false): void
|
||||
{
|
||||
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
|
||||
require_once APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php';
|
||||
|
||||
$sConfigFile = $oConfig->GetLoadedFile();
|
||||
if (strlen($sConfigFile) > 0) {
|
||||
@@ -145,12 +162,12 @@ class RunTimeEnvironment
|
||||
* @return array Array with the following format:
|
||||
* array =>
|
||||
* 'iTop' => array(
|
||||
* 'version_db' => ... (could be empty in case of a fresh install)
|
||||
* 'version_code => ...
|
||||
* 'installed_version' => ... (could be empty in case of a fresh install)
|
||||
* 'available_version => ...
|
||||
* )
|
||||
* <module_name> => array(
|
||||
* 'version_db' => ...
|
||||
* 'version_code' => ...
|
||||
* 'installed_version' => ...
|
||||
* 'available_version' => ...
|
||||
* 'install' => array(
|
||||
* 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY
|
||||
* 'message' => ...
|
||||
@@ -168,137 +185,7 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
'version_db' => '',
|
||||
'name_db' => '',
|
||||
'version_code' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleName == '') {
|
||||
throw new Exception("Missing name for the module: '$sModuleId'");
|
||||
}
|
||||
if ($sModuleVersion == '') {
|
||||
// The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
|
||||
//throw new Exception("Missing version for the module: '$sModuleId'");
|
||||
$sModuleVersion = '1.0.0';
|
||||
}
|
||||
|
||||
$sModuleAppVersion = $aModuleInfo['itop_version'];
|
||||
$aModuleInfo['version_db'] = '';
|
||||
$aModuleInfo['version_code'] = $sModuleVersion;
|
||||
|
||||
if (!in_array($sModuleAppVersion, ['1.0.0', '1.0.1', '1.0.2'])) {
|
||||
// This module is NOT compatible with the current version
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is not compatible with the current version of the application',
|
||||
];
|
||||
} elseif ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
$aSelectInstall = [];
|
||||
if (! is_null($oConfig)) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
}
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
// Build the list of installed module (get the latest installation)
|
||||
//
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$iRootId = 0;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) {
|
||||
// Root module, what is its ID ?
|
||||
$iId = (int) $aInstall['id'];
|
||||
if ($iId > $iRootId) {
|
||||
$iRootId = $iId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
//$aInstall['comment']; // unsused
|
||||
$iInstalled = strtotime($aInstall['installed']);
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$sModuleName = ROOT_MODULE;
|
||||
} elseif ($aInstall['parent_id'] != $iRootId) {
|
||||
// Skip all modules belonging to previous installations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $aInstallByModule)) {
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
|
||||
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
|
||||
}
|
||||
|
||||
$aInstallByModule[$sModuleName]['installed'] = $iInstalled;
|
||||
$aInstallByModule[$sModuleName]['version'] = $sModuleVersion;
|
||||
}
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
//
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
continue;
|
||||
} // Skip the main module
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
return AnalyzeInstallation::GetInstance()->AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,6 +226,25 @@ class RunTimeEnvironment
|
||||
return ($oExtension->sSource == iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
|
||||
public function GetExtraDirsToCompile(string $sSourceDir): array
|
||||
{
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sTargetEnv.'-modules/';
|
||||
if (is_dir($sExtraDir)) {
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
return $aDirsToCompile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the installed modules (only the installed ones)
|
||||
*/
|
||||
@@ -360,30 +266,29 @@ class RunTimeEnvironment
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
|
||||
$aRet = [];
|
||||
|
||||
// Determine the installed modules and extensions
|
||||
//
|
||||
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
|
||||
$oSourceEnv = new RunTimeEnvironment($sSourceEnv);
|
||||
$aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile);
|
||||
|
||||
// Actually read the modules available for the target 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
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
|
||||
$this->GetExtensionMap()->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($this->IsExtensionSelected($oExtension)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Do load the required modules
|
||||
//
|
||||
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
|
||||
|
||||
$aRet = [];
|
||||
$aRet[$oDictModule->GetName()] = $oDictModule;
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToCompile);
|
||||
@@ -401,10 +306,9 @@ class RunTimeEnvironment
|
||||
$aModules = $oFactory->FindModules();
|
||||
foreach ($aModules as $oModule) {
|
||||
$sModule = $oModule->GetName();
|
||||
$sModuleRootDir = $oModule->GetRootDir();
|
||||
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
$bIsExtra = $this->GetExtensionMap()->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
if (array_key_exists($sModule, $aAvailableModules)) {
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
if (($aAvailableModules[$sModule]['installed_version'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
$aRet[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
}
|
||||
@@ -462,6 +366,7 @@ class RunTimeEnvironment
|
||||
//
|
||||
$oFactory = new ModelFactory($sSourceDirFull);
|
||||
$aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
$oModule = null;
|
||||
foreach ($aModulesToCompile as $oModule) {
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
// Just before loading the delta, let's save an image of the datamodel
|
||||
@@ -471,7 +376,7 @@ class RunTimeEnvironment
|
||||
$oFactory->LoadModule($oModule);
|
||||
}
|
||||
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
if (!is_null($oModule) && ($oModule instanceof MFDeltaModule)) {
|
||||
// A delta was loaded, let's save a second copy of the datamodel
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'-with-delta.xml');
|
||||
} else {
|
||||
@@ -648,7 +553,7 @@ class RunTimeEnvironment
|
||||
$oInstallRec->Set('comment', json_encode($aData));
|
||||
$oInstallRec->Set('parent_id', 0); // root module
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
|
||||
// Record main installation
|
||||
$oInstallRec = new ModuleInstallation();
|
||||
@@ -661,15 +566,19 @@ class RunTimeEnvironment
|
||||
|
||||
// Record installed modules and extensions
|
||||
//
|
||||
$aAvailableExtensions = [];
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir());
|
||||
foreach ($aSelectedModuleCodes as $sModuleId) {
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($sModuleId, $aAvailableModules)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aModuleData = $aAvailableModules[$sModuleId];
|
||||
$sName = $sModuleId;
|
||||
$sVersion = $aModuleData['version_code'];
|
||||
$sVersion = $aModuleData['available_version'];
|
||||
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
|
||||
$aComments = [];
|
||||
$aComments[] = $sShortComment;
|
||||
@@ -702,16 +611,17 @@ class RunTimeEnvironment
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
|
||||
if ($this->oExtensionsMap) {
|
||||
if ($this->GetExtensionMap()) {
|
||||
// Mark as chosen the selected extensions code passed to us
|
||||
|
||||
// Note: some other extensions may already be marked as chosen
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->oExtensionsMap->GetChoices() as $oExtension) {
|
||||
foreach ($this->GetExtensionMap()->GetChoices() as $oExtension) {
|
||||
$oInstallRec = new ExtensionInstallation();
|
||||
$oInstallRec->Set('code', $oExtension->sCode);
|
||||
$oInstallRec->Set('label', $oExtension->sLabel);
|
||||
@@ -739,9 +649,7 @@ class RunTimeEnvironment
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
$aSelectInstall = ModuleInstallationRepository::GetInstance()->ReadFromDB($oConfig);
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
|
||||
@@ -779,7 +687,8 @@ class RunTimeEnvironment
|
||||
$aResult['datamodel_version'] = $aResult['product_version'];
|
||||
}
|
||||
$this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']);
|
||||
return empty($aResult) ? false : $aResult;
|
||||
|
||||
return count($aResult) == 0 ? false : $aResult;
|
||||
}
|
||||
|
||||
public static function MakeDirSafe($sDir)
|
||||
@@ -967,16 +876,20 @@ class RunTimeEnvironment
|
||||
/**
|
||||
* Call the given handler method for all selected modules having an installation handler
|
||||
* @param array[] $aAvailableModules
|
||||
* @param string[] $aSelectedModules
|
||||
* @param string $sHandlerName
|
||||
* @param string[]|null $aSelectedModules
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName)
|
||||
public function CallInstallerHandlers($aAvailableModules, $sHandlerName, $aSelectedModules = null)
|
||||
{
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) {
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']];
|
||||
RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs);
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_null($aSelectedModules) || in_array($sModuleId, $aSelectedModules)) {
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['installed_version'], $aModule['available_version']];
|
||||
RunTimeEnvironment::CallInstallerHandler($aModule, $sHandlerName, $aArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +916,7 @@ class RunTimeEnvironment
|
||||
try {
|
||||
call_user_func_array($aCallSpec, $aArgs);
|
||||
} catch (Exception $e) {
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID] ?? "";
|
||||
$sErrorMessage = "Module $sModuleId : error when calling module installer class $sModuleInstallerClass for $sHandlerName handler";
|
||||
$aExceptionContextData = [
|
||||
'ModulelId' => $sModuleId,
|
||||
@@ -1019,10 +933,10 @@ class RunTimeEnvironment
|
||||
/**
|
||||
* 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 string[] $aSelectedModules List of selected modules
|
||||
* @param bool $bSampleData Wether or not to load sample data
|
||||
* @param null|string[] $aSelectedModules List of selected modules
|
||||
*/
|
||||
public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData)
|
||||
public function LoadData($aAvailableModules, $bSampleData, $aSelectedModules = null)
|
||||
{
|
||||
$oDataLoader = new XMLDataLoader();
|
||||
|
||||
@@ -1035,30 +949,33 @@ class RunTimeEnvironment
|
||||
$aFiles = [];
|
||||
$aPreviouslyLoadedFiles = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE)) {
|
||||
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
|
||||
// Load data only for selected AND newly installed modules
|
||||
if (in_array($sModuleId, $aSelectedModules)) {
|
||||
if ($aModule['version_db'] != '') {
|
||||
// Simulate the load of the previously loaded XML files to get the mapping of the keys
|
||||
if ($bSampleData) {
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
// Load only structural data
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
|
||||
// Load data only for selected AND newly installed modules
|
||||
if (is_null($aSelectedModules) || in_array($sModuleId, $aSelectedModules)) {
|
||||
if ($aModule['installed_version'] != '') {
|
||||
// Simulate the load of the previously loaded XML files to get the mapping of the keys
|
||||
if ($bSampleData) {
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
if ($bSampleData) {
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
// Load only structural data
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
// Load only structural data
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
} else {
|
||||
if ($bSampleData) {
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
// Load only structural data
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Simulate the load of the previously loaded files, in order to initialize
|
||||
@@ -1067,7 +984,7 @@ class RunTimeEnvironment
|
||||
foreach ($aPreviouslyLoadedFiles as $sFileRelativePath) {
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)");
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
if (!file_exists($sFileName)) {
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
@@ -1079,7 +996,7 @@ class RunTimeEnvironment
|
||||
foreach ($aFiles as $sFileRelativePath) {
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName");
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
if (!file_exists($sFileName)) {
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
|
||||
980
setup/sequencers/ApplicationInstallSequencer.php
Normal file
980
setup/sequencers/ApplicationInstallSequencer.php
Normal file
@@ -0,0 +1,980 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
|
||||
require_once(APPROOT.'setup/backup.class.inc.php');
|
||||
|
||||
require_once(APPROOT.'setup/sequencers/StepSequencer.php');
|
||||
require_once(APPROOT.'setup/SetupDBBackup.php');
|
||||
|
||||
/**
|
||||
* The base class for the installation process.
|
||||
* The installation process is split into a sequence of unitary steps
|
||||
* for performance reasons (i.e; timeout, memory usage) and also in order
|
||||
* to provide some feedback about the progress of the installation.
|
||||
*
|
||||
* This class can be used for a step by step interactive installation
|
||||
* while displaying a progress bar, or in an unattended manner
|
||||
* (for example from the command line), to run all the steps
|
||||
* in one go.
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class ApplicationInstallSequencer extends StepSequencer
|
||||
{
|
||||
protected Parameters $oParams;
|
||||
protected static bool $bMetaModelStarted = false;
|
||||
|
||||
protected Config $oConfig;
|
||||
|
||||
/**
|
||||
* @param \Parameters $oParams
|
||||
*
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function __construct(Parameters $oParams)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function DoLogParameters($sPrefix = 'install-', $sOperation = 'Installation')
|
||||
{
|
||||
// Log the parameters...
|
||||
$oDoc = new DOMDocument('1.0', 'UTF-8');
|
||||
$oDoc->preserveWhiteSpace = false;
|
||||
$oDoc->formatOutput = true;
|
||||
$this->oParams->ToXML($oDoc, null, 'installation');
|
||||
$sXML = $oDoc->saveXML();
|
||||
$sSafeXml = preg_replace("|<pwd>([^<]*)</pwd>|", "<pwd>**removed**</pwd>", $sXML);
|
||||
SetupLog::Info("======= ".$sOperation." starts =======\nParameters:\n$sSafeXml\n");
|
||||
|
||||
// Save the response file as a stand-alone file as well
|
||||
$sFileName = $sPrefix.date('Y-m-d');
|
||||
$index = 0;
|
||||
while (file_exists(APPROOT.'log/'.$sFileName.'.xml')) {
|
||||
$index++;
|
||||
$sFileName = $sPrefix.date('Y-m-d').'-'.$index;
|
||||
}
|
||||
file_put_contents(APPROOT.'log/'.$sFileName.'.xml', $sSafeXml);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
try {
|
||||
$fStart = microtime(true);
|
||||
SetupLog::Info("##### STEP {$sStep} start");
|
||||
$this->EnterReadOnlyMode();
|
||||
switch ($sStep) {
|
||||
case '':
|
||||
|
||||
$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;
|
||||
}
|
||||
break;
|
||||
|
||||
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);
|
||||
|
||||
$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).");
|
||||
}
|
||||
}
|
||||
|
||||
$this->DoCompile(
|
||||
$aRemovedExtensionCodes,
|
||||
$aSelectedModules,
|
||||
$sSourceDir,
|
||||
$sExtensionDir,
|
||||
$bUseSymbolicLinks
|
||||
);
|
||||
|
||||
$sNextStep = 'db-schema';
|
||||
$sNextStepLabel = 'Updating database schema';
|
||||
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => $sNextStep,
|
||||
'next-step-label' => $sNextStepLabel,
|
||||
'percentage-completed' => 40,
|
||||
];
|
||||
break;
|
||||
|
||||
case 'db-schema':
|
||||
$aSelectedModules = $this->oParams->Get('selected_modules', []);
|
||||
|
||||
$this->DoUpdateDBSchema(
|
||||
$aSelectedModules
|
||||
);
|
||||
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => 'after-db-create',
|
||||
'next-step-label' => 'Creating profiles',
|
||||
'percentage-completed' => 60,
|
||||
];
|
||||
break;
|
||||
|
||||
case 'after-db-create':
|
||||
$aAdminParams = $this->oParams->Get('admin_account');
|
||||
$aSelectedModules = $this->oParams->Get('selected_modules', []);
|
||||
|
||||
$this->AfterDBCreate(
|
||||
$aAdminParams,
|
||||
$aSelectedModules
|
||||
);
|
||||
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => 'load-data',
|
||||
'next-step-label' => 'Loading data',
|
||||
'percentage-completed' => 80,
|
||||
];
|
||||
break;
|
||||
|
||||
case 'load-data':
|
||||
$aSelectedModules = $this->oParams->Get('selected_modules', []);
|
||||
$bSampleData = ($this->oParams->Get('sample_data', 0) == 1);
|
||||
|
||||
$this->DoLoadFiles(
|
||||
$aSelectedModules,
|
||||
$bSampleData
|
||||
);
|
||||
|
||||
$aResult = [
|
||||
'status' => self::INFO,
|
||||
'message' => 'All data loaded',
|
||||
'next-step' => 'create-config',
|
||||
'next-step-label' => 'Creating the configuration File',
|
||||
'percentage-completed' => 99,
|
||||
];
|
||||
break;
|
||||
|
||||
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,
|
||||
$sDataModelVersion,
|
||||
$aSelectedModuleCodes,
|
||||
$aSelectedExtensionCodes,
|
||||
$sInstallComment
|
||||
);
|
||||
|
||||
$aResult = [
|
||||
'status' => self::INFO,
|
||||
'message' => 'Configuration file created',
|
||||
'next-step' => '',
|
||||
'next-step-label' => 'Completed',
|
||||
'percentage-completed' => 100,
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$aResult = [
|
||||
'status' => self::ERROR,
|
||||
'message' => '',
|
||||
'next-step' => '',
|
||||
'next-step-label' => "Unknown setup step '$sStep'.",
|
||||
'percentage-completed' => 100,
|
||||
];
|
||||
break;
|
||||
}
|
||||
$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");
|
||||
}
|
||||
|
||||
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') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetupUtils::IsInReadOnlyMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupUtils::EnterReadOnlyMode($this->GetConfig());
|
||||
}
|
||||
|
||||
protected function ExitReadOnlyMode()
|
||||
{
|
||||
if ($this->GetTargetEnv() != 'production') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetupUtils::IsInReadOnlyMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
}
|
||||
|
||||
protected function DoCopy($aCopies)
|
||||
{
|
||||
$aReports = [];
|
||||
foreach ($aCopies as $aCopy) {
|
||||
$sSource = $aCopy['source'];
|
||||
$sDestination = APPROOT.$aCopy['destination'];
|
||||
|
||||
SetupUtils::builddir($sDestination);
|
||||
SetupUtils::tidydir($sDestination);
|
||||
SetupUtils::copydir($sSource, $sDestination);
|
||||
$aReports[] = "'{$aCopy['source']}' to '{$aCopy['destination']}' (OK)";
|
||||
}
|
||||
if (count($aReports) > 0) {
|
||||
$sReport = "Copies: ".count($aReports).': '.implode('; ', $aReports);
|
||||
} else {
|
||||
$sReport = "No file copy";
|
||||
}
|
||||
return $sReport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aRemovedExtensionCodes
|
||||
* @param array $aSelectedModules
|
||||
* @param string $sSourceDir
|
||||
* @param string $sExtensionDir
|
||||
* @param boolean $bUseSymbolicLinks
|
||||
*
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 3.1.0 N°2013 added the aParamValues param
|
||||
*/
|
||||
protected function DoCompile($aRemovedExtensionCodes, $aSelectedModules, $sSourceDir, $sExtensionDir, $bUseSymbolicLinks = null)
|
||||
{
|
||||
/**
|
||||
* @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("Compiling data model.");
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
|
||||
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
||||
require_once(APPROOT.'setup/compiler.class.inc.php');
|
||||
|
||||
$aParamValues = $this->oParams->GetParamForConfigArray();
|
||||
$sEnvironment = $this->GetTargetEnv();
|
||||
$sTargetDir = $this->GetTargetDir();
|
||||
|
||||
if (empty($sSourceDir) || empty($sTargetDir)) {
|
||||
throw new Exception("missing parameter source_dir and/or target_dir");
|
||||
}
|
||||
|
||||
$sSourcePath = APPROOT.$sSourceDir;
|
||||
$aDirsToScan = [$sSourcePath];
|
||||
$sExtensionsPath = APPROOT.$sExtensionDir;
|
||||
if (is_dir($sExtensionsPath)) {
|
||||
// if the extensions dir exists, scan it for additional modules as well
|
||||
$aDirsToScan[] = $sExtensionsPath;
|
||||
}
|
||||
$sExtraPath = APPROOT.'/data/'.$sEnvironment.'-modules/';
|
||||
if (is_dir($sExtraPath)) {
|
||||
// if the extra dir exists, scan it for additional modules as well
|
||||
$aDirsToScan[] = $sExtraPath;
|
||||
}
|
||||
$sTargetPath = APPROOT.$sTargetDir;
|
||||
|
||||
if (!is_dir($sSourcePath)) {
|
||||
throw new Exception("Failed to find the source directory '$sSourcePath', please check the rights of the web server");
|
||||
}
|
||||
|
||||
$bIsAlreadyInMaintenanceMode = SetupUtils::IsInMaintenanceMode();
|
||||
|
||||
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
|
||||
$sConfigFilePath = utils::GetConfigFilePath($sEnvironment);
|
||||
if (is_file($sConfigFilePath)) {
|
||||
$oConfig = new Config($sConfigFilePath);
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
SetupUtils::EnterMaintenanceMode($oConfig);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!is_dir($sTargetPath)) {
|
||||
if (!mkdir($sTargetPath)) {
|
||||
throw new Exception("Failed to create directory '$sTargetPath', please check the rights of the web server");
|
||||
} else {
|
||||
// adjust the rights if and only if the directory was just created
|
||||
// owner:rwx user/group:rx
|
||||
chmod($sTargetPath, 0755);
|
||||
}
|
||||
} elseif (substr($sTargetPath, 0, strlen(APPROOT)) == APPROOT) {
|
||||
// If the directory is under the root folder - as expected - let's clean-it before compiling
|
||||
SetupUtils::tidydir($sTargetPath);
|
||||
}
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aDirsToScan);
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToScan);
|
||||
|
||||
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
|
||||
$oFactory->LoadModule($oDictModule);
|
||||
|
||||
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
|
||||
$oFactory->LoadModule($oCoreModule);
|
||||
}
|
||||
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
|
||||
$oFactory->LoadModule($oApplicationModule);
|
||||
}
|
||||
|
||||
$aModules = $oFactory->FindModules();
|
||||
|
||||
foreach ($aModules as $oModule) {
|
||||
$sModule = $oModule->GetName();
|
||||
if (in_array($sModule, $aSelectedModules)) {
|
||||
$oFactory->LoadModule($oModule);
|
||||
}
|
||||
}
|
||||
// Dump the "reference" model, just before loading any actual delta
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$sEnvironment.'.xml');
|
||||
|
||||
$sDeltaFile = utils::GetDataPath().$sEnvironment.'.delta.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
$oDelta = new MFDeltaModule($sDeltaFile);
|
||||
$oFactory->LoadModule($oDelta);
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$sEnvironment.'-with-delta.xml');
|
||||
}
|
||||
|
||||
$oMFCompiler = new MFCompiler($oFactory, $sEnvironment);
|
||||
$oMFCompiler->Compile($sTargetPath, null, $bUseSymbolicLinks);
|
||||
//$aCompilerLog = $oMFCompiler->GetLog();
|
||||
//SetupLog::Info(implode("\n", $aCompilerLog));
|
||||
SetupLog::Info("Data model successfully compiled to '$sTargetPath'.");
|
||||
|
||||
$sCacheDir = APPROOT.'/data/cache-'.$sEnvironment.'/';
|
||||
SetupUtils::builddir($sCacheDir);
|
||||
SetupUtils::tidydir($sCacheDir);
|
||||
} catch (Exception $e) {
|
||||
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
|
||||
SetupUtils::ExitMaintenanceMode();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Special case to patch a ugly patch in itop-config-mgmt
|
||||
$sFileToPatch = $sTargetPath.'/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php';
|
||||
if (file_exists($sFileToPatch)) {
|
||||
$sContent = file_get_contents($sFileToPatch);
|
||||
|
||||
$sContent = str_replace("require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", "//\n// The line below is no longer needed in iTop 2.0 -- patched by the setup program\n// require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", $sContent);
|
||||
|
||||
file_put_contents($sFileToPatch, $sContent);
|
||||
}
|
||||
|
||||
// Set an "Instance UUID" identifying this machine based on a file located in the data directory
|
||||
$sInstanceUUIDFile = utils::GetDataPath().'instance.txt';
|
||||
SetupUtils::builddir(utils::GetDataPath());
|
||||
if (!file_exists($sInstanceUUIDFile)) {
|
||||
$sIntanceUUID = utils::CreateUUID('filesystem');
|
||||
file_put_contents($sInstanceUUIDFile, $sIntanceUUID);
|
||||
}
|
||||
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
|
||||
SetupUtils::ExitMaintenanceMode();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
protected function DoUpdateDBSchema($aSelectedModules)
|
||||
{
|
||||
$sTargetEnvironment = $this->GetTargetEnv();
|
||||
$sModulesDir = $this->GetTargetDir();
|
||||
$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'");
|
||||
}
|
||||
|
||||
// Set a DBProperty with a unique ID to identify this instance of iTop
|
||||
$sUUID = DBProperty::GetProperty('database_uuid', '');
|
||||
if ($sUUID === '') {
|
||||
$sUUID = utils::CreateUUID('database');
|
||||
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();
|
||||
}
|
||||
}
|
||||
203
setup/sequencers/DataAuditSequencer.php
Normal file
203
setup/sequencers/DataAuditSequencer.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'setup/parameters.class.inc.php');
|
||||
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');
|
||||
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
|
||||
class DataAuditSequencer extends ApplicationInstallSequencer
|
||||
{
|
||||
public const DATA_AUDIT_FAILED = 100;
|
||||
|
||||
protected function GetTempEnv()
|
||||
{
|
||||
$sTargetEnv = $this->GetTargetEnv();
|
||||
return 'dry-'.$sTargetEnv;
|
||||
}
|
||||
|
||||
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 => )
|
||||
*/
|
||||
public function ExecuteStep($sStep = '', $sInstallComment = null)
|
||||
{
|
||||
try {
|
||||
$fStart = microtime(true);
|
||||
SetupLog::Info("##### STEP {$sStep} start");
|
||||
$this->EnterReadOnlyMode();
|
||||
switch ($sStep) {
|
||||
case '':
|
||||
$this->DoLogParameters('data-audit-', 'Data Audit');
|
||||
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'percentage-completed' => 20,
|
||||
'next-step' => 'compile',
|
||||
'next-step-label' => 'Compiling the data model',
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'compile':
|
||||
$aSelectedModules = $this->oParams->Get('selected_modules');
|
||||
$sSourceDir = $this->oParams->Get('source_dir', 'datamodels/latest');
|
||||
$sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions');
|
||||
$aRemovedExtensionCodes = $this->oParams->Get('removed_extensions', []);
|
||||
|
||||
$this->DoCompile(
|
||||
$aRemovedExtensionCodes,
|
||||
$aSelectedModules,
|
||||
$sSourceDir,
|
||||
$sExtensionDir,
|
||||
false
|
||||
);
|
||||
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => 'write-config',
|
||||
'next-step-label' => 'Writing audit config',
|
||||
'percentage-completed' => 40,
|
||||
];
|
||||
break;
|
||||
case 'write-config':
|
||||
$this->DoWriteConfig();
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => 'setup-audit',
|
||||
'next-step-label' => 'Checking data consistency with the new data model',
|
||||
'percentage-completed' => 60,
|
||||
];
|
||||
break;
|
||||
case 'setup-audit':
|
||||
$this->DoSetupAudit();
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => 'cleanup',
|
||||
'next-step-label' => 'Temporary folders cleanup',
|
||||
'percentage-completed' => 80,
|
||||
];
|
||||
break;
|
||||
case 'cleanup' :
|
||||
$this->DoCleanup();
|
||||
$aResult = [
|
||||
'status' => self::OK,
|
||||
'message' => '',
|
||||
'next-step' => '',
|
||||
'next-step-label' => 'Completed',
|
||||
'percentage-completed' => 100,
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$aResult = [
|
||||
'status' => self::ERROR,
|
||||
'message' => '',
|
||||
'next-step' => '',
|
||||
'next-step-label' => "Unknown setup step '$sStep'.",
|
||||
'percentage-completed' => 100,
|
||||
];
|
||||
break;
|
||||
}
|
||||
} 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);
|
||||
$this->ExitReadOnlyMode();
|
||||
} finally {
|
||||
$fDuration = round(microtime(true) - $fStart, 2);
|
||||
SetupLog::Info("##### STEP {$sStep} duration: {$fDuration}s");
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected function DoSetupAudit()
|
||||
{
|
||||
/**
|
||||
* @since 3.2.0 move the ContextTag init at the very beginning of the method
|
||||
* @noinspection PhpUnusedLocalVariableInspection
|
||||
*/
|
||||
$oContextTag = new ContextTag(ContextTag::TAG_SETUP);
|
||||
|
||||
$sTargetEnvironment = $this->GetTempEnv();
|
||||
$sPreviousEnvironment = $this->GetTargetEnv();
|
||||
|
||||
$oSetupAudit = new SetupAudit($sPreviousEnvironment, $sTargetEnvironment);
|
||||
|
||||
//Make sure the MetaModel is started before analysing for issues
|
||||
$sConfFile = utils::GetConfigFilePath($sPreviousEnvironment);
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, false /* $bAllowCache */, false /* $bTraceSourceFiles */, $sPreviousEnvironment);
|
||||
$oSetupAudit->GetIssues(true);
|
||||
$iCount = $oSetupAudit->GetDataToCleanupCount();
|
||||
|
||||
if ($iCount > 0) {
|
||||
throw new Exception("$iCount elements require data adjustments or cleanup in the backoffice prior to upgrading iTop", static::DATA_AUDIT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
protected function DoCleanup()
|
||||
{
|
||||
$sDestination = APPROOT.$this->GetTargetDir();
|
||||
SetupUtils::tidydir($sDestination);
|
||||
SetupUtils::rmdir_safe($sDestination);
|
||||
}
|
||||
}
|
||||
106
setup/sequencers/StepSequencer.php
Normal file
106
setup/sequencers/StepSequencer.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
abstract class StepSequencer
|
||||
{
|
||||
public const OK = 1;
|
||||
public const ERROR = 2;
|
||||
public const WARNING = 3;
|
||||
public const INFO = 4;
|
||||
protected array $aStepsHistory = [];
|
||||
|
||||
public function LogStep($sStep, $aResult)
|
||||
{
|
||||
$this->aStepsHistory[] = ['step' => $sStep, 'result' => $aResult];
|
||||
}
|
||||
public function GetHistory()
|
||||
{
|
||||
return $this->aStepsHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all the installation steps in one go and directly outputs
|
||||
* some information about the progress and the success of the various
|
||||
* sequential steps.
|
||||
*
|
||||
* @param bool $bVerbose
|
||||
* @param string|null $sMessage
|
||||
* @param string|null $sComment
|
||||
*
|
||||
* @return boolean True if the installation was successful, false otherwise
|
||||
*/
|
||||
public function ExecuteAllSteps(bool $bVerbose = true, ?string &$sMessage = null, ?string $sComment = null)
|
||||
{
|
||||
$sStep = '';
|
||||
$sStepLabel = '';
|
||||
$iOverallStatus = self::OK;
|
||||
do {
|
||||
|
||||
if ($bVerbose) {
|
||||
if ($sStep != '') {
|
||||
echo "$sStepLabel\n";
|
||||
echo "Executing '$sStep'\n";
|
||||
} else {
|
||||
echo "Starting...\n";
|
||||
}
|
||||
}
|
||||
$aRes = $this->ExecuteStep($sStep, $sComment);
|
||||
$this->LogStep($sStep, $aRes);
|
||||
$sStep = $aRes['next-step'];
|
||||
$sStepLabel = $aRes['next-step-label'];
|
||||
$sMessage = $aRes['message'];
|
||||
if ($bVerbose) {
|
||||
switch ($aRes['status']) {
|
||||
case self::OK:
|
||||
echo "Ok. ".$aRes['percentage-completed']." % done.\n";
|
||||
break;
|
||||
|
||||
case self::ERROR:
|
||||
$iOverallStatus = self::ERROR;
|
||||
echo "Error: ".$aRes['message']."\n";
|
||||
break;
|
||||
|
||||
case self::WARNING:
|
||||
$iOverallStatus = self::WARNING;
|
||||
echo "Warning: ".$aRes['message']."\n";
|
||||
echo $aRes['percentage-completed']." % done.\n";
|
||||
break;
|
||||
|
||||
case self::INFO:
|
||||
echo "Info: ".$aRes['message']."\n";
|
||||
echo $aRes['percentage-completed']." % done.\n";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch ($aRes['status']) {
|
||||
case self::ERROR:
|
||||
$iOverallStatus = self::ERROR;
|
||||
break;
|
||||
case self::WARNING:
|
||||
$iOverallStatus = self::WARNING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (($aRes['status'] != self::ERROR) && ($aRes['next-step'] != ''));
|
||||
|
||||
return ($iOverallStatus == self::OK);
|
||||
}
|
||||
|
||||
abstract public function ExecuteStep($sStep = '', $sComment = null);
|
||||
}
|
||||
@@ -25,21 +25,17 @@ function WizardAsyncAction(sActionCode, oParams, OnErrorFunction)
|
||||
|
||||
function WizardUpdateButtons()
|
||||
{
|
||||
if (CanMoveForward())
|
||||
{
|
||||
if (CanMoveForward()) {
|
||||
$("#btn_next").prop('disabled', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
$("#btn_next").prop('disabled', true);
|
||||
}
|
||||
|
||||
if (CanMoveBackward())
|
||||
{
|
||||
if (CanMoveBackward()) {
|
||||
$("#btn_back").prop('disabled', false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
$("#btn_back").prop('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,32 +254,23 @@ class SetupUtils
|
||||
|
||||
if (!utils::IsModeCLI()) {
|
||||
$sUploadTmpDir = self::GetUploadTmpDir();
|
||||
if (empty($sUploadTmpDir)) {
|
||||
$sUploadTmpDir = '/tmp';
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::WARNING,
|
||||
"Temporary directory for files upload is not defined (upload_tmp_dir), assuming that $sUploadTmpDir is used."
|
||||
);
|
||||
}
|
||||
// check that the upload directory is indeed writable from PHP
|
||||
if (!empty($sUploadTmpDir)) {
|
||||
if (!file_exists($sUploadTmpDir)) {
|
||||
if (!file_exists($sUploadTmpDir)) {
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::ERROR,
|
||||
"Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."
|
||||
);
|
||||
} else {
|
||||
if (!is_writable($sUploadTmpDir)) {
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::ERROR,
|
||||
"Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."
|
||||
"Temporary directory for files upload ($sUploadTmpDir) is not writable."
|
||||
);
|
||||
} else {
|
||||
if (!is_writable($sUploadTmpDir)) {
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::ERROR,
|
||||
"Temporary directory for files upload ($sUploadTmpDir) is not writable."
|
||||
);
|
||||
} else {
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::TRACE,
|
||||
"Info - Temporary directory for files upload ($sUploadTmpDir) is writable."
|
||||
);
|
||||
}
|
||||
$aResult[] = new CheckResult(
|
||||
CheckResult::TRACE,
|
||||
"Info - Temporary directory for files upload ($sUploadTmpDir) is writable."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,7 +509,7 @@ class SetupUtils
|
||||
}
|
||||
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
|
||||
try {
|
||||
ModuleDiscovery::GetAvailableModules($aDirsToScan, true, $aSelectedModules);
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aDirsToScan, true, $aSelectedModules);
|
||||
} catch (Exception $e) {
|
||||
$aResult[] = new CheckResult(CheckResult::ERROR, $e->getMessage());
|
||||
}
|
||||
@@ -599,7 +590,7 @@ class SetupUtils
|
||||
// create and test destination location
|
||||
//
|
||||
$sDestDir = dirname($sDBBackupPath);
|
||||
setuputils::builddir($sDestDir);
|
||||
SetupUtils::builddir($sDestDir);
|
||||
if (!is_dir($sDestDir)) {
|
||||
$aResult[] = new CheckResult(CheckResult::ERROR, "$sDestDir does not exist and could not be created.");
|
||||
}
|
||||
@@ -1555,17 +1546,8 @@ JS
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
public static function GetConfig(WizardController $oWizard)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
$oConfig = new Config();
|
||||
$sSourceDir = $oWizard->GetParameter('source_dir', '');
|
||||
|
||||
@@ -1579,8 +1561,26 @@ JS
|
||||
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$aParamValues['source_dir'] = $sRelativeSourceDir;
|
||||
$oConfig->UpdateFromParams($aParamValues, null);
|
||||
$aDirsToScan = [$sSourceDir];
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
|
||||
return $oConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WizardController $oWizard
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if ommitted
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function AnalyzeInstallation($oWizard, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
|
||||
$oConfig = self::GetConfig($oWizard);
|
||||
|
||||
$aDirsToScan = [$oWizard->GetParameter('source_dir', '')];
|
||||
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToScan[] = APPROOT.'extensions';
|
||||
@@ -1593,6 +1593,10 @@ JS
|
||||
$aDirsToScan[] = $sExtraDir;
|
||||
}
|
||||
$oProductionEnv = new RunTimeEnvironment();
|
||||
$aRemovedExtensionCodes = json_decode($oWizard->GetParameter('removed_extensions'), true) ?? [];
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aDirsToScan);
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
|
||||
|
||||
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
|
||||
foreach ($aAvailableModules as $key => $aModule) {
|
||||
@@ -1618,7 +1622,7 @@ JS
|
||||
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$aParamValues['source_dir'] = '';
|
||||
$oConfig->UpdateFromParams($aParamValues, null);
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
|
||||
$oProductionEnv = new RunTimeEnvironment();
|
||||
return $oProductionEnv->GetApplicationVersion($oConfig);
|
||||
@@ -2146,7 +2150,7 @@ class SetupInfo
|
||||
/**
|
||||
* Called by the setup process to initializes the list of selected modules. Do not call this method
|
||||
* from an 'auto_select' rule
|
||||
* @param hash $aModules
|
||||
* @param array $aModules
|
||||
* @return void
|
||||
*/
|
||||
public static function SetSelectedModules($aModules)
|
||||
|
||||
@@ -5,8 +5,7 @@ use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps_autoload.php');
|
||||
|
||||
class InstallationFileService
|
||||
{
|
||||
@@ -71,12 +70,12 @@ class InstallationFileService
|
||||
return $this->aAfterComputationSelectedExtensions;
|
||||
}
|
||||
|
||||
public function SetItopExtensionsMap(ItopExtensionsMap $oItopExtensionsMap): void
|
||||
public function SetItopExtensionsMap(iTopExtensionsMap $oItopExtensionsMap): void
|
||||
{
|
||||
$this->oItopExtensionsMap = $oItopExtensionsMap;
|
||||
}
|
||||
|
||||
public function GetItopExtensionsMap(): ItopExtensionsMap
|
||||
public function GetItopExtensionsMap(): iTopExtensionsMap
|
||||
{
|
||||
if (is_null($this->oItopExtensionsMap)) {
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
|
||||
@@ -259,7 +258,7 @@ class InstallationFileService
|
||||
{
|
||||
$sProductionModuleDir = APPROOT.'data/'.$this->sTargetEnvironment.'-modules/';
|
||||
|
||||
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs(), false, null);
|
||||
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs());
|
||||
|
||||
$this->aAutoSelectModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
|
||||
@@ -272,7 +272,7 @@ $bFoundIssues = false;
|
||||
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
|
||||
if ($bInstall) {
|
||||
echo "Starting the unattended installation...\n";
|
||||
$oWizard = new ApplicationInstaller($oParams);
|
||||
$oWizard = new ApplicationInstallSequencer($oParams);
|
||||
$bRes = $oWizard->ExecuteAllSteps();
|
||||
if (!$bRes) {
|
||||
echo "\nencountered installation issues!";
|
||||
|
||||
@@ -31,8 +31,7 @@ require_once('../approot.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/core/config.class.inc.php');
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps_autoload.php');
|
||||
|
||||
Session::Start();
|
||||
clearstatcache(); // Make sure we know what we are doing !
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||
require_once(APPROOT.'setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'setup/applicationinstaller.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'setup/extensionsmap.class.inc.php');
|
||||
|
||||
/**
|
||||
* Engine for displaying the various pages of a "wizard"
|
||||
* Each "step" of the wizard must be implemented as
|
||||
@@ -54,7 +59,7 @@ class WizardController
|
||||
|
||||
/**
|
||||
* Pushes information about the current step onto the stack
|
||||
* @param hash $aStepInfo Array('class' => , 'state' => )
|
||||
* @param array $aStepInfo Array('class' => , 'state' => )
|
||||
*/
|
||||
protected function PushStep($aStepInfo)
|
||||
{
|
||||
@@ -134,7 +139,7 @@ class WizardController
|
||||
public function Start()
|
||||
{
|
||||
$sCurrentStepClass = $this->sInitialStepClass;
|
||||
$oStep = new $sCurrentStepClass($this, $this->sInitialState);
|
||||
$oStep = $this->GetWizardStep($sCurrentStepClass, $this->sInitialState);
|
||||
$this->DisplayStep($oStep);
|
||||
}
|
||||
/**
|
||||
@@ -146,21 +151,24 @@ class WizardController
|
||||
$sCurrentStepClass = utils::ReadParam('_class', $this->sInitialStepClass);
|
||||
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
|
||||
/** @var \WizardStep $oStep */
|
||||
$oStep = new $sCurrentStepClass($this, $sCurrentState);
|
||||
$oStep = $oStep = $this->GetWizardStep($sCurrentStepClass, $sCurrentState);
|
||||
if ($oStep->ValidateParams()) {
|
||||
$this->PushStep(['class' => $sCurrentStepClass, 'state' => $sCurrentState]);
|
||||
if ($oStep->CanComeBack()) {
|
||||
$this->PushStep(['class' => $sCurrentStepClass, 'state' => $sCurrentState]);
|
||||
}
|
||||
$aPossibleSteps = $oStep->GetPossibleSteps();
|
||||
$aNextStepInfo = $oStep->ProcessParams(true); // true => moving forward
|
||||
if (in_array($aNextStepInfo['class'], $aPossibleSteps)) {
|
||||
$oNextStep = new $aNextStepInfo['class']($this, $aNextStepInfo['state']);
|
||||
$oWizardState = $oStep->UpdateWizardStateAndGetNextStep(true); // true => moving forward
|
||||
if (in_array($oWizardState->GetNextStep(), $aPossibleSteps)) {
|
||||
$oNextStep = $this->GetWizardStep($oWizardState->GetNextStep(), $oWizardState->GetState());
|
||||
$this->DisplayStep($oNextStep);
|
||||
} else {
|
||||
throw new Exception("Internal error: Unexpected next step '{$aNextStepInfo['class']}'. The possible next steps are: ".implode(', ', $aPossibleSteps));
|
||||
throw new Exception("Internal error: Unexpected next step '{$oWizardState->GetNextStep()}'. The possible next steps are: ".implode(', ', $aPossibleSteps));
|
||||
}
|
||||
} else {
|
||||
$this->DisplayStep($oStep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move one step back
|
||||
*/
|
||||
@@ -169,12 +177,12 @@ class WizardController
|
||||
// let the current step save its parameters
|
||||
$sCurrentStepClass = utils::ReadParam('_class', $this->sInitialStepClass);
|
||||
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
|
||||
$oStep = new $sCurrentStepClass($this, $sCurrentState);
|
||||
$aNextStepInfo = $oStep->ProcessParams(false); // false => Moving backwards
|
||||
$oStep = $this->GetWizardStep($sCurrentStepClass, $sCurrentState);
|
||||
$oWizardState = $oStep->UpdateWizardStateAndGetNextStep(false); // false => Moving backwards
|
||||
|
||||
// Display the previous step
|
||||
$aCurrentStepInfo = $this->PopStep();
|
||||
$oStep = new $aCurrentStepInfo['class']($this, $aCurrentStepInfo['state']);
|
||||
$oStep = $this->GetWizardStep($aCurrentStepInfo['class'], $aCurrentStepInfo['state']);
|
||||
$this->DisplayStep($oStep);
|
||||
}
|
||||
|
||||
@@ -316,7 +324,7 @@ on the page's parameters
|
||||
$sStep = $this->sInitialStepClass;
|
||||
}
|
||||
|
||||
$oStep = new $sStep($this, '');
|
||||
$oStep = $this->GetWizardStep($sStep);
|
||||
$aAllSteps[$sStep] = $oStep->GetPossibleSteps();
|
||||
foreach ($aAllSteps[$sStep] as $sNextStep) {
|
||||
if (!array_key_exists($sNextStep, $aAllSteps)) {
|
||||
@@ -348,7 +356,7 @@ on the page's parameters
|
||||
$sOutput .= "\tnode [shape = doublecircle]; ".implode(' ', $aDeadEnds).";\n";
|
||||
$sOutput .= "\tnode [shape = box];\n";
|
||||
foreach ($aAllSteps as $sStep => $aNextSteps) {
|
||||
$oStep = new $sStep($this, '');
|
||||
$oStep = $this->GetWizardStep($sStep);
|
||||
$sOutput .= "\t$sStep [ label = \"".$oStep->GetTitle()."\"];\n";
|
||||
if (count($aNextSteps) > 0) {
|
||||
foreach ($aNextSteps as $sNextStep) {
|
||||
@@ -359,326 +367,19 @@ on the page's parameters
|
||||
$sOutput .= "}\n";
|
||||
return $sOutput;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class to build "steps" for the wizard controller
|
||||
* If a step needs to maintain an internal "state" (for complex steps)
|
||||
* then it's up to the derived class to implement the behavior based on
|
||||
* the internal 'sCurrentState' variable.
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class WizardStep
|
||||
{
|
||||
/**
|
||||
* A reference to the WizardController
|
||||
* @var WizardController
|
||||
* @param string $sCurrentStepClass
|
||||
* @param string $sCurrentState
|
||||
*
|
||||
* @return \WizardStep
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected $oWizard;
|
||||
/**
|
||||
* Current 'state' of the wizard step. Simple 'steps' can ignore it
|
||||
* @var string
|
||||
*/
|
||||
protected $sCurrentState;
|
||||
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
private function GetWizardStep(string $sCurrentStepClass, string $sCurrentState = ''): WizardStep
|
||||
{
|
||||
$this->oWizard = $oWizard;
|
||||
$this->sCurrentState = $sCurrentState;
|
||||
}
|
||||
|
||||
public function GetState()
|
||||
{
|
||||
return $this->sCurrentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the wizard page for the current class/state
|
||||
* The page can contain any number of "<input/>" fields, but no "<form>...</form>" tag
|
||||
* The name of the input fields (and their id if one is supplied) MUST NOT start with "_"
|
||||
* (this is reserved for the wizard's own parameters)
|
||||
* @return void
|
||||
*/
|
||||
abstract public function Display(WebPage $oPage);
|
||||
/**
|
||||
* Displays the wizard page for the current class/state
|
||||
* return UIBlock
|
||||
* The name of the input fields (and their id if one is supplied) MUST NOT start with "_"
|
||||
* (this is reserved for the wizard's own parameters)
|
||||
* @return \Combodo\iTop\Application\UI\Base\UIBlock
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function DisplayBlock(WebPage $oPage)
|
||||
{
|
||||
return new Html($this->Display($oPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the page's parameters and (if moving forward) returns the next step/state to be displayed
|
||||
* @param bool $bMoveForward True if the wizard is moving forward 'Next >>' button pressed, false otherwise
|
||||
* @return hash array('class' => $sNextClass, 'state' => $sNextState)
|
||||
*/
|
||||
abstract public function ProcessParams($bMoveForward = true);
|
||||
|
||||
/**
|
||||
* Returns the list of possible steps from this step forward
|
||||
* @return array Array of strings (step classes)
|
||||
*/
|
||||
abstract public function GetPossibleSteps();
|
||||
|
||||
/**
|
||||
* Returns title of the current step
|
||||
* @return string The title of the wizard page for the current step
|
||||
*/
|
||||
abstract public function GetTitle();
|
||||
|
||||
/**
|
||||
* Tells whether the parameters are Ok to move forward
|
||||
* @return boolean True to move forward, false to stey on the same step
|
||||
*/
|
||||
public function ValidateParams()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step/state is the last one of the wizard (dead-end)
|
||||
* @return boolean True if the 'Next >>' button should be displayed
|
||||
*/
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return 'return true;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for the " Next >> " button
|
||||
* @return string The label for the button
|
||||
*/
|
||||
public function GetNextButtonLabel()
|
||||
{
|
||||
return 'Next';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step/state allows to go back or not
|
||||
* @return boolean True if the '<< Back' button should be displayed
|
||||
*/
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Back" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
return 'return true;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step of the wizard requires that the configuration file be writable
|
||||
* @return bool True if the wizard will possibly need to modify the configuration at some point
|
||||
*/
|
||||
public function RequiresWritableConfig()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this function to implement asynchronous action(s) (AJAX)
|
||||
* @param string $sCode The code of the action (if several actions need to be distinguished)
|
||||
* @param hash $aParameters The action's parameters name => value
|
||||
*/
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Example of a simple Setup Wizard with some parameters to store
|
||||
* the installation mode (install | upgrade) and a simple asynchronous
|
||||
* (AJAX) action.
|
||||
*
|
||||
* The setup wizard is executed by the following code:
|
||||
*
|
||||
* $oWizard = new WizardController('Step1');
|
||||
* $oWizard->Run();
|
||||
*
|
||||
class Step1 extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Welcome';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return array('Step2', 'Step2bis');
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
$sNextStep = '';
|
||||
$sInstallMode = utils::ReadParam('install_mode');
|
||||
if ($sInstallMode == 'install')
|
||||
{
|
||||
$this->oWizard->SetParameter('install_mode', 'install');
|
||||
$sNextStep = 'Step2';
|
||||
if (!is_subclass_of($sCurrentStepClass, WizardStep::class)) {
|
||||
throw new Exception('Unknown step '.$sCurrentStepClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->oWizard->SetParameter('install_mode', 'upgrade');
|
||||
$sNextStep = 'Step2bis';
|
||||
|
||||
}
|
||||
return array('class' => $sNextStep, 'state' => '');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->p('This is Step 1!');
|
||||
$sInstallMode = $this->oWizard->GetParameter('install_mode', 'install');
|
||||
$sChecked = ($sInstallMode == 'install') ? ' checked ' : '';
|
||||
$oPage->p('<input type="radio" name="install_mode" value="install"'.$sChecked.'/> Install');
|
||||
$sChecked = ($sInstallMode == 'upgrade') ? ' checked ' : '';
|
||||
$oPage->p('<input type="radio" name="install_mode" value="upgrade"'.$sChecked.'/> Upgrade');
|
||||
return new $sCurrentStepClass($this, $sCurrentState);
|
||||
}
|
||||
}
|
||||
|
||||
class Step2 extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Installation Parameters';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return array('Step3');
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
return array('class' => 'Step3', 'state' => '');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->p('This is Step 2! (Installation)');
|
||||
}
|
||||
}
|
||||
|
||||
class Step2bis extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Upgrade Parameters';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return array('Step2ter');
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
$sUpgradeInfo = utils::ReadParam('upgrade_info');
|
||||
$this->oWizard->SetParameter('upgrade_info', $sUpgradeInfo);
|
||||
$sAdditionalUpgradeInfo = utils::ReadParam('additional_upgrade_info');
|
||||
$this->oWizard->SetParameter('additional_upgrade_info', $sAdditionalUpgradeInfo);
|
||||
return array('class' => 'Step2ter', 'state' => '');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->p('This is Step 2bis! (Upgrade)');
|
||||
$sUpgradeInfo = $this->oWizard->GetParameter('upgrade_info', '');
|
||||
$oPage->p('Type your name here: <input type="text" id="upgrade_info" name="upgrade_info" value="'.$sUpgradeInfo.'" size="20"/><span id="v_upgrade_info"></span>');
|
||||
$sAdditionalUpgradeInfo = $this->oWizard->GetParameter('additional_upgrade_info', '');
|
||||
$oPage->p('The installer replies: <input type="text" name="additional_upgrade_info" value="'.$sAdditionalUpgradeInfo.'" size="20"/>');
|
||||
|
||||
$oPage->add_ready_script("$('#upgrade_info').change(function() {
|
||||
$('#v_upgrade_info').html('<img src=\"../images/indicator.gif\"/>');
|
||||
WizardAsyncAction('', { upgrade_info: $('#upgrade_info').val() }); });");
|
||||
}
|
||||
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
usleep(300000); // 300 ms
|
||||
$sName = $aParameters['upgrade_info'];
|
||||
$sReply = addslashes("Hello ".$sName);
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#v_upgrade_info").html('');
|
||||
$("input[name=additional_upgrade_info]").val("$sReply");
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Step2ter extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Additional Upgrade Info';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return array('Step3');
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
return array('class' => 'Step3', 'state' => '');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->p('This is Step 2ter! (Upgrade)');
|
||||
}
|
||||
}
|
||||
|
||||
class Step3 extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Installation Complete';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function ProcessParams($bMoveForward = true)
|
||||
{
|
||||
return array('class' => '', 'state' => '');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->p('This is the FINAL Step');
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
End of the example */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
111
setup/wizardsteps/AbstractWizStepInstall.php
Normal file
111
setup/wizardsteps/AbstractWizStepInstall.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
abstract class AbstractWizStepInstall extends WizardStep
|
||||
{
|
||||
/**
|
||||
* Prepare the parameters to execute the installation asynchronously
|
||||
* @return array A big hash array that can be converted to XML or JSON with all the needed parameters
|
||||
*/
|
||||
protected function BuildConfig()
|
||||
{
|
||||
$sMode = $this->oWizard->GetParameter('install_mode', 'install');
|
||||
$aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true);
|
||||
$aSelectedExtensions = json_decode($this->oWizard->GetParameter('selected_extensions'), true);
|
||||
$sBackupDestination = '';
|
||||
$sPreviousConfigurationFile = '';
|
||||
$sDBName = $this->oWizard->GetParameter('db_name');
|
||||
if ($sMode == 'upgrade') {
|
||||
$sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', '');
|
||||
if (!empty($sPreviousVersionDir)) {
|
||||
$aPreviousInstance = SetupUtils::GetPreviousInstance($sPreviousVersionDir);
|
||||
if ($aPreviousInstance['found']) {
|
||||
$sPreviousConfigurationFile = $aPreviousInstance['configuration_file'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->oWizard->GetParameter('db_backup', false)) {
|
||||
$sBackupDestination = $this->oWizard->GetParameter('db_backup_path', '');
|
||||
}
|
||||
} else {
|
||||
|
||||
$sDBNewName = $this->oWizard->GetParameter('db_new_name', '');
|
||||
if ($sDBNewName != '') {
|
||||
$sDBName = $sDBNewName; // Database will be created
|
||||
}
|
||||
}
|
||||
|
||||
$sSourceDir = $this->oWizard->GetParameter('source_dir');
|
||||
$aCopies = [];
|
||||
if (($sMode == 'upgrade') && ($this->oWizard->GetParameter('upgrade_type') == 'keep-previous')) {
|
||||
$sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir');
|
||||
$aCopies[] = ['source' => $sSourceDir, 'destination' => 'modules']; // Source is an absolute path, destination is relative to APPROOT
|
||||
$aCopies[] = ['source' => $sPreviousVersionDir.'/portal', 'destination' => 'portal']; // Source is an absolute path, destination is relative to APPROOT
|
||||
$sSourceDir = APPROOT.'modules';
|
||||
}
|
||||
|
||||
$aInstallParams = [
|
||||
'mode' => $sMode,
|
||||
'preinstall' => [
|
||||
'copies' => $aCopies,
|
||||
// 'backup' => see below
|
||||
],
|
||||
'source_dir' => str_replace(APPROOT, '', $sSourceDir),
|
||||
'datamodel_version' => $this->oWizard->GetParameter('datamodel_version'), //TODO: let the installer compute this automatically...
|
||||
'previous_configuration_file' => $sPreviousConfigurationFile,
|
||||
'extensions_dir' => 'extensions',
|
||||
'target_env' => 'production',
|
||||
'workspace_dir' => '',
|
||||
'database' => [
|
||||
'server' => $this->oWizard->GetParameter('db_server'),
|
||||
'user' => $this->oWizard->GetParameter('db_user'),
|
||||
'pwd' => $this->oWizard->GetParameter('db_pwd'),
|
||||
'name' => $sDBName,
|
||||
'db_tls_enabled' => $this->oWizard->GetParameter('db_tls_enabled'),
|
||||
'db_tls_ca' => $this->oWizard->GetParameter('db_tls_ca'),
|
||||
'prefix' => $this->oWizard->GetParameter('db_prefix'),
|
||||
],
|
||||
'url' => $this->oWizard->GetParameter('application_url'),
|
||||
'graphviz_path' => $this->oWizard->GetParameter('graphviz_path'),
|
||||
'admin_account' => [
|
||||
'user' => $this->oWizard->GetParameter('admin_user'),
|
||||
'pwd' => $this->oWizard->GetParameter('admin_pwd'),
|
||||
'language' => $this->oWizard->GetParameter('admin_language'),
|
||||
],
|
||||
'language' => $this->oWizard->GetParameter('default_language'),
|
||||
'selected_modules' => $aSelectedModules,
|
||||
'selected_extensions' => $aSelectedExtensions,
|
||||
'sample_data' => $this->oWizard->GetParameter('sample_data', '') === 'yes',
|
||||
'old_addon' => $this->oWizard->GetParameter('old_addon', false), // whether or not to use the "old" userrights profile addon
|
||||
'options' => json_decode($this->oWizard->GetParameter('misc_options', '[]'), true),
|
||||
'mysql_bindir' => $this->oWizard->GetParameter('mysql_bindir'),
|
||||
];
|
||||
|
||||
if ($sBackupDestination != '') {
|
||||
$aInstallParams['preinstall']['backup'] = [
|
||||
'destination' => $sBackupDestination,
|
||||
'configuration_file' => $sPreviousConfigurationFile,
|
||||
];
|
||||
}
|
||||
|
||||
return $aInstallParams;
|
||||
}
|
||||
|
||||
}
|
||||
73
setup/wizardsteps/AbstractWizStepMiscParams.php
Normal file
73
setup/wizardsteps/AbstractWizStepMiscParams.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* @since 3.0.0 N°4092
|
||||
*/
|
||||
abstract class AbstractWizStepMiscParams extends WizardStep
|
||||
{
|
||||
/**
|
||||
* @since 3.0.0 N°4092
|
||||
*/
|
||||
final protected function AddUseSymlinksFlagOption(WebPage $oPage): void
|
||||
{
|
||||
if (MFCompiler::CanUseSymbolicLinksFlagBeUsed()) {
|
||||
$sChecked = (MFCompiler::IsUseSymbolicLinksFlagPresent()) ? ' checked' : '';
|
||||
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Dev parameters</legend>');
|
||||
$oPage->p('<input id="use-symbolic-links" type="checkbox"'.$sChecked.'><label for="use-symbolic-links"> Create symbolic links instead of creating a copy in env-production (useful for debugging extensions)');
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add_ready_script(
|
||||
<<<'JS'
|
||||
$("#use-symbolic-links").on("click", function() {
|
||||
var $this = $(this),
|
||||
bUseSymbolicLinks = $this.prop("checked");
|
||||
var sAuthent = $('#authent_token').val();
|
||||
var oAjaxParams = { operation: 'toggle_use_symbolic_links', bUseSymbolicLinks: bUseSymbolicLinks, authent: sAuthent};
|
||||
$.post(GetAbsoluteUrlAppRoot()+'setup/ajax.dataloader.php', oAjaxParams);
|
||||
});
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final protected function AddForceUninstallFlagOption(WebPage $oPage): void
|
||||
{
|
||||
$sChecked = $this->oWizard->GetParameter('force-uninstall', false) ? ' checked ' : '';
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Advanced parameters</legend>');
|
||||
$oPage->p('<input id="force-uninstall" type="checkbox"'.$sChecked.' name="force-uninstall"><label for="force-uninstall"> Disable uninstallation checks for extensions');
|
||||
$oPage->add('</fieldset>');
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<'JS'
|
||||
$("#force-uninstall").on("click", function() {
|
||||
let $this = $(this);
|
||||
let bForceUninstall = $this.prop("checked");
|
||||
if( bForceUninstall && !confirm('Beware, uninstalling extensions flagged as non uninstallable may result in data corruption and application crashes. Are you sure you want to continue ?')){
|
||||
$this.prop("checked",false);
|
||||
}
|
||||
});
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
109
setup/wizardsteps/WizStepAdminAccount.php
Normal file
109
setup/wizardsteps/WizStepAdminAccount.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* Administrator Account definition screen
|
||||
*/
|
||||
class WizStepAdminAccount extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Administrator Account';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepInstallMiscParams::class];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$this->oWizard->SaveParameter('admin_user', '');
|
||||
$this->oWizard->SaveParameter('admin_pwd', '');
|
||||
$this->oWizard->SaveParameter('confirm_pwd', '');
|
||||
$this->oWizard->SaveParameter('admin_language', 'EN US');
|
||||
|
||||
return new WizardState(WizStepInstallMiscParams::class);
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$sAdminUser = $this->oWizard->GetParameter('admin_user', 'admin');
|
||||
$sAdminPwd = $this->oWizard->GetParameter('admin_pwd', '');
|
||||
$sConfirmPwd = $this->oWizard->GetParameter('confirm_pwd', '');
|
||||
$sAdminLanguage = $this->oWizard->GetParameter('admin_language', 'EN US');
|
||||
$oPage->add('<h2>Definition of the Administrator Account</h2>');
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Administrator Account</legend>');
|
||||
$oPage->add('<table>');
|
||||
$oPage->add('<tr><td>Login: </td><td><input id="admin_user" class="ibo-input" name="admin_user" type="text" size="25" maxlength="64" value="'.utils::EscapeHtml($sAdminUser).'"><span id="v_admin_user"/></td></tr>');
|
||||
$oPage->add('<tr><td>Password: </td><td><input id="admin_pwd" class="ibo-input" autocomplete="off" name="admin_pwd" type="password" size="25" maxlength="64" value="'.utils::EscapeHtml($sAdminPwd).'"><span id="v_admin_pwd"/></td></tr>');
|
||||
$oPage->add('<tr><td>Confirm password: </td><td><input id="confirm_pwd" class="ibo-input" autocomplete="off" name="confirm_pwd" type="password" size="25" maxlength="64" value="'.utils::EscapeHtml($sConfirmPwd).'"></td></tr>');
|
||||
$sSourceDir = APPROOT.'dictionaries/';
|
||||
$aLanguages = SetupUtils::GetAvailableLanguages($sSourceDir);
|
||||
$oPage->add('<tr><td>Language: </td><td>');
|
||||
$oPage->add(SetupUtils::GetLanguageSelect($sSourceDir, 'admin_language', $sAdminLanguage));
|
||||
$oPage->add('</td></tr>');
|
||||
$oPage->add('</table>');
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#admin_user').on('change keyup', function() { WizardUpdateButtons(); } );
|
||||
$('#admin_pwd').on('change keyup', function() { WizardUpdateButtons(); } );
|
||||
$('#confirm_pwd').on('change keyup', function() { WizardUpdateButtons(); } );
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return
|
||||
<<<EOF
|
||||
bRet = ($('#admin_user').val() != '');
|
||||
if (!bRet)
|
||||
{
|
||||
$("#v_admin_user").html('<i class="fas fa-exclamation-triangle setup-invalid-field--icon" title="This field cannot be empty"></i>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#v_admin_user").html('');
|
||||
}
|
||||
|
||||
bPasswordsMatch = ($('#admin_pwd').val() == $('#confirm_pwd').val());
|
||||
if (!bPasswordsMatch)
|
||||
{
|
||||
$('#v_admin_pwd').html('<i class="fas fa-exclamation-triangle setup-invalid-field--icon" title="Retyped password does not match"></i>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#v_admin_pwd').html('');
|
||||
}
|
||||
bRet = bPasswordsMatch && bRet;
|
||||
|
||||
return bRet;
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
118
setup/wizardsteps/WizStepDBParams.php
Normal file
118
setup/wizardsteps/WizStepDBParams.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Database Connection parameters screen
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
class WizStepDBParams extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Database Configuration';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepAdminAccount::class];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$this->oWizard->SaveParameter('db_server', '');
|
||||
$this->oWizard->SaveParameter('db_user', '');
|
||||
$this->oWizard->SaveParameter('db_pwd', '');
|
||||
$this->oWizard->SaveParameter('db_name', '');
|
||||
$this->oWizard->SaveParameter('db_prefix', '');
|
||||
$this->oWizard->SaveParameter('new_db_name', '');
|
||||
$this->oWizard->SaveParameter('create_db', '');
|
||||
$this->oWizard->SaveParameter('db_new_name', '');
|
||||
$this->oWizard->SaveParameter('db_tls_enabled', false);
|
||||
$this->oWizard->SaveParameter('db_tls_ca', '');
|
||||
|
||||
return new WizardState(WizStepAdminAccount::class);
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->add('<h2>Configuration of the database connection:</h2>');
|
||||
$sDBServer = $this->oWizard->GetParameter('db_server', '');
|
||||
$sDBUser = $this->oWizard->GetParameter('db_user', '');
|
||||
$sDBPwd = $this->oWizard->GetParameter('db_pwd', '');
|
||||
$sDBName = $this->oWizard->GetParameter('db_name', '');
|
||||
$sDBPrefix = $this->oWizard->GetParameter('db_prefix', '');
|
||||
$sTlsEnabled = $this->oWizard->GetParameter('db_tls_enabled', '');
|
||||
$sTlsCA = $this->oWizard->GetParameter('db_tls_ca', '');
|
||||
$sNewDBName = $this->oWizard->GetParameter('db_new_name', false);
|
||||
|
||||
$oPage->add('<table>');
|
||||
SetupUtils::DisplayDBParameters(
|
||||
$oPage,
|
||||
true,
|
||||
$sDBServer,
|
||||
$sDBUser,
|
||||
$sDBPwd,
|
||||
$sDBName,
|
||||
$sDBPrefix,
|
||||
$sTlsEnabled,
|
||||
$sTlsCA,
|
||||
$sNewDBName
|
||||
);
|
||||
$sAuthentToken = $this->oWizard->GetParameter('authent', '');
|
||||
$oPage->add('<input type="hidden" id="authent_token" value="'.$sAuthentToken.'"/>');
|
||||
$oPage->add('</table>');
|
||||
$sCreateDB = $this->oWizard->GetParameter('create_db', 'yes');
|
||||
if ($sCreateDB == 'no') {
|
||||
$oPage->add_ready_script('$("#existing_db").prop("checked", true);');
|
||||
} else {
|
||||
$oPage->add_ready_script('$("#create_db").prop("checked", true);');
|
||||
}
|
||||
}
|
||||
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
switch ($sCode) {
|
||||
case 'check_db':
|
||||
SetupUtils::AsyncCheckDB($oPage, $aParameters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return
|
||||
<<<EOF
|
||||
if ($("#wiz_form").data("db_connection") === "error") return false;
|
||||
|
||||
var bRet = true;
|
||||
bRet = ValidateField("db_name", true) && bRet;
|
||||
bRet = ValidateField("db_new_name", true) && bRet;
|
||||
bRet = ValidateField("db_prefix", true) && bRet;
|
||||
|
||||
return bRet;
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
123
setup/wizardsteps/WizStepDataAudit.php
Normal file
123
setup/wizardsteps/WizStepDataAudit.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
require_once(APPROOT.'setup/sequencers/DataAuditSequencer.php');
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
*/
|
||||
class WizStepDataAudit extends WizStepInstall
|
||||
{
|
||||
public const SequencerClass = DataAuditSequencer::class;
|
||||
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Checking compatibility';
|
||||
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepSummary::class];
|
||||
}
|
||||
|
||||
public function GetNextButtonLabel()
|
||||
{
|
||||
return 'Next';
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
if ($this->CheckDependencies()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
return new WizardState(WizStepSummary::class);
|
||||
}
|
||||
|
||||
public function CanComeBack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
|
||||
$aInstallParams = $this->BuildConfig();
|
||||
|
||||
$this->AddProgressBar($oPage, 'Progress of the verification');
|
||||
|
||||
$sJSONData = json_encode($aInstallParams);
|
||||
$oPage->add('<input type="hidden" id="installer_parameters" value="'.utils::EscapeHtml($sJSONData).'"/>');
|
||||
|
||||
$sAuthentToken = $this->oWizard->GetParameter('authent', '');
|
||||
$oPage->add('<input type="hidden" id="authent_token" value="'.$sAuthentToken.'"/>');
|
||||
$sApplicationUrl = $this->oWizard->GetParameter('application_url').'pages/exec.php?exec_module=combodo-data-feature-removal&exec_page=index.php';
|
||||
$oPage->add('<input type="hidden" id="application_url" value="'.$sApplicationUrl.'"/>');
|
||||
if (!$this->CheckDependencies()) {
|
||||
$oPage->error($this->sDependencyIssue);
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$("#wiz_form").data("installation_status", "error");
|
||||
document.getElementById("setup_msg").innerText = "Unmet dependencies";
|
||||
JS);
|
||||
} else {
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$("#wiz_form").data("installation_status", "not started");
|
||||
ExecuteStep("");
|
||||
JS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function AddProgressErrorScript($oPage, $aRes)
|
||||
{
|
||||
if (isset($aRes['error_code']) && $aRes['error_code'] === DataAuditSequencer::DATA_AUDIT_FAILED) {
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(2)').before('<td style="text-align:center;"><button class="ibo-button ibo-is-alternative ibo-is-neutral" type="submit" name="operation" value="next"><span class="ibo-button--label">Ignore and continue</span></button></td>');
|
||||
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(2)').after('<td style="text-align:center;"><a href="'+$('#application_url').val()+'"><button class="default ibo-button ibo-is-regular ibo-is-primary" type="button"><span class="ibo-button--label">Go to backoffice</span></button></a></td>');
|
||||
|
||||
$("#wiz_form").data("installation_status", "cleanup_needed");
|
||||
$('#btn_next').hide();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return 'return ["completed", "cleanup_needed"].indexOf($("#wiz_form").data("installation_status")) !== -1;';
|
||||
}
|
||||
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
return 'return ["not started", "error", "cleanup_needed"].indexOf($("#wiz_form").data("installation_status")) !== -1;';
|
||||
}
|
||||
}
|
||||
258
setup/wizardsteps/WizStepDetectedInfo.php
Normal file
258
setup/wizardsteps/WizStepDetectedInfo.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* Upgrade information
|
||||
*/
|
||||
class WizStepDetectedInfo extends WizardStep
|
||||
{
|
||||
protected $bCanMoveForward;
|
||||
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Upgrade Information';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepUpgradeMiscParams::class, WizStepLicense2::class];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$sUpgradeType = utils::ReadParam('upgrade_type');
|
||||
|
||||
$this->oWizard->SetParameter('mode', 'upgrade');
|
||||
$this->oWizard->SetParameter('upgrade_type', $sUpgradeType);
|
||||
$bDisplayLicense = $this->oWizard->GetParameter('display_license');
|
||||
|
||||
switch ($sUpgradeType) {
|
||||
case 'keep-previous':
|
||||
$sSourceDir = utils::ReadParam('relative_source_dir', '', false, 'raw_data');
|
||||
$this->oWizard->SetParameter('source_dir', $this->oWizard->GetParameter('previous_version_dir').'/'.$sSourceDir);
|
||||
$this->oWizard->SetParameter('datamodel_version', utils::ReadParam('datamodel_previous_version', '', false, 'raw_data'));
|
||||
break;
|
||||
|
||||
case 'use-compatible':
|
||||
$sDataModelPath = utils::ReadParam('datamodel_path', '', false, 'raw_data');
|
||||
$this->oWizard->SetParameter('source_dir', $sDataModelPath);
|
||||
$this->oWizard->SaveParameter('datamodel_version', '');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing, maybe the user pressed the Back button
|
||||
}
|
||||
if ($bDisplayLicense) {
|
||||
$aRet = new WizardState(WizStepLicense2::class);
|
||||
} else {
|
||||
$aRet = new WizardState(WizStepUpgradeMiscParams::class);
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$oPage->add_style(
|
||||
<<<EOF
|
||||
#changes_summary {
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
#changes_summary div {
|
||||
width:100;
|
||||
margin-top:0;
|
||||
padding-top: 0.5em;
|
||||
padding-left: 0;
|
||||
}
|
||||
#changes_summary div ul {
|
||||
margin-left:0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
#changes_summary div.closed ul {
|
||||
display:none;
|
||||
}
|
||||
#changes_summary div li {
|
||||
list-style: none;
|
||||
width: 100;
|
||||
margin-left:0;
|
||||
padding-left: 0em;
|
||||
}
|
||||
.title {
|
||||
padding-left: 20px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background: url(../images/minus.gif) 2px 2px no-repeat;
|
||||
}
|
||||
#changes_summary div.closed .title {
|
||||
background: url(../images/plus.gif) 2px 2px no-repeat;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$this->bCanMoveForward = true;
|
||||
$bDisplayLicense = true;
|
||||
$sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', '');
|
||||
$aInstalledInfo = SetupUtils::GetApplicationVersion($this->oWizard);
|
||||
|
||||
if ($aInstalledInfo === false) {
|
||||
throw(new Exception('No previous version of '.ITOP_APPLICATION.' found in the supplied database. The upgrade cannot continue.'));
|
||||
} elseif (strcasecmp($aInstalledInfo['product_name'], ITOP_APPLICATION) != 0) {
|
||||
$oPage->p("<b>Warning: The installed products seem different. Are you sure that you want to upgrade {$aInstalledInfo['product_name']} with ".ITOP_APPLICATION."?</b>");
|
||||
}
|
||||
|
||||
$sInstalledVersion = $aInstalledInfo['product_version'];
|
||||
$sInstalledDataModelVersion = $aInstalledInfo['datamodel_version'];
|
||||
|
||||
$oPage->add("<h2>Information about the upgrade from version $sInstalledVersion to ".ITOP_VERSION_FULL."</h2>");
|
||||
|
||||
if ($sInstalledVersion == ITOP_VERSION_FULL) {
|
||||
// Reinstalling the same version let's skip the license agreement...
|
||||
$bDisplayLicense = false;
|
||||
}
|
||||
$this->oWizard->SetParameter('display_license', $bDisplayLicense); // Remember for later
|
||||
|
||||
$sCompatibleDMDir = SetupUtils::GetLatestDataModelDir();
|
||||
if ($sCompatibleDMDir === false) {
|
||||
// No compatible version exists... cannot upgrade. Either it is too old, or too new (downgrade !)
|
||||
$this->bCanMoveForward = false;
|
||||
$oPage->p("No datamodel directory found.");
|
||||
} else {
|
||||
$sUpgradeDMVersion = SetupUtils::GetDataModelVersion($sCompatibleDMDir);
|
||||
$sPreviousSourceDir = isset($aInstalledInfo['source_dir']) ? $aInstalledInfo['source_dir'] : 'modules';
|
||||
$aChanges = false;
|
||||
if (is_dir($sPreviousVersionDir)) {
|
||||
// Check if the previous version is a "genuine" one or not...
|
||||
$aChanges = SetupUtils::CheckVersion($sInstalledDataModelVersion, $sPreviousVersionDir.'/'.$sPreviousSourceDir);
|
||||
}
|
||||
if (($aChanges !== false) && ((count($aChanges['added']) > 0) || (count($aChanges['removed']) > 0) || (count($aChanges['modified']) > 0))) {
|
||||
// Some changes were detected, prompt the user to keep or discard them
|
||||
$oPage->p("<img src=\"../images/error.png\"/> Some modifications were detected between the ".ITOP_APPLICATION." version in '$sPreviousVersionDir' and a genuine $sInstalledVersion version.");
|
||||
$oPage->p("What do you want to do?");
|
||||
|
||||
$aWritableDirs = ['modules', 'portal'];
|
||||
$aErrors = SetupUtils::CheckWritableDirs($aWritableDirs);
|
||||
$sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'keep-previous') ? ' checked ' : '';
|
||||
$sDisabled = (count($aErrors) > 0) ? ' disabled ' : '';
|
||||
|
||||
$oPage->p('<input id="radio_upgrade_keep" type="radio" name="upgrade_type" value="keep-previous" '.$sChecked.$sDisabled.'/><label for="radio_upgrade_keep"> Preserve the modifications of the installed version (the dashboards inside '.ITOP_APPLICATION.' may not be editable).</label>');
|
||||
$oPage->add('<input type="hidden" name="datamodel_previous_version" value="'.utils::EscapeHtml($sInstalledDataModelVersion).'">');
|
||||
|
||||
$oPage->add('<input type="hidden" name="relative_source_dir" value="'.utils::EscapeHtml($sPreviousSourceDir).'">');
|
||||
|
||||
if (count($aErrors) > 0) {
|
||||
$oPage->p("Cannot copy the installed version due to the following access rights issue(s):");
|
||||
foreach ($aErrors as $sDir => $oCheckResult) {
|
||||
$oPage->p('<img src="../images/error.png"/> '.$oCheckResult->sLabel);
|
||||
}
|
||||
}
|
||||
|
||||
$sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'use-compatible') ? ' checked ' : '';
|
||||
|
||||
$oPage->p('<input id="radio_upgrade_convert" type="radio" name="upgrade_type" value="use-compatible" '.$sChecked.'/><label for="radio_upgrade_convert"> Discard the modifications, use a standard '.$sUpgradeDMVersion.' data model.</label>');
|
||||
|
||||
$oPage->add('<input type="hidden" name="datamodel_path" value="'.utils::EscapeHtml($sCompatibleDMDir).'">');
|
||||
$oPage->add('<input type="hidden" name="datamodel_version" value="'.utils::EscapeHtml($sUpgradeDMVersion).'">');
|
||||
|
||||
$oPage->add('<div id="changes_summary"><div class="closed"><span class="title">Details of the modifications</span><div>');
|
||||
if (count($aChanges['added']) > 0) {
|
||||
$oPage->add('<ul>New files added:');
|
||||
foreach ($aChanges['added'] as $sFilePath => $void) {
|
||||
$oPage->add('<li>'.$sFilePath.'</li>');
|
||||
}
|
||||
$oPage->add('</ul>');
|
||||
}
|
||||
if (count($aChanges['removed']) > 0) {
|
||||
$oPage->add('<ul>Deleted files:');
|
||||
foreach ($aChanges['removed'] as $sFilePath => $void) {
|
||||
$oPage->add('<li>'.$sFilePath.'</li>');
|
||||
}
|
||||
$oPage->add('</ul>');
|
||||
}
|
||||
if (count($aChanges['modified']) > 0) {
|
||||
$oPage->add('<ul>Modified files:');
|
||||
foreach ($aChanges['modified'] as $sFilePath => $void) {
|
||||
$oPage->add('<li>'.$sFilePath.'</li>');
|
||||
}
|
||||
$oPage->add('</ul>');
|
||||
}
|
||||
$oPage->add('</div></div></div>');
|
||||
} else {
|
||||
// No changes detected... or no way to tell because of the lack of a manifest or previous source dir
|
||||
// Use the "compatible" datamodel as-is.
|
||||
$sCompatibleDMDirToDisplay = utils::HtmlEntities($sCompatibleDMDir);
|
||||
$sUpgradeDMVersionToDisplay = utils::HtmlEntities($sUpgradeDMVersion);
|
||||
$oPage->add(
|
||||
<<<HTML
|
||||
<div class="message message-valid">The datamodel will be upgraded from version $sInstalledDataModelVersion to version $sUpgradeDMVersion.</div>
|
||||
<input type="hidden" name="upgrade_type" value="use-compatible">
|
||||
<input type="hidden" name="datamodel_path" value="$sCompatibleDMDirToDisplay">
|
||||
<input type="hidden" name="datamodel_version" value="$sUpgradeDMVersionToDisplay">
|
||||
HTML
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#changes_summary .title").on('click', function() { $(this).parent().toggleClass('closed'); } );
|
||||
$('input[name=upgrade_type]').on('click change', function() { WizardUpdateButtons(); });
|
||||
EOF
|
||||
);
|
||||
|
||||
$oMutex = new iTopMutex(
|
||||
'cron'.$this->oWizard->GetParameter('db_name', '').$this->oWizard->GetParameter('db_prefix', ''),
|
||||
$this->oWizard->GetParameter('db_server', ''),
|
||||
$this->oWizard->GetParameter('db_user', ''),
|
||||
$this->oWizard->GetParameter('db_pwd', ''),
|
||||
$this->oWizard->GetParameter('db_tls_enabled', ''),
|
||||
$this->oWizard->GetParameter('db_tls_ca', '')
|
||||
);
|
||||
if ($oMutex->IsLocked()) {
|
||||
$oPage->add('<div class="message">'.ITOP_APPLICATION.' cron process is being executed on the target database. '.ITOP_APPLICATION.' cron process will be stopped during the setup execution.</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return $this->bCanMoveForward;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return
|
||||
<<<EOF
|
||||
if ($("#radio_upgrade_keep").length == 0) return true;
|
||||
|
||||
bRet = ($('input[name=upgrade_type]:checked').length > 0);
|
||||
return bRet;
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
169
setup/wizardsteps/WizStepDone.php
Normal file
169
setup/wizardsteps/WizStepDone.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* Summary of the installation tasks
|
||||
*/
|
||||
class WizStepDone extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Done';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
return new WizardState('');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
// Check if there are some manual steps required:
|
||||
$aManualSteps = [];
|
||||
$aAvailableModules = SetupUtils::AnalyzeInstallation($this->oWizard);
|
||||
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot(true);
|
||||
$aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true);
|
||||
foreach ($aSelectedModules as $sModuleId) {
|
||||
if (!empty($aAvailableModules[$sModuleId]['doc.manual_setup'])) {
|
||||
$sUrl = $aAvailableModules[$sModuleId]['doc.manual_setup'];
|
||||
$sManualStepUrl = utils::IsURL($sUrl) ? $sUrl : $sRootUrl.$sUrl;
|
||||
$aManualSteps[$aAvailableModules[$sModuleId]['label']] = $sManualStepUrl;
|
||||
}
|
||||
}
|
||||
$oPage->add('<div class="ibo-is-html-content">');
|
||||
if (count($aManualSteps) > 0) {
|
||||
$oPage->add("<h2>Manual operations required</h2>");
|
||||
$oPage->p("In order to complete the installation, the following manual operations are required:");
|
||||
foreach ($aManualSteps as $sModuleLabel => $sUrl) {
|
||||
$oPage->p("<a href=\"$sUrl\" target=\"_blank\">Manual instructions for $sModuleLabel</a>");
|
||||
}
|
||||
$oPage->add("<h2>Congratulations for installing ".ITOP_APPLICATION."</h2>");
|
||||
} else {
|
||||
$oPage->add("<h2>Congratulations for installing ".ITOP_APPLICATION."</h2>");
|
||||
$oPage->ok("The installation completed successfully.");
|
||||
}
|
||||
|
||||
$bHasBackup = false;
|
||||
if (($this->oWizard->GetParameter('mode', '') == 'upgrade') && $this->oWizard->GetParameter('db_backup', false) && $this->oWizard->GetParameter('authent', false)) {
|
||||
$sBackupDestination = $this->oWizard->GetParameter('db_backup_path', '');
|
||||
if (file_exists($sBackupDestination.'.tar.gz')) {
|
||||
$bHasBackup = true;
|
||||
// To mitigate security risks: pass only the filename without the extension, the download will add the extension itself
|
||||
$oPage->p('Your backup is ready');
|
||||
$oPage->p('<a style="background:transparent;" href="'.utils::GetAbsoluteUrlAppRoot(true).'setup/ajax.dataloader.php?operation=async_action&step_class=WizStepDone¶ms[backup]='.urlencode($sBackupDestination).'&authent='.$this->oWizard->GetParameter('authent', '').'" target="_blank"><img src="../images/icons/icons8-archive-folder.svg" style="border:0;vertical-align:middle;"> Download '.basename($sBackupDestination).'</a>');
|
||||
} else {
|
||||
$oPage->p('<img src="../images/error.png"/> Warning: Backup creation failed !');
|
||||
}
|
||||
}
|
||||
|
||||
// Form goes here.. No back button since the job is done !
|
||||
$oPage->add('<div id="placeholder" class="setup-end-placeholder">');
|
||||
$oPage->add("<div><a class=\"ibo-svg-illustration--container\" title=\"Subscribe to Combodo Newsletter.\" href=\"https://www.combodo.com/newsletter-subscription?var_mode=recalcul\" target=\"_blank\">".file_get_contents(APPROOT.'images/illustrations/undraw_newsletter.svg')." Register now</a></div>");
|
||||
$oPage->add("<div><a class=\"ibo-svg-illustration--container\" title=\"Get Professional Support from Combodo\" href=\"https://support.combodo.com\" target=\"_blank\">".file_get_contents(APPROOT.'images/illustrations/undraw_active_support.svg')."Get professional support</a></div>");
|
||||
$oPage->add("<div><a class=\"ibo-svg-illustration--container\" title=\"Get Professional Training from Combodo\" href=\"http://www.combodo.com/training\" target=\"_blank\">".file_get_contents(APPROOT.'images/illustrations/undraw_education.svg')."Get professional training</a></div>");
|
||||
$oPage->add('</div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
|
||||
$oConfig = new Config(utils::GetConfigFilePath());
|
||||
$aParamValues = $this->oWizard->GetParamForConfigArray();
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
// Load the data model only, in order to load env-production/core/main.php to get the XML parameters (needed by GetModuleSettings below)
|
||||
// But main.php may also contain classes (defined without any module), and thus requiring the full data model
|
||||
// to be loaded to prevent "class not found" errors...
|
||||
$oProductionEnv = new RunTimeEnvironment('production');
|
||||
$oProductionEnv->InitDataModel($oConfig, true);
|
||||
$sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
|
||||
|
||||
$sSetupTokenFile = APPROOT.'data/.setup';
|
||||
$sSetupToken = bin2hex(random_bytes(12));
|
||||
file_put_contents($sSetupTokenFile, $sSetupToken);
|
||||
$sIframeUrl .= "&setup_token=$sSetupToken";
|
||||
|
||||
if ($sIframeUrl != '') {
|
||||
$oPage->add('<iframe id="fresh_content" frameborder="0" scrolling="auto" src="'.$sIframeUrl.'"></iframe>');
|
||||
|
||||
$oPage->add_script("
|
||||
window.addEventListener('message', function(event) {
|
||||
if (event.data === 'itophub_load_completed')
|
||||
{
|
||||
$('#placeholder').hide();
|
||||
$('#fresh_content').show();
|
||||
}
|
||||
}, false);
|
||||
");
|
||||
}
|
||||
|
||||
$sForm = '<div class="ibo-setup--wizard--buttons-container" style="text-align:center"><form method="post" class="ibo-setup--enter-itop" action="'.$this->oWizard->GetParameter('application_url').'pages/UI.php">';
|
||||
$sForm .= '<input type="hidden" name="auth_user" value="'.utils::EscapeHtml($this->oWizard->GetParameter('admin_user')).'">';
|
||||
$sForm .= '<input type="hidden" name="auth_pwd" value="'.utils::EscapeHtml($this->oWizard->GetParameter('admin_pwd')).'">';
|
||||
$sForm .= "<button id=\"enter_itop\" class=\"ibo-button ibo-is-regular ibo-is-primary\" type=\"submit\">Enter ".ITOP_APPLICATION."</button></div>";
|
||||
$sForm .= '</form>';
|
||||
|
||||
$sForm = addslashes($sForm);
|
||||
$oPage->add_ready_script("$('#wiz_form').append('$sForm');");
|
||||
// avoid leaving in a dirty state
|
||||
SetupUtils::ExitMaintenanceMode(false);
|
||||
SetupUtils::ExitReadOnlyMode(false);
|
||||
|
||||
if (false === $bHasBackup) {
|
||||
SetupUtils::EraseSetupToken();
|
||||
}
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public function CanMoveBackward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this step of the wizard requires that the configuration file be writable
|
||||
* @return bool True if the wizard will possibly need to modify the configuration at some point
|
||||
*/
|
||||
public function RequiresWritableConfig()
|
||||
{
|
||||
return false; //This step executes once the config was written and secured
|
||||
}
|
||||
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
SetupUtils::EraseSetupToken();
|
||||
// For security reasons: add the extension now so that this action can be used to read *only* .tar.gz files from the disk...
|
||||
$sBackupFile = $aParameters['backup'].'.tar.gz';
|
||||
if (file_exists($sBackupFile)) {
|
||||
// Make sure there is NO output at all before our content, otherwise the document will be corrupted
|
||||
$sPreviousContent = ob_get_clean();
|
||||
$oPage->SetContentType('application/gzip');
|
||||
$oPage->SetContentDisposition('attachment', basename($sBackupFile));
|
||||
$oPage->add(file_get_contents($sBackupFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
171
setup/wizardsteps/WizStepInstall.php
Normal file
171
setup/wizardsteps/WizStepInstall.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
require_once(APPROOT.'setup/sequencers/ApplicationInstallSequencer.php');
|
||||
|
||||
class WizStepInstall extends AbstractWizStepInstall
|
||||
{
|
||||
public const SequencerClass = ApplicationInstallSequencer::class;
|
||||
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Building iTop';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepDone::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for the " Next >> " button
|
||||
* @return string The label for the button
|
||||
*/
|
||||
public function GetNextButtonLabel()
|
||||
{
|
||||
return 'Continue';
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
if ($this->CheckDependencies()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
return new WizardState(WizStepDone::class);
|
||||
}
|
||||
|
||||
protected function AddProgressBar(WebPage $oPage, string $sTitle = 'Progress of the operations')
|
||||
{
|
||||
$oPage->add('<fieldset id="installation_progress"><legend>'.$sTitle.'</legend>');
|
||||
$oPage->add('<div id="progress_content">');
|
||||
$oPage->LinkScriptFromAppRoot('setup/jquery.progression.js');
|
||||
$oPage->add('<p class="center"><span id="setup_msg">Ready to start...</span></p><div style="display:block;margin-left: auto; margin-right:auto;" id="progress">0%</div>');
|
||||
$oPage->add('</div>'); // progress_content
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add("<div class=\"message message-error ibo-is-html-content\" style=\"display:none;\" id=\"setup_error\"></div>");
|
||||
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
|
||||
$aInstallParams = $this->BuildConfig();
|
||||
$this->AddProgressBar($oPage, 'Progress of the installation');
|
||||
|
||||
$sJSONData = json_encode($aInstallParams);
|
||||
$oPage->add('<input type="hidden" id="installer_parameters" value="'.utils::EscapeHtml($sJSONData).'"/>');
|
||||
|
||||
$sAuthentToken = $this->oWizard->GetParameter('authent', '');
|
||||
$oPage->add('<input type="hidden" id="authent_token" value="'.$sAuthentToken.'"/>');
|
||||
if (!$this->CheckDependencies()) {
|
||||
$oPage->error($this->sDependencyIssue);
|
||||
}
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$("#wiz_form").data("installation_status", "not started");
|
||||
ExecuteStep("");
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
$oParameters = new PHPParameters();
|
||||
$sStep = $aParameters['installer_step'];
|
||||
$sJSONParameters = $aParameters['installer_config'];
|
||||
$oParameters->LoadFromHash(json_decode($sJSONParameters, true /* bAssoc */));
|
||||
$oInstaller = new (static::SequencerClass)($oParameters);
|
||||
$aRes = $oInstaller->ExecuteStep($sStep);
|
||||
if (($aRes['status'] != $oInstaller::ERROR) && ($aRes['next-step'] != '')) {
|
||||
// Tell the web page to move the progress bar and to launch the next step
|
||||
$sMessage = addslashes(utils::EscapeHtml($aRes['next-step-label']));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "running");
|
||||
WizardUpdateButtons();
|
||||
$('#setup_msg').html('$sMessage');
|
||||
$('#progress').progression( {Current:{$aRes['percentage-completed']}, Maximum: 100} );
|
||||
|
||||
//$("#percentage").html('{$aRes['percentage-completed']} % completed<br/>{$aRes['next-step-label']}');
|
||||
ExecuteStep('{$aRes['next-step']}');
|
||||
EOF
|
||||
);
|
||||
} elseif ($aRes['status'] != $oInstaller::ERROR) {
|
||||
// Installation complete, move to the next step of the wizard
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "completed");
|
||||
$('#progress').progression( {Current:100, Maximum: 100} );
|
||||
WizardUpdateButtons();
|
||||
$("#btn_next").off("click.install");
|
||||
$("#btn_next").trigger('click');
|
||||
EOF
|
||||
);
|
||||
} else {
|
||||
//Error case
|
||||
$sMessage = addslashes(utils::EscapeHtml($aRes['message']));
|
||||
$sMessage = str_replace("\n", '<br>', $sMessage);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#wiz_form").data("installation_status", "error");
|
||||
$("#progress .progress").addClass('progress-error');
|
||||
WizardUpdateButtons();
|
||||
$('#setup_error').html('$sMessage').show();
|
||||
EOF
|
||||
);
|
||||
$this->AddProgressErrorScript($oPage, $aRes);
|
||||
}
|
||||
}
|
||||
|
||||
protected function AddProgressErrorScript($oPage, $aRes)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return 'return $("#wiz_form").data("installation_status") === "completed";';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveBackward()
|
||||
{
|
||||
return 'var sStatus = $("#wiz_form").data("installation_status"); return ((sStatus === "not started") || (sStatus === "error"));';
|
||||
}
|
||||
|
||||
}
|
||||
184
setup/wizardsteps/WizStepInstallMiscParams.php
Normal file
184
setup/wizardsteps/WizStepInstallMiscParams.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* Miscellaneous Parameters (URL, Sample Data) when installing from scratch
|
||||
*/
|
||||
class WizStepInstallMiscParams extends AbstractWizStepMiscParams
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Miscellaneous Parameters';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepModulesChoice::class];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$this->oWizard->SaveParameter('default_language', '');
|
||||
$this->oWizard->SaveParameter('application_url', '');
|
||||
$this->oWizard->SaveParameter('graphviz_path', '');
|
||||
$this->oWizard->SaveParameter('sample_data', 'yes');
|
||||
return new WizardState(WizStepModulesChoice::class, 'start_install');
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$sDefaultLanguage = $this->oWizard->GetParameter('default_language', $this->oWizard->GetParameter('admin_language'));
|
||||
$sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetDefaultUrlAppRoot(true));
|
||||
$sDefaultGraphvizPath = (strtolower(substr(PHP_OS, 0, 3)) === 'win') ? 'C:\\Program Files\\Graphviz\\bin\\dot.exe' : '/usr/bin/dot';
|
||||
$sGraphvizPath = $this->oWizard->GetParameter('graphviz_path', $sDefaultGraphvizPath);
|
||||
$sSampleData = $this->oWizard->GetParameter('sample_data', 'yes');
|
||||
$oPage->add('<h2>Additional parameters</h2>');
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Default Language</legend>');
|
||||
$oPage->add('<table>');
|
||||
$sSourceDir = APPROOT.'dictionaries/';
|
||||
$aLanguages = SetupUtils::GetAvailableLanguages($sSourceDir);
|
||||
$oPage->add('<tr><td>Default Language: </td><td>');
|
||||
$oPage->add(SetupUtils::GetLanguageSelect($sSourceDir, 'default_language', $sDefaultLanguage));
|
||||
$oPage->add('</td></tr>');
|
||||
$oPage->add('</table>');
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Application URL</legend>');
|
||||
$oPage->add('<table>');
|
||||
$oPage->add('<tr><td>URL: </td><td><input id="application_url" class="ibo-input" name="application_url" type="text" size="35" maxlength="1024" value="'.utils::EscapeHtml($sApplicationURL).'" style="width: 100%;box-sizing: border-box;"><span id="v_application_url"/></td><tr>');
|
||||
$oPage->add('</table>');
|
||||
$oPage->add('<div class="message message-warning">Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.</div>');
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Path to Graphviz\' dot application</legend>');
|
||||
$oPage->add('<table>');
|
||||
$oPage->add('<tr><td>Path: </td><td><input id="graphviz_path" class="ibo-input" name="graphviz_path" type="text" size="35" maxlength="1024" value="'.utils::EscapeHtml($sGraphvizPath).'" style="width: 100%;box-sizing: border-box;"><span id="v_graphviz_path"/></td>');
|
||||
$oPage->add('<td><i class="fas fa-question-circle setup-input--hint--icon" data-tooltip-content="Graphviz is required to display the impact analysis graph (i.e. impacts / depends on)."></i></td><tr>');
|
||||
$oPage->add('</table>');
|
||||
$oPage->add('<span id="graphviz_status"></span>');
|
||||
$oPage->add('</fieldset>');
|
||||
$oPage->add('<fieldset>');
|
||||
$oPage->add('<legend>Sample Data</legend>');
|
||||
$sChecked = ($sSampleData == 'yes') ? 'checked ' : '';
|
||||
$oPage->p('<input id="sample_data_yes" name="sample_data" type="radio" value="yes" '.$sChecked.'><label for="sample_data_yes"> I am installing a <b>demo or test</b> instance, populate the database with some demo data.');
|
||||
$sChecked = ($sSampleData == 'no') ? 'checked ' : '';
|
||||
$oPage->p('<input id="sample_data_no" name="sample_data" type="radio" value="no" '.$sChecked.'><label for="sample_data_no"> I am installing a <b>production</b> instance, create an empty database to start from.');
|
||||
$oPage->add('</fieldset>');
|
||||
$sAuthentToken = $this->oWizard->GetParameter('authent', '');
|
||||
$oPage->add('<input type="hidden" id="authent_token" value="'.$sAuthentToken.'"/>');
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#application_url').on('change keyup', function() { WizardUpdateButtons(); } );
|
||||
$('#graphviz_path').on('change keyup init', function() { WizardUpdateButtons(); WizardAsyncAction('check_graphviz', { graphviz_path: $('#graphviz_path').val(), authent: $('#authent_token').val()}); } ).trigger('init');
|
||||
$('#btn_next').on('click', function() {
|
||||
bRet = true;
|
||||
if ($(this).attr('data-graphviz') != 'ok')
|
||||
{
|
||||
bRet = confirm('The impact analysis will not be displayed properly. Are you sure you want to continue?');
|
||||
}
|
||||
return bRet;
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->AddUseSymlinksFlagOption($oPage);
|
||||
}
|
||||
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
switch ($sCode) {
|
||||
case 'check_graphviz':
|
||||
$sGraphvizPath = $aParameters['graphviz_path'];
|
||||
$aCheck = SetupUtils::CheckGraphviz($sGraphvizPath);
|
||||
|
||||
// N°2214 logging TRACE results
|
||||
$aTraceCheck = CheckResult::FilterCheckResultArray($aCheck, [CheckResult::TRACE]);
|
||||
foreach ($aTraceCheck as $oTraceCheck) {
|
||||
SetupLog::Ok($oTraceCheck->sLabel);
|
||||
}
|
||||
|
||||
$aNonTraceCheck = array_diff($aCheck, $aTraceCheck);
|
||||
foreach ($aNonTraceCheck as $oCheck) {
|
||||
switch ($oCheck->iSeverity) {
|
||||
case CheckResult::INFO:
|
||||
$sStatus = 'ok';
|
||||
$sInfoExplanation = $oCheck->sLabel;
|
||||
$sMessage = json_encode('<div class="message message-valid">'.$sInfoExplanation.'</div>');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
case CheckResult::ERROR:
|
||||
case CheckResult::WARNING:
|
||||
$sStatus = 'ko';
|
||||
$sErrorExplanation = $oCheck->sLabel;
|
||||
$sMessage = json_encode('<div class="message message-error">'.$sErrorExplanation.'</div>');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($oCheck->iSeverity !== CheckResult::TRACE) {
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$("#graphviz_status").html($sMessage);
|
||||
$('#btn_next').attr('data-graphviz', '$sStatus');
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return
|
||||
<<<EOF
|
||||
bRet = ($('#application_url').val() != '');
|
||||
if (!bRet)
|
||||
{
|
||||
$("#v_application_url").html('<img src="../images/validation_error.png" title="This field cannot be empty"/>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#v_application_url").html('');
|
||||
}
|
||||
bGraphviz = ($('#graphviz_path').val() != '');
|
||||
if (!bGraphviz)
|
||||
{
|
||||
// Does not prevent to move forward
|
||||
$("#v_graphviz_path").html('<img src="../images/validation_error.png" title="Impact analysis will not display properly"/>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#v_graphviz_path").html('');
|
||||
}
|
||||
return bRet;
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
218
setup/wizardsteps/WizStepInstallOrUpgrade.php
Normal file
218
setup/wizardsteps/WizStepInstallOrUpgrade.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2013-2026 Combodo SAS
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
|
||||
/**
|
||||
* Second step of the iTop Installation Wizard: Install or Upgrade
|
||||
*/
|
||||
class WizStepInstallOrUpgrade extends WizardStep
|
||||
{
|
||||
public function GetTitle()
|
||||
{
|
||||
return 'Install or Upgrade choice';
|
||||
}
|
||||
|
||||
public function GetPossibleSteps()
|
||||
{
|
||||
return [WizStepDetectedInfo::class, WizStepLicense::class];
|
||||
}
|
||||
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
$sNextStep = '';
|
||||
$sInstallMode = utils::ReadParam('install_mode');
|
||||
|
||||
$this->oWizard->SaveParameter('previous_version_dir', '');
|
||||
$this->oWizard->SaveParameter('db_server', '');
|
||||
$this->oWizard->SaveParameter('db_user', '');
|
||||
$this->oWizard->SaveParameter('db_pwd', '');
|
||||
$this->oWizard->SaveParameter('db_name', '');
|
||||
$this->oWizard->SaveParameter('db_prefix', '');
|
||||
$this->oWizard->SaveParameter('db_tls_enabled', false);
|
||||
$this->oWizard->SaveParameter('db_tls_ca', '');
|
||||
|
||||
if ($sInstallMode == 'install') {
|
||||
$this->oWizard->SetParameter('install_mode', 'install');
|
||||
$sFullSourceDir = SetupUtils::GetLatestDataModelDir();
|
||||
$this->oWizard->SetParameter('source_dir', $sFullSourceDir);
|
||||
$this->oWizard->SetParameter('datamodel_version', SetupUtils::GetDataModelVersion($sFullSourceDir));
|
||||
$sNextStep = WizStepLicense::class;
|
||||
} else {
|
||||
$this->oWizard->SetParameter('install_mode', 'upgrade');
|
||||
$sNextStep = WizStepDetectedInfo::class;
|
||||
|
||||
}
|
||||
return new WizardState($sNextStep);
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage)
|
||||
{
|
||||
$sInstallMode = $this->oWizard->GetParameter('install_mode', '');
|
||||
$sDBServer = $this->oWizard->GetParameter('db_server', '');
|
||||
$sDBUser = $this->oWizard->GetParameter('db_user', '');
|
||||
$sDBPwd = $this->oWizard->GetParameter('db_pwd', '');
|
||||
$sDBName = $this->oWizard->GetParameter('db_name', '');
|
||||
$sDBPrefix = $this->oWizard->GetParameter('db_prefix', '');
|
||||
$sTlsEnabled = $this->oWizard->GetParameter('db_tls_enabled', false);
|
||||
$sTlsCA = $this->oWizard->GetParameter('db_tls_ca', '');
|
||||
$sPreviousVersionDir = '';
|
||||
if ($sInstallMode == '') {
|
||||
$aPreviousInstance = SetupUtils::GetPreviousInstance(APPROOT);
|
||||
if ($aPreviousInstance['found']) {
|
||||
$sInstallMode = 'upgrade';
|
||||
$sDBServer = $aPreviousInstance['db_server'];
|
||||
$sDBUser = $aPreviousInstance['db_user'];
|
||||
$sDBPwd = $aPreviousInstance['db_pwd'];
|
||||
$sDBName = $aPreviousInstance['db_name'];
|
||||
$sDBPrefix = $aPreviousInstance['db_prefix'];
|
||||
$sTlsEnabled = $aPreviousInstance['db_tls_enabled'];
|
||||
$sTlsCA = $aPreviousInstance['db_tls_ca'];
|
||||
$this->oWizard->SaveParameter('graphviz_path', $aPreviousInstance['graphviz_path']);
|
||||
$sPreviousVersionDir = APPROOT;
|
||||
} else {
|
||||
$sInstallMode = 'install';
|
||||
}
|
||||
}
|
||||
$sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', $sPreviousVersionDir);
|
||||
|
||||
$sUpgradeInfoStyle = '';
|
||||
if ($sInstallMode == 'install') {
|
||||
$sUpgradeInfoStyle = ' style="display: none;" ';
|
||||
}
|
||||
$oPage->add('<div class="setup-content-title">What do you want to do?</div>');
|
||||
$sChecked = ($sInstallMode == 'install') ? ' checked ' : '';
|
||||
$oPage->p('<input id="radio_install" type="radio" name="install_mode" value="install" '.$sChecked.'/><label for="radio_install"> Install a new '.ITOP_APPLICATION.'</label>');
|
||||
$sChecked = ($sInstallMode == 'upgrade') ? ' checked ' : '';
|
||||
$sDisabled = (($sInstallMode == 'install') && (empty($sPreviousVersionDir))) ? ' disabled' : '';
|
||||
$oPage->p('<input id="radio_update" type="radio" name="install_mode" value="upgrade" '.$sChecked.$sDisabled.'/><label for="radio_update"> Upgrade an existing '.ITOP_APPLICATION.' instance</label>');
|
||||
|
||||
$sUpgradeDir = utils::HtmlEntities($sPreviousVersionDir);
|
||||
$oPage->add(
|
||||
<<<HTML
|
||||
<div id="upgrade_info"'.$sUpgradeInfoStyle.'>
|
||||
<div class="setup-disk-location--input--container">Location on the disk:<input id="previous_version_dir_display" type="text" value="$sUpgradeDir" class="ibo-input" disabled>
|
||||
<input type="hidden" name="previous_version_dir" value="$sUpgradeDir"></div>
|
||||
HTML
|
||||
);
|
||||
|
||||
SetupUtils::DisplayDBParameters(
|
||||
$oPage,
|
||||
false,
|
||||
$sDBServer,
|
||||
$sDBUser,
|
||||
$sDBPwd,
|
||||
$sDBName,
|
||||
$sDBPrefix,
|
||||
$sTlsEnabled,
|
||||
$sTlsCA,
|
||||
null
|
||||
);
|
||||
|
||||
$sAuthentToken = $this->oWizard->GetParameter('authent', '');
|
||||
$oPage->add('</div>');
|
||||
$oPage->add('<input type="hidden" id="authent_token" value="'.$sAuthentToken.'"/>');
|
||||
//$oPage->add('</fieldset>');
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$("#radio_update").on('change', function() { if (this.checked ) { $('#upgrade_info').show(); WizardUpdateButtons(); } else { $('#upgrade_info').hide(); } });
|
||||
$("#radio_install").on('change', function() { if (this.checked ) { $('#upgrade_info').hide(); WizardUpdateButtons(); } else { $('#upgrade_info').show(); } });
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
public function AsyncAction(WebPage $oPage, $sCode, $aParameters)
|
||||
{
|
||||
switch ($sCode) {
|
||||
case 'check_path':
|
||||
$sPreviousVersionDir = $aParameters['previous_version_dir'];
|
||||
$aPreviousInstance = SetupUtils::GetPreviousInstance($sPreviousVersionDir);
|
||||
if ($aPreviousInstance['found']) {
|
||||
$sDBServer = utils::EscapeHtml($aPreviousInstance['db_server']);
|
||||
$sDBUser = utils::EscapeHtml($aPreviousInstance['db_user']);
|
||||
$sDBPwd = utils::EscapeHtml($aPreviousInstance['db_pwd']);
|
||||
$sDBName = utils::EscapeHtml($aPreviousInstance['db_name']);
|
||||
$sDBPrefix = utils::EscapeHtml($aPreviousInstance['db_prefix']);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#db_server").val('$sDBServer');
|
||||
$("#db_user").val('$sDBUser');
|
||||
$("#db_pwd").val('$sDBPwd');
|
||||
$("#db_name").val('$sDBName');
|
||||
$("#db_prefix").val('$sDBPrefix');
|
||||
$("#db_pwd").trigger('change'); // Forces check of the DB connection
|
||||
EOF
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'check_db':
|
||||
SetupUtils::AsyncCheckDB($oPage, $aParameters);
|
||||
break;
|
||||
|
||||
case 'check_backup':
|
||||
$sDBBackupPath = $aParameters['db_backup_path'];
|
||||
$fFreeSpace = SetupUtils::CheckDiskSpace($sDBBackupPath);
|
||||
if ($fFreeSpace !== false) {
|
||||
$sMessage = utils::EscapeHtml(SetupUtils::HumanReadableSize($fFreeSpace).' free in '.dirname($sDBBackupPath));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#backup_info").html('$sMessage');
|
||||
EOF
|
||||
);
|
||||
} else {
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$("#backup_info").html('');
|
||||
EOF
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the "Next" button should be enabled interactively
|
||||
* @return string A piece of javascript code returning either true or false
|
||||
*/
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
return
|
||||
<<<EOF
|
||||
if ($("#radio_install").prop("checked"))
|
||||
{
|
||||
ValidateField("db_name", false);
|
||||
ValidateField("db_new_name", false);
|
||||
ValidateField("db_prefix", false);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bRet = ($("#wiz_form").data("db_connection") !== "error");
|
||||
bRet = ValidateField("db_name", true) && bRet;
|
||||
bRet = ValidateField("db_new_name", true) && bRet;
|
||||
bRet = ValidateField("db_prefix", true) && bRet;
|
||||
|
||||
return bRet;
|
||||
}
|
||||
EOF
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user