diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php
index a04edb2139..104131175e 100644
--- a/setup/extensionsmap.class.inc.php
+++ b/setup/extensionsmap.class.inc.php
@@ -43,48 +43,53 @@ class iTopExtensionsMap
*
* @param string $sFromEnvironment The environment to scan
* @param array $aExtraDirs extensions dir to scan
+ * @param array $aExtraDirs extensions dir to scan
+ * @param string|null $sAppRootForTests
*
* @return void
*/
- public function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [])
+ public function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null)
{
$this->aExtensions = [];
$this->aExtensionsByCode = [];
$this->aScannedDirs = [];
- $this->ScanDisk($sFromEnvironment);
+
+ $sAppRoot = $sAppRootForTests ?? APPROOT;
+ $this->ScanDisk($sFromEnvironment, $sAppRoot);
$this->aExtraDirs = $aExtraDirs;
- if (is_dir(APPROOT.'extensions')) {
- $this->aExtraDirs [] = APPROOT.'extensions';
+ if (is_dir($sAppRoot.'extensions')) {
+ $this->aExtraDirs [] = $sAppRoot.'extensions';
}
- if (is_dir(APPROOT.'data/'.$sFromEnvironment.'-modules')) {
- $this->aExtraDirs [] = APPROOT.'data/'.$sFromEnvironment.'-modules';
+ if (is_dir($sAppRoot.'data/'.$sFromEnvironment.'-modules')) {
+ $this->aExtraDirs [] = $sAppRoot.'data/'.$sFromEnvironment.'-modules';
}
foreach ($aExtraDirs as $sDir) {
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
}
- $this->CheckDependencies();
+ $this->CheckDependencies($sAppRoot);
}
/**
* Populate the list of available (pseudo)extensions by scanning the disk
* where the iTop files are located
* @param string $sEnvironment
+ * @param string $sAppRoot
* @return void
*/
- protected function ScanDisk($sEnvironment)
+ protected function ScanDisk($sEnvironment, string $sAppRoot)
{
- if (!$this->ReadInstallationWizard(APPROOT.'/datamodels/2.x')) {
+ if (!$this->ReadInstallationWizard($sAppRoot.'/datamodels/2.x')) {
$this->bHasXmlInstallationFile = false;
//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)) {
+ if (!$this->ReadDir($sAppRoot.'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);
+ $this->ReadDir($sAppRoot.'datamodels/1.x', iTopExtension::SOURCE_WIZARD);
}
}
- $this->ReadDir(APPROOT.'extensions', iTopExtension::SOURCE_MANUAL);
- $this->ReadDir(APPROOT.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE);
+ $this->ReadDir($sAppRoot.'extensions', iTopExtension::SOURCE_MANUAL);
+ $this->ReadDir($sAppRoot.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE);
}
/**
@@ -99,25 +104,58 @@ class iTopExtensionsMap
return false;
}
+ $aModuleConfigs = [];
+ $this->ListModuleFiles(basename($sDir), dirname($sDir), $aModuleConfigs);
+
$oXml = new XMLParameters($sDir.'/installation.xml');
foreach ($oXml->Get('steps') as $aStepInfo) {
if (array_key_exists('options', $aStepInfo)) {
- $this->ProcessWizardChoices($aStepInfo['options']);
+ $this->ProcessWizardChoices($aStepInfo['options'], $aModuleConfigs);
}
if (array_key_exists('alternatives', $aStepInfo)) {
- $this->ProcessWizardChoices($aStepInfo['alternatives']);
+ $this->ProcessWizardChoices($aStepInfo['alternatives'], $aModuleConfigs);
}
}
- // TODO Add aModuleVersion from module definition on disk in extensions
+
return true;
}
+ private function ListModuleFiles(string $sRelDir, string $sRootDir, array &$aRes): void
+ {
+ $sDirectory = $sRootDir.'/'.$sRelDir;
+
+ if ($hDir = opendir($sDirectory)) {
+ // This is the correct way to loop over the directory. (according to the documentation)
+ while (($sFile = readdir($hDir)) !== false) {
+ $aMatches = [];
+ if (is_dir($sDirectory.'/'.$sFile)) {
+ if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn') && ($sFile != 'vendor')) {
+ $this->ListModuleFiles($sRelDir.'/'.$sFile, $sRootDir, $aRes);
+ }
+ } elseif (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) {
+ try {
+ $aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sDirectory.'/'.$sFile);
+ $sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
+ list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
+ $aModuleConfig = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
+ $aModuleConfig['module_version'] = $sModuleVersion;
+ $aRes[$sModuleName] = $aModuleConfig;
+ } catch (ModuleFileReaderException $e) {
+ continue;
+ }
+ }
+ }
+ closedir($hDir);
+ }
+ }
+
/**
* Helper to process a "choice" array read from the installation.xml file
* @param array $aChoices
+ * @param array $aModuleConfigs
* @return void
*/
- protected function ProcessWizardChoices($aChoices)
+ protected function ProcessWizardChoices($aChoices, $aModuleConfigs)
{
foreach ($aChoices as $aChoiceInfo) {
if (array_key_exists('extension_code', $aChoiceInfo)) {
@@ -129,13 +167,23 @@ class iTopExtensionsMap
if (array_key_exists('modules', $aChoiceInfo)) {
// Some wizard choices are not associated with any module
$oExtension->aModules = $aChoiceInfo['modules'];
+ foreach ($oExtension->aModules as $sModuleName) {
+ $aCurrentModuleConfig = $aModuleConfigs[$sModuleName] ?? null;
+ if (is_null($aCurrentModuleConfig)) {
+ IssueLog::Info("Installation choice comes with missing module file", null, ["choice" => $oExtension->sCode, 'module' => $sModuleName]);
+ continue;
+ }
+ $oExtension->aModuleVersion[$sModuleName] = $aCurrentModuleConfig['module_version'];
+ unset($aCurrentModuleConfig['module_version']);
+ $oExtension->aModuleInfo[$sModuleName] = $aCurrentModuleConfig;
+ }
}
if (array_key_exists('sub_options', $aChoiceInfo)) {
if (array_key_exists('options', $aChoiceInfo['sub_options'])) {
- $this->ProcessWizardChoices($aChoiceInfo['sub_options']['options']);
+ $this->ProcessWizardChoices($aChoiceInfo['sub_options']['options'], $aModuleConfigs);
}
if (array_key_exists('alternatives', $aChoiceInfo['sub_options'])) {
- $this->ProcessWizardChoices($aChoiceInfo['sub_options']['alternatives']);
+ $this->ProcessWizardChoices($aChoiceInfo['sub_options']['alternatives'], $aModuleConfigs);
}
}
$this->AddExtension($oExtension);
@@ -335,19 +383,19 @@ class iTopExtensionsMap
/**
* Check if some extension contains a module with missing dependencies...
* If so, populate the aMissingDepenencies array
+ * @param string $sAppRoot
* @return void
*/
- protected function CheckDependencies()
+ protected function CheckDependencies(string $sAppRoot)
{
$aSearchDirs = [];
- if (is_dir(APPROOT.'/datamodels/2.x')) {
- $aSearchDirs[] = APPROOT.'/datamodels/2.x';
- } elseif (is_dir(APPROOT.'/datamodels/1.x')) {
- $aSearchDirs[] = APPROOT.'/datamodels/1.x';
+ if (is_dir($sAppRoot.'/datamodels/2.x')) {
+ $aSearchDirs[] = $sAppRoot.'/datamodels/2.x';
+ } elseif (is_dir($sAppRoot.'/datamodels/1.x')) {
+ $aSearchDirs[] = $sAppRoot.'/datamodels/1.x';
}
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
-
try {
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
} catch (MissingDependencyException $e) {
diff --git a/setup/itopextension.class.inc.php b/setup/itopextension.class.inc.php
index 81344f9e7c..b40a64d74a 100644
--- a/setup/itopextension.class.inc.php
+++ b/setup/itopextension.class.inc.php
@@ -141,4 +141,32 @@ class iTopExtension
}
return true;
}
+
+ public function __serialize(): array
+ {
+ return [
+ 'sCode' => $this->sCode,
+ 'sSource' => $this->sSource,
+ 'sVersion' => $this->sVersion,
+ 'aModules' => $this->aModules,
+ 'aModuleVersion' => $this->aModuleVersion,
+ 'aModuleInfo' => $this->aModuleInfo,
+ ];
+ }
+
+ public function __unserialize(array $aData): void
+ {
+ $this->sCode = $aData['sCode'] ?? '';
+ $this->sSource = $aData['sSource'] ?? '';
+ $this->sVersion = $aData['sVersion'] ?? '';
+ $this->aModules = $aData['aModules'] ?? '';
+ $this->aModuleVersion = $aData['aModuleVersion'] ?? '';
+ $this->aModuleInfo = $aData['aModuleInfo'] ?? '';
+ }
+
+ public function __toString(): string
+ {
+ return json_encode($this->__serialize(), JSON_PRETTY_PRINT);
+ }
+
}
diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php
index f8841f7e12..b7f27413a2 100755
--- a/setup/modulediscovery.class.inc.php
+++ b/setup/modulediscovery.class.inc.php
@@ -355,7 +355,6 @@ class ModuleDiscovery
*/
protected static function ListModuleFiles($sRelDir, $sRootDir)
{
- static $iDummyClassIndex = 0;
$sDirectory = $sRootDir.'/'.$sRelDir;
if ($hDir = opendir($sDirectory)) {
diff --git a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php
index 3f505b6fe4..4e56e320e3 100644
--- a/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php
+++ b/tests/php-unit-tests/unitary-tests/setup/ExtensionsMapTest.php
@@ -151,4 +151,31 @@ class ExtensionsMapTest extends ItopTestCase
$this->SetNonPublicProperty($oExtensionsMap, $mapKeyInItopExtensionMap, $aMap);
}
+ public function testiTopExtensionsMapInit()
+ {
+ $oiTopExtensionsMap = new iTopExtensionsMap(sAppRootForTests:__DIR__."/ressources");
+
+ //file_put_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json', json_encode($this->SerializeExtensionMap($oiTopExtensionsMap), JSON_PRETTY_PRINT));
+
+ $sExpected = file_get_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json');
+ $sExpected = str_replace('"sVersion": "ITOP_VERSION"', '"sVersion": "'.ITOP_VERSION.'"', $sExpected);
+ $this->assertEquals($sExpected, json_encode($this->SerializeExtensionMap($oiTopExtensionsMap), JSON_PRETTY_PRINT));
+ }
+
+ public function SerializeExtensionMap(iTopExtensionsMap $oiTopExtensionsMap): array
+ {
+ $aRes = [];
+ foreach ($oiTopExtensionsMap->GetAllExtensions() as $oExtension) {
+ $aRes[] = [
+ 'sCode' => $oExtension->sCode,
+ 'sSource' => $oExtension->sSource,
+ 'sVersion' => $oExtension->sVersion,
+ 'aModules' => $oExtension->aModules,
+ 'aModuleVersion' => $oExtension->aModuleVersion,
+ 'aModuleInfo' => $oExtension->aModuleInfo,
+ ];
+ }
+
+ return $aRes;
+ }
}
diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/all_extensions_from_datamodels.json b/tests/php-unit-tests/unitary-tests/setup/ressources/all_extensions_from_datamodels.json
new file mode 100644
index 0000000000..47d4852b1e
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/setup/ressources/all_extensions_from_datamodels.json
@@ -0,0 +1,316 @@
+[
+ {
+ "sCode": "itop-config-mgmt-core",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-structure",
+ "itop-config-mgmt",
+ "itop-attachments",
+ "itop-profiles-itil",
+ "itop-welcome-itil",
+ "itop-tickets",
+ "itop-files-information",
+ "combodo-db-tools",
+ "itop-core-update",
+ "itop-hub-connector",
+ "itop-oauth-client",
+ "combodo-backoffice-darkmoon-theme",
+ "combodo-backoffice-fullmoon-high-contrast-theme",
+ "combodo-backoffice-fullmoon-protanopia-deuteranopia-theme",
+ "combodo-backoffice-fullmoon-tritanopia-theme",
+ "itop-themes-compat",
+ "combodo-my-account",
+ "combodo-my-account-user-info",
+ "combodo-oauth2-client",
+ "itop-attribute-class-set",
+ "itop-attribute-encrypted-password",
+ "itop-ui-copypaste"
+ ],
+ "aModuleVersion": {
+ "itop-structure": "3.3.0",
+ "itop-config-mgmt": "3.3.0"
+ },
+ "aModuleInfo": {
+ "itop-structure": {
+ "label": "Core iTop Structure",
+ "category": "business",
+ "dependencies": [],
+ "mandatory": true,
+ "visible": false,
+ "installer": "StructureInstaller",
+ "datamodel": [
+ "main.itop-structure.php"
+ ],
+ "data.struct": [],
+ "data.sample": [
+ "data\/data.sample.organizations.xml",
+ "data\/data.sample.locations.xml",
+ "data\/data.sample.persons.xml",
+ "data\/data.sample.teams.xml",
+ "data\/data.sample.contactteam.xml",
+ "data\/data.sample.contacttype.xml"
+ ],
+ "doc.manual_setup": "",
+ "doc.more_information": "",
+ "settings": [],
+ "module_file_path": "\/var\/www\/html\/iTopLegacy\/tests\/php-unit-tests\/unitary-tests\/setup\/ressources\/datamodels\/2.x\/itop-structure\/module.itop-structure.php"
+ },
+ "itop-config-mgmt": {
+ "label": "Configuration Management (CMDB)",
+ "category": "business",
+ "dependencies": [
+ "itop-structure\/2.7.1"
+ ],
+ "mandatory": false,
+ "visible": true,
+ "installer": "ConfigMgmtInstaller",
+ "datamodel": [
+ "model.itop-config-mgmt.php",
+ "main.itop-config-mgmt.php"
+ ],
+ "data.struct": [
+ "data\/en_us.data.itop-brand.xml",
+ "data\/en_us.data.itop-networkdevicetype.xml",
+ "data\/en_us.data.itop-osfamily.xml",
+ "data\/en_us.data.itop-osversion.xml"
+ ],
+ "data.sample": [
+ "data\/data.sample.model.xml",
+ "data\/data.sample.networkdevicetype.xml",
+ "data\/data.sample.servers.xml",
+ "data\/data.sample.nw-devices.xml",
+ "data\/data.sample.software.xml",
+ "data\/data.sample.dbserver.xml",
+ "data\/data.sample.dbschema.xml",
+ "data\/data.sample.webserver.xml",
+ "data\/data.sample.webapp.xml",
+ "data\/data.sample.applications.xml",
+ "data\/data.sample.applicationsolutionci.xml"
+ ],
+ "doc.manual_setup": "",
+ "doc.more_information": "",
+ "settings": [],
+ "module_file_path": "\/var\/www\/html\/iTopLegacy\/tests\/php-unit-tests\/unitary-tests\/setup\/ressources\/datamodels\/2.x\/itop-config-mgmt\/module.itop-config-mgmt.php"
+ }
+ }
+ },
+ {
+ "sCode": "itop-config-mgmt-datacenter",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-datacenter-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-config-mgmt-end-user",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-endusers-devices"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-config-mgmt-storage",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-storage-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-container-mgmt",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-container-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-config-mgmt-virtualization",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-virtualization-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-service-mgmt-enterprise",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-service-mgmt"
+ ],
+ "aModuleVersion": {
+ "itop-service-mgmt": "3.3.0"
+ },
+ "aModuleInfo": {
+ "itop-service-mgmt": {
+ "label": "Service Management",
+ "category": "business",
+ "dependencies": [
+ "itop-tickets\/2.0.0"
+ ],
+ "mandatory": false,
+ "visible": true,
+ "installer": "ServiceMgmtInstaller",
+ "datamodel": [],
+ "data.struct": [],
+ "data.sample": [
+ "data\/data.sample.organizations.xml",
+ "data\/data.sample.contracts.xml",
+ "data\/data.sample.servicefamilies.xml",
+ "data\/data.sample.services.xml",
+ "data\/data.sample.serviceelements.xml",
+ "data\/data.sample.sla.xml",
+ "data\/data.sample.slt.xml",
+ "data\/data.sample.sltsla.xml",
+ "data\/data.sample.contractservice.xml",
+ "data\/data.sample.deliverymodelcontact.xml"
+ ],
+ "doc.manual_setup": "",
+ "doc.more_information": "",
+ "settings": [],
+ "module_file_path": "\/var\/www\/html\/iTopLegacy\/tests\/php-unit-tests\/unitary-tests\/setup\/ressources\/datamodels\/2.x\/itop-service-mgmt\/module.itop-service-mgmt.php"
+ }
+ }
+ },
+ {
+ "sCode": "itop-service-mgmt-service-provider",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-service-mgmt-provider"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-simple-ticket-enhanced-portal",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-portal",
+ "itop-portal-base"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-simple-ticket",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-request-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-itil-user-request",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-request-mgmt-itil"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-itil-incident",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-incident-mgmt-itil"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-itil-enhanced-portal",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-portal",
+ "itop-portal-base"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-itil",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-ticket-mgmt-none",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-change-mgmt-simple",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-change-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-change-mgmt-itil",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-change-mgmt-itil"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-change-mgmt-none",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-kown-error-mgmt",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-faq-light",
+ "itop-knownerror-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ },
+ {
+ "sCode": "itop-problem-mgmt",
+ "sSource": "datamodels",
+ "sVersion": "ITOP_VERSION",
+ "aModules": [
+ "itop-problem-mgmt"
+ ],
+ "aModuleVersion": [],
+ "aModuleInfo": []
+ }
+]
\ No newline at end of file
diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/installation.xml b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/installation.xml
new file mode 100644
index 0000000000..70d3866c17
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/installation.xml
@@ -0,0 +1,243 @@
+
+
Hello $mentioned->first_name$,
+You have been mentioned by $current_contact->friendlyname$ in $this->hyperlink()$
', + ], + 'FR FR' => [ + 'name' => 'Notification aux personnes mentionnées dans les journaux', + 'subject' => 'Vous avez été mentionné dans "$this->friendlyname$"', + 'body' => 'Bonjour $mentioned->first_name$,
+Vous avez été mentionné par $current_contact->friendlyname$ dans $this->hyperlink()$
', + ], + ]; + + // Create action in app. default language and link it to the triggers + $aData = $aActionsData[$sDefaultLanguage]; + $oAction = MetaModel::NewObject(ActionEmail::class); + $oAction->Set('name', $aData['name']); + $oAction->Set('status', 'enabled'); + $oAction->Set('language', $sDefaultLanguage); + $oAction->Set('from', '$current_contact->email$'); + $oAction->Set('to', 'SELECT Person WHERE id = :mentioned->id'); + $oAction->Set('subject', $aData['subject']); + $oAction->Set('body', $aData['body']); + + /** @var \ormLinkSet $oOrm */ + $oOrm = $oAction->Get('trigger_list'); + foreach ($aCreatedTriggerIds as $sTriggerId) { + $oLink = new lnkTriggerAction(); + $oLink->Set('trigger_id', $sTriggerId); + $oOrm->AddItem($oLink); + } + $oAction->Set('trigger_list', $oOrm); + $oAction->DBInsert(); + + SetupLog::Info("|- Created action \"{$oAction->Get('name')}\" and linked it to the previously created triggers."); + } + + if ($iClassesWithLogCount === 0) { + SetupLog::Info("... no trigger/action created as there is no DM class with a log attribute."); + } else { + SetupLog::Info("... default triggers/action successfully created for $iClassesWithLogCount classes."); + } + } + + // Add notifications by newsroom to Persons if mentioned on any log + if (version_compare($sPreviousVersion, '3.2.0', '<')) { + SetupLog::Info("Adding default newsroom actions for Person objects mentions. All existing TriggerOnObjectMention mentioning the Person class will be concerned..."); + + $sPersonClass = Person::class; + $iExistingTriggersCount = 0; + + // Actions data for english and french + $aActionsData = [ + 'EN US' => [ + 'name' => 'Notification to persons mentioned in logs', + 'message' => 'You have been mentioned by $current_contact->friendlyname$', + ], + 'FR FR' => [ + 'name' => 'Notification aux personnes mentionnées dans les journaux', + 'message' => 'Vous avez été mentionné par $current_contact->friendlyname$', + ], + ]; + + // Start by creating the default action no matter what (even if there is no relevant trigger, it will be there for future use) + $aData = $aActionsData[$sDefaultLanguage]; + $oAction = MetaModel::NewObject(ActionNewsroom::class); + $oAction->Set('name', $aData['name']); + $oAction->Set('status', 'enabled'); + $oAction->Set('language', $sDefaultLanguage); + $oAction->Set('priority', 3); // Important priority as a mention is probably more important than a simple notification + $oAction->Set('recipients', 'SELECT Person WHERE id = :mentioned->id'); + $oAction->Set('title', '$this->friendlyname$'); + $oAction->Set('message', $aData['message']); + $oAction->DBWrite(); + + SetupLog::Info("|- Created newsroom action \"{$oAction->Get('name')}\"."); + + // Retrieve all triggers and find those with a mentioned_filter on the Person class + $oTriggersSearch = DBObjectSearch::FromOQL("SELECT ".TriggerOnObjectMention::class); + $oTriggersSearch->AllowAllData(); + + $oTriggersSet = new DBObjectSet($oTriggersSearch); + while ($oTrigger = $oTriggersSet->Fetch()) { + // If mentioned class is not a Person, ignore + $oMentionedFilter = DBSearch::FromOQL($oTrigger->Get('mentioned_filter')); + if (!is_null($oMentionedFilter) && is_a($oMentionedFilter->GetClass(), $sPersonClass, true) === false) { + SetupLog::Info("|- Action \"{$oAction->GetName()}\" NOT LINKED to existing trigger \"{$oTrigger->GetName()}\". (mentioned class \"{$oMentionedFilter->GetClass()}\")"); + continue; + } + + // Link the trigger to the action + /** @var \ormLinkSet $oOrm */ + $oOrm = $oTrigger->Get('action_list'); + $oLink = new lnkTriggerAction(); + $oLink->Set('action_id', $oAction->GetKey()); + $oOrm->AddItem($oLink); + + $oTrigger->Set('action_list', $oOrm); + $oTrigger->DBUpdate(); + $iExistingTriggersCount++; + + SetupLog::Info("|- Linked newsroom action \"{$oAction->GetName()}\" to existing trigger \"{$oTrigger->GetName()}\"."); + } + + if ($iExistingTriggersCount === 0) { + SetupLog::Info("... no action created as there is no existing trigger on mention for the $sPersonClass class."); + } else { + SetupLog::Info("... default newsroom action successfully created and linked to $iExistingTriggersCount triggers on mention."); + } + } + + // Force subscription policy to ForceAtLeastOneChannel for all existing TriggerOnObjectMention + if (version_compare($sPreviousVersion, '3.2.0', '<')) { + SetupLog::Info("Forcing subscription policy to ForceAtLeastOneChannel for all existing TriggerOnObjectMention..."); + + $oTriggersSearch = DBObjectSearch::FromOQL("SELECT ".TriggerOnObjectMention::class); + $oTriggersSearch->AllowAllData(); + + $oTriggersSet = new DBObjectSet($oTriggersSearch); + while ($oTrigger = $oTriggersSet->Fetch()) { + $oTrigger->Set('subscription_policy', \Combodo\iTop\Core\Trigger\Enum\SubscriptionPolicy::ForceAtLeastOneChannel->value); + $oTrigger->DBUpdate(); + + SetupLog::Info("|- Trigger \"{$oTrigger->GetName()}\" updated."); + } + + SetupLog::Info("... all existing TriggerOnObjectMention updated."); + } + + // Add notifications by newsroom (not linked to any trigger yet) for TriggerOnPortalUpdate and TriggerOnReachingState + if (version_compare($sPreviousVersion, '3.2.0', '<')) { + // TriggerOnPortalUpdate + SetupLog::Info("Adding default newsroom action for TriggerOnPortalUpdate (not linked to any trigger yet)..."); + + // - Actions data for english and french + $aActionsData = [ + 'EN US' => [ + 'name' => 'Notification on public log update through the portal', + 'message' => 'New message from $current_contact->friendlyname$', + ], + 'FR FR' => [ + 'name' => 'Notification sur MAJ du journal public via le portail', + 'message' => 'Nouveau message de $current_contact->friendlyname$', + ], + ]; + + // - Create action in app. default language and link it to the triggers + $aData = $aActionsData[$sDefaultLanguage]; + $oAction = MetaModel::NewObject(ActionNewsroom::class); + $oAction->Set('name', $aData['name']); + $oAction->Set('status', 'enabled'); + $oAction->Set('language', $sDefaultLanguage); + $oAction->Set('priority', 4); // Standard priority + $oAction->Set('recipients', 'SELECT Person WHERE id = :this->agent_id'); + $oAction->Set('title', '$this->friendlyname$'); + $oAction->Set('message', $aData['message']); + $oAction->DBWrite(); + + // TriggerOnReachingState + SetupLog::Info("Adding default newsroom action for TriggerOnReachingState (not linked to any trigger yet)..."); + + // Actions data for english and french + $aActionsData = [ + 'EN US' => [ + 'name' => 'Notification to agent when ticket assigned', + 'message' => 'Ticket has been assigned to you', + ], + 'FR FR' => [ + 'name' => 'Notification à l\'agent à l\'assignation du ticket', + 'message' => 'Le ticket vous a été assigné', + ], + ]; + + // Create action in app. default language and link it to the triggers + $aData = $aActionsData[$sDefaultLanguage]; + $oAction = MetaModel::NewObject(ActionNewsroom::class); + $oAction->Set('name', $aData['name']); + $oAction->Set('status', 'enabled'); + $oAction->Set('language', $sDefaultLanguage); + $oAction->Set('priority', 3); // Important priority + $oAction->Set('recipients', 'SELECT Person WHERE id = :this->agent_id'); + $oAction->Set('title', '$this->friendlyname$'); + $oAction->Set('message', $aData['message']); + $oAction->DBWrite(); + } + + //N°824 - Fill object_class in EventNotification from the Triggers target_class + if (version_compare($sPreviousVersion, '3.2.0', '<')) { + SetupLog::Info("Filling object_class in EventNotification from the Triggers target_class"); + $iNbProcessed = 0; + + $sTableToSet = MetaModel::DBGetTable('EventNotification', 'object_class'); + $oAttDefToSet = MetaModel::GetAttributeDef('EventNotification', 'object_class'); + $oAttDefObjectId = MetaModel::GetAttributeDef('EventNotification', 'object_id'); + $oAttDefTriggerId = MetaModel::GetAttributeDef('EventNotification', 'trigger_id'); + + $aColumnsToSets = array_keys($oAttDefToSet->GetSQLColumns()); + $sColumnToSet = $aColumnsToSets[0]; // We know that a string has only one column + $aColumnsTriggerId = array_keys($oAttDefTriggerId->GetSQLColumns()); + $sColumnTriggerId = $aColumnsTriggerId[0]; // We know that a string has only one column + $aColumnsObjectd = array_keys($oAttDefObjectId->GetSQLColumns()); + $sColumnObjectId = $aColumnsObjectd[0]; // We know that a string has only one column + + $oSearch = DBObjectSearch::FromOQL('SELECT TriggerOnObject'); + $oSet = new DBObjectSet($oSearch); + $aTriggerIdToTargetClass = []; + while ($oTrigger = $oSet->Fetch()) { + $aTriggerIdToTargetClass[$oTrigger->GetKey()] = $oTrigger->Get('target_class'); + } + + foreach ($aTriggerIdToTargetClass as $sKey => $sTargetClass) { + + if (MetaModel::HasChildrenClasses($sTargetClass)) { + //in this case, we have toget the name of the final class + $sTableToRead = MetaModel::DBGetTable($sTargetClass, 'finalclass'); + $oAttDefToRead = MetaModel::GetAttributeDef($sTargetClass, 'finalclass'); + $aColumnsToReads = array_keys($oAttDefToRead->GetSQLColumns()); + $sColumnToRead = $aColumnsToReads[0]; // We know that a string has only one column + $sObjectPrimaryKey = MetaModel::DBGetKey($sTargetClass); + + $sRepair = "UPDATE `$sTableToSet` JOIN `$sTableToRead` ON `$sTableToSet`.`$sColumnObjectId` = `$sTableToRead`.`$sObjectPrimaryKey` SET `$sTableToSet`.`$sColumnToSet` = `$sTableToRead`.`$sColumnToRead` WHERE `$sTableToSet`.`$sColumnTriggerId` = '".$sKey."' AND `$sTableToSet`.`$sColumnToSet` = ''"; + } else { + + $sRepair = "UPDATE `$sTableToSet` SET `$sTableToSet`.`$sColumnToSet` = '".$sTargetClass."' WHERE `$sTableToSet`.`$sColumnTriggerId` = '".$sKey."' AND `$sTableToSet`.`$sColumnToSet` = ''"; + } + + SetupLog::Info(" | | Query: ".$sRepair); + CMDBSource::Query($sRepair); + $iNbProcessed += CMDBSource::AffectedRows(); + } + SetupLog::Info("| | ".$iNbProcessed." EventNotification processed."); + } + } + } +}