'Core iTop Structure', 'category' => 'business', // Setup // 'dependencies' => [ ], 'mandatory' => true, 'visible' => false, 'installer' => 'StructureInstaller', // Components // 'datamodel' => [ 'main.itop-structure.php', ], 'data.struct' => [ ], 'data.sample' => [ 'data.sample.organizations.xml', 'data.sample.locations.xml', 'data.sample.persons.xml', 'data.sample.teams.xml', 'data.sample.contactteam.xml', '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."); } } } }