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 @@ + + + + + Configuration Management options + The options below allow you to configure the type of elements that are to be managed inside iTop.]]> + /images/icons/icons8-apps-tab.svg + + + itop-config-mgmt-core + Configuration Management Core + All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc. + + 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 + + true + + + itop-config-mgmt-datacenter + Data Center Devices + Manage Data Center devices such as Racks, Enclosures, PDUs, etc. + + itop-datacenter-mgmt + + true + + + itop-config-mgmt-end-user + End-User Devices + Manage devices related to end-users: PCs, Phones, Tablets, etc. + + itop-endusers-devices + + true + + + itop-config-mgmt-storage + Storage Devices + Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc. + + itop-storage-mgmt + + true + + + itop-config-mgmt-virtualization + Virtualization + Manage Hypervisors, Virtual Machines and Farms. + + itop-virtualization-mgmt + + true + + + + itop-container-mgmt + Containerization + + + itop-container-mgmt + + false + + + + + + + + Service Management options + Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.]]> + /images/icons/icons8-services.svg + + + itop-service-mgmt-enterprise + Service Management for Enterprises + Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend. + + itop-service-mgmt + + true + + + itop-service-mgmt-service-provider + Service Management for Service Providers + Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices. + + itop-service-mgmt-provider + + + + + + Tickets Management options + Select the type of tickets you want to use in order to respond to user requests and incidents.]]> + /images/icons/icons8-discussion-forum.svg + + + itop-ticket-mgmt-simple-ticket + Simple Ticket Management + Select this option to use one single type of tickets for all kind of requests. + + itop-request-mgmt + + true + + + + itop-ticket-mgmt-simple-ticket-enhanced-portal + Customer Portal + + + itop-portal + itop-portal-base + + true + + + + + + itop-ticket-mgmt-itil + ITIL Compliant Tickets Management + Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields + + + + itop-ticket-mgmt-itil-user-request + User Request Management + Manage User Request tickets in iTop + + itop-request-mgmt-itil + + + + itop-ticket-mgmt-itil-incident + Incident Management + Manage Incidents tickets in iTop + + itop-incident-mgmt-itil + + + + itop-ticket-mgmt-itil-enhanced-portal + Customer Portal + + + itop-portal + itop-portal-base + + true + + + + + + itop-ticket-mgmt-none + No Tickets Management + Don't manage incidents or user requests in iTop + + + + + + + Change Management options + Select the type of tickets you want to use in order to manage changes to the IT infrastructure.]]> + /images/icons/icons8-change.svg + + + itop-change-mgmt-simple + Simple Change Management + Select this option to use one type of ticket for all kind of changes. + + itop-change-mgmt + + true + + + itop-change-mgmt-itil + ITIL Change Management + Select this option to use Normal/Routine/Emergency change tickets. + + itop-change-mgmt-itil + + + + itop-change-mgmt-none + No Change Management + Don't manage changes in iTop + + + + + + + Additional ITIL tickets + Pick from the list below the additional ITIL processes that are to be implemented in iTop.]]> + /images/icons/icons8-important-book.svg + + + + itop-kown-error-mgmt + Known Errors Management and FAQ + Select this option to track "Known Errors" and FAQs in iTop. + + itop-faq-light + itop-knownerror-mgmt + + + + itop-problem-mgmt + Problem Management + Select this option to track "Problems" in iTop. + + itop-problem-mgmt + + + + + + diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php new file mode 100644 index 0000000000..12bea24bd1 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php @@ -0,0 +1,107 @@ + 'Configuration Management (CMDB)', + 'category' => 'business', + + // Setup + // + 'dependencies' => [ + 'itop-structure/2.7.1', + ], + 'mandatory' => false, + 'visible' => true, + 'installer' => 'ConfigMgmtInstaller', + + // Components + // + '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', + ], + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => [ + ], + ] +); + +if (!class_exists('ConfigMgmtInstaller')) { + // Module installation handler + // + class ConfigMgmtInstaller extends ModuleInstallerAPI + { + public static function BeforeWritingConfig(Config $oConfiguration) + { + // If you want to override/force some configuration values, do it here + return $oConfiguration; + } + + /** + * Handler called before creating or upgrading the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function BeforeDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + if (strlen($sPreviousVersion) > 0) { + // If you want to migrate data from one format to another, do it here + self::RenameEnumValueInDB('Software', 'type', 'DBserver', 'DBServer'); + self::RenameEnumValueInDB('Software', 'type', 'Webserver', 'WebServer'); + self::RenameEnumValueInDB('Model', 'type', 'SANswitch', 'SANSwitch'); + self::RenameEnumValueInDB('Model', 'type', 'IpPhone', 'IPPhone'); + self::RenameEnumValueInDB('Model', 'type', 'Telephone', 'Phone'); + self::RenameClassInDB('DBserver', 'DBServer'); + self::RenameClassInDB('OSfamily', 'OSFamily'); + self::RenameClassInDB('OSversion', 'OSVersion'); + self::RenameClassInDB('Webserver', 'WebServer'); + self::RenameClassInDB('OSpatch', 'OSPatch'); + self::RenameClassInDB('lnkFunctionalCIToOSpatch', 'lnkFunctionalCIToOSPatch'); + self::RenameClassInDB('OsLicence', 'OSLicence'); + self::RenameClassInDB('IOSversion', 'IOSVersion'); + self::RenameClassInDB('IPinterface', 'IPInterface'); + } + } + + /** + * Handler called after the creation/update of the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + } + } +} diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php new file mode 100644 index 0000000000..98103251e5 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php @@ -0,0 +1,89 @@ + 'Service Management', + 'category' => 'business', + + // Setup + // + 'dependencies' => [ + 'itop-tickets/2.0.0', + ], + 'mandatory' => false, + 'visible' => true, + 'installer' => 'ServiceMgmtInstaller', + + // Components + // + 'datamodel' => [ + ], + 'data.struct' => [ + //'data.struct.itop-service-mgmt.xml', + ], + '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.coveragewindows.xml', + 'data/data.sample.contractservice.xml', + // 'data/data.sample.deliverymodel.xml', + 'data/data.sample.deliverymodelcontact.xml', + ], + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => [ + ], + ] +); + +if (!class_exists('ServiceMgmtInstaller')) { + // Module installation handler + // + class ServiceMgmtInstaller extends ModuleInstallerAPI + { + public static function BeforeWritingConfig(Config $oConfiguration) + { + // If you want to override/force some configuration values, do it here + return $oConfiguration; + } + + /** + * Handler called before creating or upgrading the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function BeforeDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + if (strlen($sPreviousVersion) > 0) { + self::RenameEnumValueInDB('SLT', 'request_type', 'servicerequest', 'service_request'); + } + } + + /** + * Handler called after the creation/update of the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + } + } +} diff --git a/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-structure/module.itop-structure.php b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-structure/module.itop-structure.php new file mode 100644 index 0000000000..f1da9aeafe --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/ressources/datamodels/2.x/itop-structure/module.itop-structure.php @@ -0,0 +1,449 @@ + 'Core iTop Structure', + 'category' => 'business', + + // Setup + // + 'dependencies' => [ + ], + 'mandatory' => true, + 'visible' => false, + 'installer' => 'StructureInstaller', + + // Components + // + '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', + ], + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => [ + ], + ] +); + +if (!class_exists('StructureInstaller')) { + // Module installation handler + // + class StructureInstaller extends ModuleInstallerAPI + { + public static function BeforeWritingConfig(Config $oConfiguration) + { + // If you want to override/force some configuration values, do it here + return $oConfiguration; + } + + /** + * Handler called before creating or upgrading the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function BeforeDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + if (strlen($sPreviousVersion) > 0) { + // Search for existing ActionEmail where the language attribute was defined on its child + if (version_compare($sPreviousVersion, '3.2.0', '<')) { + SetupLog::Info("| Migrate ActionEmail language attribute values to its parent."); + $sTableToRead = MetaModel::DBGetTable('ActionEmail'); + $sTableToSet = MetaModel::DBGetTable('ActionNotification'); + self::MoveColumnInDB($sTableToRead, 'language', $sTableToSet, 'language', true); + SetupLog::Info("| ActionEmail migration done."); + } + // If you want to migrate data from one format to another, do it here + self::RenameEnumValueInDB('Software', 'type', 'DBserver', 'DBServer'); + self::RenameEnumValueInDB('Software', 'type', 'Webserver', 'WebServer'); + self::RenameEnumValueInDB('Model', 'type', 'SANswitch', 'SANSwitch'); + self::RenameEnumValueInDB('Model', 'type', 'IpPhone', 'IPPhone'); + self::RenameEnumValueInDB('Model', 'type', 'Telephone', 'Phone'); + self::RenameClassInDB('DBserver', 'DBServer'); + self::RenameClassInDB('OSfamily', 'OSFamily'); + self::RenameClassInDB('OSversion', 'OSVersion'); + self::RenameClassInDB('Webserver', 'WebServer'); + self::RenameClassInDB('OSpatch', 'OSPatch'); + self::RenameClassInDB('lnkFunctionalCIToOSpatch', 'lnkFunctionalCIToOSPatch'); + self::RenameClassInDB('OsLicence', 'OSLicence'); + self::RenameClassInDB('IOSversion', 'IOSVersion'); + self::RenameClassInDB('IPinterface', 'IPInterface'); + } + } + + /** + * Handler called after the creation/update of the database schema + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string PRevious version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + */ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + // Default language will be used for actions + // Note: There is a issue when upgrading, default language cannot be retrieved from the passed configuration, we have to read it from the disk + if (utils::IsNullOrEmptyString($sPreviousVersion)) { + // Fresh install + $sDefaultLanguage = $oConfiguration->GetDefaultLanguage(); + } else { + // Upgrade + $sDefaultLanguage = utils::GetConfig(true)->GetDefaultLanguage(); + } + // Fallback language on english if not french + $sDefaultLanguage = $sDefaultLanguage === 'FR FR' ? 'FR FR' : 'EN US'; + SetupLog::Info("Default app language used for actions: $sDefaultLanguage"); + + // Search for existing TriggerOnObject where the Trigger string complement is empty and fed it with target_class field value + if (version_compare($sPreviousVersion, '3.1.0', '<')) { + SetupLog::Info("| Feed computed field triggering_class on existing Triggers."); + + $sTableToSet = MetaModel::DBGetTable('Trigger', 'complement'); + $sTableToRead = MetaModel::DBGetTable('TriggerOnObject', 'target_class'); + $oAttDefToSet = MetaModel::GetAttributeDef('Trigger', 'complement'); + $oAttDefToRead = MetaModel::GetAttributeDef('TriggerOnObject', 'target_class'); + + $aColumnsToSets = array_keys($oAttDefToSet->GetSQLColumns()); + $sColumnToSet = $aColumnsToSets[0]; // We know that a string has only one column + $aColumnsToReads = array_keys($oAttDefToRead->GetSQLColumns()); + $sColumnToRead = $aColumnsToReads[0]; // We know that a string has only one column + + $sRepair = "UPDATE $sTableToSet JOIN $sTableToRead ON $sTableToSet.id = $sTableToRead.id SET $sTableToSet.$sColumnToSet = CONCAT('class restriction: ',$sTableToRead.$sColumnToRead) WHERE $sTableToSet.$sColumnToSet = ''"; + SetupLog::Debug(" | | Query: ".$sRepair); + CMDBSource::Query($sRepair); + $iNbProcessed = CMDBSource::AffectedRows(); + SetupLog::Info("| | ".$iNbProcessed." triggers processed."); + } + + // Add notifications by email to Persons if mentioned on any log + if (version_compare($sPreviousVersion, '3.0.0', '<')) { + SetupLog::Info("Adding default triggers/action for Person objects mentions. All DM classes with at least 1 log attribute will be concerned..."); + + $sPersonClass = 'Person'; + $sPersonStateAttCode = MetaModel::GetStateAttributeCode($sPersonClass); + $sPersonOwnerOrgAttCode = UserRightsProfile::GetOwnerOrganizationAttCode($sPersonClass); + + $iClassesWithLogCount = 0; + $aCreatedTriggerIds = []; + foreach (MetaModel::EnumRootClasses() as $sRootClass) { + foreach (MetaModel::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL, true) as $sClass) { + $aLogAttCodes = MetaModel::GetAttributesList($sClass, ['AttributeCaseLog']); + + // Skip class with no log attribute + if (count($aLogAttCodes) === 0) { + continue; + } + + // Prepare the mentioned_filter OQL + $oPersonSearch = DBObjectSearch::FromOQL("SELECT $sPersonClass"); + + // - Add status condition if attribute present + if (empty($sPersonStateAttCode) === false) { + $oPersonSearch->AddConditionExpression(new BinaryExpression( + new FieldExpression($sPersonStateAttCode), + '=', + new ScalarExpression('active') + )); + } + + // - Check if the classes have a silo attribute so we can use them in the mentioned_filter + if (empty($sPersonOwnerOrgAttCode) === false) { + // Filter on current contact org. + $oCurrentContactExpr = new BinaryExpression( + new FieldExpression($sPersonOwnerOrgAttCode), + '=', + new VariableExpression("current_contact->org_id") + ); + + // Filter on class owner org. if any + $sClassOwnerOrgAttCode = UserRightsProfile::GetOwnerOrganizationAttCode($sClass); + $oOwnerOrgExpr = empty($sClassOwnerOrgAttCode) ? null : new BinaryExpression( + new FieldExpression($sPersonOwnerOrgAttCode), + '=', + new VariableExpression("this->$sClassOwnerOrgAttCode") + ); + + // No owner org, simple condition + if ($oOwnerOrgExpr === null) { + $oPersonSearch->AddConditionExpression($oCurrentContactExpr); + } + // Owner org, condition is either from owner org or current contact's + else { + $oOrExpr = new BinaryExpression($oCurrentContactExpr, 'OR', $oOwnerOrgExpr); + $oPersonSearch->AddConditionExpression($oOrExpr); + } + } + + // Build the trigger + $oTrigger = MetaModel::NewObject(TriggerOnObjectMention::class); + $oTrigger->Set('description', 'Person mentioned on '.$sClass); + $oTrigger->Set('target_class', $sClass); + $oTrigger->Set('mentioned_filter', $oPersonSearch->ToOQL()); + $oTrigger->DBInsert(); + + SetupLog::Info("|- Created trigger \"{$oTrigger->Get('description')}\" for class $sClass."); + $aCreatedTriggerIds[] = $oTrigger->GetKey(); + $iClassesWithLogCount++; + // Note: We break because we only have to create one trigger/action for the class hierarchy as it will be for all their log attributes + break; + } + } + + // Build the corresponding action and link it to the triggers + if (count($aCreatedTriggerIds) > 0) { + // Actions data for english and french + $aActionsData = [ + 'EN US' => [ + 'name' => 'Notification to persons mentioned in logs', + 'subject' => 'You have been mentioned in "$this->friendlyname$"', + 'body' => '

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."); + } + } + } +}