Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Vladishev <aleksander.vladishev@zabbix.com>2022-05-24 22:58:34 +0300
committerAlexander Vladishev <aleksander.vladishev@zabbix.com>2022-05-24 22:58:34 +0300
commit8ab28dcf078f9530b56f38e412fc49c05a5edc52 (patch)
tree5bf72422987f49719fc877b2f0c4564fa27b4d03
parented570e4843186cf4900524f3a0f87bcfbf2896b8 (diff)
parentd3b516db8f924d879f77f7eb8943b22f237013f9 (diff)
.......... [ZBX-20859] updated to the latest master; no conflicts
-rw-r--r--.github/workflows/sonarcloud.yml2
-rw-r--r--ChangeLog1
-rw-r--r--ChangeLog.d/bugfix/ZBX-206131
-rw-r--r--ChangeLog.d/bugfix/ZBX-208141
-rw-r--r--ChangeLog.d/bugfix/ZBX-208981
-rw-r--r--templates/app/ftp_service/template_app_ftp_service.yaml2
-rw-r--r--templates/db/postgresql_agent2/README.md2
-rw-r--r--ui/app/controllers/CControllerAuthenticationEdit.php2
-rw-r--r--ui/app/controllers/CControllerHostCreate.php17
-rw-r--r--ui/app/views/administration.authentication.edit.php25
-rw-r--r--ui/app/views/js/administration.authentication.edit.js.php21
-rw-r--r--ui/include/classes/api/services/CDiscoveryRule.php319
-rw-r--r--ui/include/classes/api/services/CHostBase.php218
-rw-r--r--ui/include/classes/api/services/CHostGeneral.php22
-rw-r--r--ui/include/classes/api/services/CTemplate.php32
-rw-r--r--ui/include/classes/api/services/CTrigger.php643
-rw-r--r--ui/include/classes/api/services/CTriggerGeneral.php1418
-rw-r--r--ui/include/classes/api/services/CTriggerPrototype.php568
-rw-r--r--ui/include/forms.inc.php9
-rw-r--r--ui/include/triggers.inc.php475
-rw-r--r--ui/include/views/configuration.triggers.edit.php6
-rw-r--r--ui/include/views/monitoring.sysmap.edit.php6
-rw-r--r--ui/templates.php13
-rw-r--r--ui/tests/api_json/testTemplate.php2
-rw-r--r--ui/tests/api_json/testTriggerValidation.php6
-rw-r--r--ui/tests/include/web/CElement.php3
-rw-r--r--ui/tests/include/web/CElementCollection.php26
-rw-r--r--ui/tests/include/web/elements/CTableElement.php2
-rw-r--r--ui/tests/selenium/testTriggerDependencies.php75
-rw-r--r--ui/triggers.php2
30 files changed, 2316 insertions, 1604 deletions
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index ecd95aa8ed7..dc1e9086d74 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -15,7 +15,7 @@ jobs:
env:
SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SERVER_URL: "https://sonarcloud.io"
- BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
+ BUILD_WRAPPER_OUT_DIR: $HOME/.sonar/build_wrapper_output_directory # Directory where build-wrapper output will be placed
steps:
- uses: actions/checkout@v2
with:
diff --git a/ChangeLog b/ChangeLog
index 84c13da75db..f8583af7920 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14,6 +14,7 @@ A.F....... [ZBXNEXT-7476] disabled php 8 strict type validation (ashubin)
A.F....... [ZBXNEXT-7491] fixed PHP 8.1 runtime warnings with PostgreSQL backend (Sasha)
Bug fixes:
+..F....... [ZBX-20242] fixed inability to change LDAP bind password (agriscenko)
...G...... [ZBX-20911] added process state check in PROC_NUM() for Oracle Solaris (Andris)
..F....... [ZBX-20752] fixed saved properties of classic graphs being lost when reopening graph configuration (agriscenko)
..F....... [ZBX-19381] fixed resolving of macros in item key parameters when testing items in web interface (Sasha)
diff --git a/ChangeLog.d/bugfix/ZBX-20613 b/ChangeLog.d/bugfix/ZBX-20613
new file mode 100644
index 00000000000..3fded15f049
--- /dev/null
+++ b/ChangeLog.d/bugfix/ZBX-20613
@@ -0,0 +1 @@
+A.F....... [ZBX-20613] returned the ability to edit the dependencies of inherited triggers; improved validation upon unlinking templates with triggers that have dependencies; improved performance of trigger dependency functionality; prevented the ability to fully replace trigger dependencies of inherited triggers if the new dependency was added to parent trigger; supplemented validation of trigger dependencies on templates to prevent linkage dead-ends (vmaksimovs)
diff --git a/ChangeLog.d/bugfix/ZBX-20814 b/ChangeLog.d/bugfix/ZBX-20814
new file mode 100644
index 00000000000..5fb3ccd4389
--- /dev/null
+++ b/ChangeLog.d/bugfix/ZBX-20814
@@ -0,0 +1 @@
+..F....... [ZBX-20814] fixed Map->Sharing user group shares table not showing saved user groups (esekace)
diff --git a/ChangeLog.d/bugfix/ZBX-20898 b/ChangeLog.d/bugfix/ZBX-20898
new file mode 100644
index 00000000000..d630940c0b3
--- /dev/null
+++ b/ChangeLog.d/bugfix/ZBX-20898
@@ -0,0 +1 @@
+.........T [ZBX-20898] fixed duplicate UUID in official template (abakaldin)
diff --git a/templates/app/ftp_service/template_app_ftp_service.yaml b/templates/app/ftp_service/template_app_ftp_service.yaml
index 8e3d1e5223b..1fb9f225237 100644
--- a/templates/app/ftp_service/template_app_ftp_service.yaml
+++ b/templates/app/ftp_service/template_app_ftp_service.yaml
@@ -32,7 +32,7 @@ zabbix_export:
value: network
triggers:
-
- uuid: b299d73cebcd430c8bfc54cf9b84e853
+ uuid: aee485c125f94e37ac97c3ce1a654757
expression: 'max(/FTP Service/net.tcp.service[ftp],#3)=0'
name: 'FTP service is down on {HOST.NAME}'
priority: AVERAGE
diff --git a/templates/db/postgresql_agent2/README.md b/templates/db/postgresql_agent2/README.md
index 88727664850..10b94efc64c 100644
--- a/templates/db/postgresql_agent2/README.md
+++ b/templates/db/postgresql_agent2/README.md
@@ -19,7 +19,7 @@ This template was tested on:
1\. Create PostgreSQL user for monitoring (`<password>` at your discretion):
```bash
-CREATE USER 'zbx_monitor' WITH PASSWORD '<PASSWORD>' INHERIT;
+CREATE USER zbx_monitor WITH PASSWORD '<PASSWORD>' INHERIT;
GRANT EXECUTE ON FUNCTION pg_catalog.pg_ls_dir(text) TO zbx_monitor;
GRANT EXECUTE ON FUNCTION pg_catalog.pg_stat_file(text) TO zbx_monitor;
GRANT EXECUTE ON FUNCTION pg_catalog.pg_ls_waldir() TO zbx_monitor;
diff --git a/ui/app/controllers/CControllerAuthenticationEdit.php b/ui/app/controllers/CControllerAuthenticationEdit.php
index e4b6d51d1c5..c7807cec589 100644
--- a/ui/app/controllers/CControllerAuthenticationEdit.php
+++ b/ui/app/controllers/CControllerAuthenticationEdit.php
@@ -92,8 +92,6 @@ class CControllerAuthenticationEdit extends CController {
$openssl_status = (new CFrontendSetup())->checkPhpOpenSsl();
$data = [
- 'action_submit' => 'authentication.update',
- 'action_passw_change' => 'authentication.edit',
'ldap_error' => ($ldap_status['result'] == CFrontendSetup::CHECK_OK) ? '' : $ldap_status['error'],
'ldap_test_password' => '',
'ldap_test_user' => CWebUser::$data['username'],
diff --git a/ui/app/controllers/CControllerHostCreate.php b/ui/app/controllers/CControllerHostCreate.php
index 89565b41cd9..c7f835922d0 100644
--- a/ui/app/controllers/CControllerHostCreate.php
+++ b/ui/app/controllers/CControllerHostCreate.php
@@ -106,11 +106,11 @@ class CControllerHostCreate extends CControllerHostUpdateGeneral {
$host = $this->extendHostCloneEncryption($host, $src_hostid);
}
- $hostids = API::Host()->create($host);
+ $result = API::Host()->create($host);
- if ($hostids === false
- || !$this->createValueMaps($hostids['hostids'][0])
- || ($full_clone && !$this->copyFromCloneSourceHost($src_hostid, $hostids['hostids'][0]))) {
+ if ($result === false
+ || !$this->createValueMaps($result['hostids'][0])
+ || ($full_clone && !$this->copyFromCloneSourceHost($src_hostid, $result['hostids'][0]))) {
throw new Exception();
}
@@ -210,14 +210,7 @@ class CControllerHostCreate extends CControllerHostUpdateGeneral {
}
// Copy triggers.
- $db_triggers = API::Trigger()->get([
- 'output' => ['triggerid'],
- 'hostids' => $src_hostid,
- 'inherited' => false,
- 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
- ]);
-
- if ($db_triggers && !copyTriggersToHosts(array_column($db_triggers, 'triggerid'), $hostid, $src_hostid)) {
+ if (!copyTriggersToHosts([$hostid], $src_hostid)) {
return false;
}
diff --git a/ui/app/views/administration.authentication.edit.php b/ui/app/views/administration.authentication.edit.php
index 1f6d1385d7a..0d18f9075fb 100644
--- a/ui/app/views/administration.authentication.edit.php
+++ b/ui/app/views/administration.authentication.edit.php
@@ -136,19 +136,23 @@ $http_tab = (new CFormList('list_http'))
// LDAP configuration fields.
if ($data['change_bind_password']) {
- $password_box = [
- new CVar('change_bind_password', 1),
- (new CPassBox('ldap_bind_password', $data['ldap_bind_password']))
- ->setEnabled($data['ldap_enabled'])
- ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH)
- ];
+ $password_box = (new CPassBox('ldap_bind_password', $data['ldap_bind_password'],
+ DB::getFieldLength('config', 'ldap_bind_password'))
+ )
+ ->setEnabled($data['ldap_enabled'])
+ ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH)
+ ;
}
else {
$password_box = [
- new CVar('action_passw_change', $data['action_passw_change']),
- (new CButton('change_bind_password', _('Change password')))
+ (new CSimpleButton(_('Change password')))
+ ->setId('bind-password-btn')
->setEnabled($data['ldap_enabled'])
- ->addClass(ZBX_STYLE_BTN_GREY)
+ ->addClass(ZBX_STYLE_BTN_GREY),
+ (new CPassBox('ldap_bind_password', '', DB::getFieldLength('config', 'ldap_bind_password')))
+ ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH)
+ ->addClass(ZBX_STYLE_DISPLAY_NONE)
+ ->setEnabled(false)
];
}
@@ -322,7 +326,8 @@ $saml_tab = (new CFormList('list_saml'))
(new CWidget())
->setTitle(_('Authentication'))
->addItem((new CForm())
- ->addVar('action', $data['action_submit'])
+ ->addVar('action', 'authentication.update')
+ ->addVar('change_bind_password', $data['change_bind_password'])
->addVar('db_authentication_type', $data['db_authentication_type'])
->setId('authentication-form')
->setName('form_auth')
diff --git a/ui/app/views/js/administration.authentication.edit.js.php b/ui/app/views/js/administration.authentication.edit.js.php
index 1baac8907f4..1b8d80a1b84 100644
--- a/ui/app/views/js/administration.authentication.edit.js.php
+++ b/ui/app/views/js/administration.authentication.edit.js.php
@@ -51,7 +51,7 @@
fields = $form.find('[name^=http_]');
}
else if ($(this).is('#ldap_configured')) {
- fields = $form.find('[name^=ldap_],button[name=change_bind_password]');
+ fields = $form.find('[name^=ldap_],#bind-password-btn');
}
else {
fields = $form.find('[name^=saml_]');
@@ -62,15 +62,22 @@
.prop('disabled', !this.checked);
});
- $form.find('button#change_bind_password').click(function() {
- $form.find('[name=action]')
- .val($form.find('[name=action_passw_change]').val());
-
- submitFormWithParam('form_auth', 'change_bind_password', '1');
- });
+ $form.find('#bind-password-btn').on('click', showPasswordField);
$form.find('[name=ldap_test]').click(function() {
warn = false;
});
});
+
+ function showPasswordField(e) {
+ const form_field = e.target.parentNode;
+ const password_field = form_field.querySelector('[name="ldap_bind_password"]');
+
+ password_field.disabled = false;
+ password_field.classList.remove('<?= ZBX_STYLE_DISPLAY_NONE ?>');
+
+ form_field.removeChild(e.target);
+
+ document.getElementById('change_bind_password').value = 1;
+ }
</script>
diff --git a/ui/include/classes/api/services/CDiscoveryRule.php b/ui/include/classes/api/services/CDiscoveryRule.php
index 7d906a2ebc8..fe91fd2520e 100644
--- a/ui/include/classes/api/services/CDiscoveryRule.php
+++ b/ui/include/classes/api/services/CDiscoveryRule.php
@@ -714,196 +714,225 @@ class CDiscoveryRule extends CItemGeneral {
/**
* Copies all of the triggers from the source discovery to the target discovery rule.
*
- * @throws APIException if trigger saving fails
- *
- * @param array $srcDiscovery The source discovery rule to copy from
- * @param array $srcHost The host the source discovery belongs to
- * @param array $dstHost The host the target discovery belongs to
+ * @param array $src_discovery The source discovery rule to copy from.
+ * @param array $src_host The host the source discovery belongs to.
+ * @param string $src_host['hostid']
+ * @param string $src_host['host']
+ * @param array $dst_host The host the target discovery belongs to.
+ * @param string $dst_host['hostid']
+ * @param string $dst_host['host']
*
* @return array
+ *
+ * @throws APIException
*/
- protected function copyTriggerPrototypes(array $srcDiscovery, array $srcHost, array $dstHost) {
- $srcTriggers = API::TriggerPrototype()->get([
- 'discoveryids' => $srcDiscovery['itemid'],
- 'output' => ['triggerid', 'expression', 'description', 'url', 'status', 'priority', 'comments',
- 'templateid', 'type', 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag',
- 'opdata', 'discover', 'event_name'
+ protected function copyTriggerPrototypes(array $src_discovery, array $src_host, array $dst_host): array {
+ $src_triggers = API::TriggerPrototype()->get([
+ 'output' => ['triggerid', 'expression', 'description', 'url', 'status', 'priority', 'comments', 'type',
+ 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag', 'opdata', 'discover',
+ 'event_name'
],
- 'selectHosts' => API_OUTPUT_EXTEND,
'selectItems' => ['itemid', 'type'],
- 'selectDiscoveryRule' => API_OUTPUT_EXTEND,
- 'selectFunctions' => API_OUTPUT_EXTEND,
- 'selectDependencies' => ['triggerid'],
'selectTags' => ['tag', 'value'],
- 'preservekeys' => true
+ 'selectDependencies' => ['triggerid'],
+ 'discoveryids' => $src_discovery['itemid']
]);
- foreach ($srcTriggers as $id => $trigger) {
+ $dst_triggers = [];
+
+ foreach ($src_triggers as $i => $src_trigger) {
// Skip trigger prototypes with web items and remove them from source.
- if (httpItemExists($trigger['items'])) {
- unset($srcTriggers[$id]);
+ if (httpItemExists($src_trigger['items'])) {
+ unset($src_triggers[$i]);
+ }
+ else {
+ $dst_triggers[] = array_intersect_key($src_trigger, array_flip(['expression', 'description', 'url',
+ 'status', 'priority', 'comments','type', 'recovery_mode', 'recovery_expression', 'correlation_mode',
+ 'correlation_tag', 'opdata', 'discover', 'event_name', 'tags'
+ ]));
}
}
- if (!$srcTriggers) {
+ if (!$dst_triggers) {
return [];
}
- /*
- * Copy the remaining trigger prototypes to a new source. These will contain IDs and original dependencies.
- * The dependencies from $srcTriggers will be removed.
- */
- $trigger_prototypes = $srcTriggers;
-
- // Contains original trigger prototype dependency IDs.
- $dep_triggerids = [];
-
- /*
- * Collect dependency trigger IDs and remove them from source. Otherwise these IDs do not pass
- * validation, since they don't belong to destination discovery rule.
- */
- $add_dependencies = false;
-
- foreach ($srcTriggers as $id => &$trigger) {
- if ($trigger['dependencies']) {
- foreach ($trigger['dependencies'] as $dep_trigger) {
- $dep_triggerids[] = $dep_trigger['triggerid'];
- }
- $add_dependencies = true;
- }
- unset($trigger['dependencies']);
- }
- unset($trigger);
+ $src_triggers = array_values($src_triggers);
- // Save new trigger prototypes and without dependencies for now.
- $dstTriggers = $srcTriggers;
- $dstTriggers = CMacrosResolverHelper::resolveTriggerExpressions($dstTriggers,
+ $dst_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dst_triggers,
['sources' => ['expression', 'recovery_expression']]
);
- foreach ($dstTriggers as $id => &$trigger) {
- unset($trigger['triggerid'], $trigger['templateid'], $trigger['hosts'], $trigger['functions'],
- $trigger['items'], $trigger['discoveryRule']
- );
- // Update the destination expressions.
- $trigger['expression'] = triggerExpressionReplaceHost($trigger['expression'], $srcHost['host'],
- $dstHost['host']
+ foreach ($dst_triggers as &$trigger) {
+ $trigger['expression'] = triggerExpressionReplaceHost($trigger['expression'], $src_host['host'],
+ $dst_host['host']
);
+
if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) {
$trigger['recovery_expression'] = triggerExpressionReplaceHost($trigger['recovery_expression'],
- $srcHost['host'], $dstHost['host']
+ $src_host['host'], $dst_host['host']
);
}
}
unset($trigger);
- $result = API::TriggerPrototype()->create($dstTriggers);
+ $result = API::TriggerPrototype()->create($dst_triggers);
+
if (!$result) {
self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone trigger prototypes.'));
}
- // Process dependencies, if at least one trigger prototype has a dependency.
- if ($add_dependencies) {
- $trigger_prototypeids = array_keys($trigger_prototypes);
+ $dst_triggerids = $result['triggerids'];
+ $src_trigger_indexes = array_flip(array_column($src_triggers, 'triggerid'));
- foreach ($result['triggerids'] as $i => $triggerid) {
- $new_trigger_prototypes[$trigger_prototypeids[$i]] = [
- 'new_triggerid' => $triggerid,
- 'new_hostid' => $dstHost['hostid'],
- 'new_host' => $dstHost['host'],
- 'src_hostid' => $srcHost['hostid'],
- 'src_host' => $srcHost['host']
- ];
+ $dst_triggers = [];
+
+ /*
+ * A check that the trigger-up belongs to the source host needs to be performed on copying the dependencies
+ * on triggers.
+ * If it does, we need to check that the triggers with the same description and expression exist on the
+ * destination host.
+ * If not, we need to check if the dependencies from destination triggers to these triggers are valid.
+ */
+ $src_triggerids_up = [];
+
+ foreach ($dst_triggerids as $i => $dst_triggerid) {
+ if (!$src_triggers[$i]['dependencies']) {
+ unset($dst_triggerids[$i]);
+ continue;
}
- /*
- * Search for original dependent triggers and expressions to find corresponding triggers on destination host
- * with same expression.
- */
- $dep_triggers = API::Trigger()->get([
- 'output' => ['description', 'expression'],
- 'selectHosts' => ['hostid'],
- 'triggerids' => $dep_triggerids,
- 'preservekeys' => true
- ]);
- $dep_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dep_triggers);
+ $dst_triggers[$dst_triggerid] = ['triggerid' => $dst_triggerid];
- // Map dependencies to the new trigger IDs and save.
- foreach ($trigger_prototypes as &$trigger_prototype) {
- // Get corresponding created trigger prototype ID.
- $new_trigger_prototype = $new_trigger_prototypes[$trigger_prototype['triggerid']];
+ foreach ($src_triggers[$i]['dependencies'] as $i2 => $src_trigger_up) {
+ if (array_key_exists($src_trigger_up['triggerid'], $src_trigger_indexes)) {
+ // Add dependency on the trigger prototype of the same LLD rule.
+ $dst_triggers[$dst_triggerid]['dependencies'][] =
+ ['triggerid' => $result['triggerids'][$src_trigger_indexes[$src_trigger_up['triggerid']]]];
- if ($trigger_prototype['dependencies']) {
- foreach ($trigger_prototype['dependencies'] as &$dependency) {
- $dep_triggerid = $dependency['triggerid'];
+ unset($src_triggers[$i]['dependencies'][$i2]);
+ }
+ else {
+ $src_triggerids_up[$src_trigger_up['triggerid']] = true;
+ }
+ }
- /*
- * We have added a dependent trigger prototype and we know corresponding trigger prototype ID
- * for newly created trigger prototype.
- */
- if (array_key_exists($dependency['triggerid'], $new_trigger_prototypes)) {
- /*
- * Dependency is within same host according to $srcHostId parameter or dep trigger has
- * single host.
- */
- if ($new_trigger_prototype['src_hostid'] ==
- $new_trigger_prototypes[$dep_triggerid]['src_hostid']) {
- $dependency['triggerid'] = $new_trigger_prototypes[$dep_triggerid]['new_triggerid'];
- }
- }
- elseif (in_array(['hostid' => $new_trigger_prototype['src_hostid']],
- $dep_triggers[$dep_triggerid]['hosts'])) {
- // Get all possible $depTrigger matching triggers by description.
- $target_triggers = API::Trigger()->get([
- 'output' => ['hosts', 'triggerid', 'expression'],
- 'hostids' => $new_trigger_prototype['new_hostid'],
- 'filter' => ['description' => $dep_triggers[$dep_triggerid]['description']],
- 'preservekeys' => true
- ]);
- $target_triggers = CMacrosResolverHelper::resolveTriggerExpressions($target_triggers);
-
- // Compare exploded expressions for exact match.
- $expr1 = $dep_triggers[$dep_triggerid]['expression'];
- $dependency['triggerid'] = null;
-
- foreach ($target_triggers as $target_trigger) {
- $expr2 = triggerExpressionReplaceHost($target_trigger['expression'],
- $new_trigger_prototype['new_host'],
- $new_trigger_prototype['src_host']
- );
+ if (!$src_triggers[$i]['dependencies']) {
+ unset($dst_triggerids[$i]);
+ }
+ }
- if ($expr2 === $expr1) {
- // Matching trigger has been found.
- $dependency['triggerid'] = $target_trigger['triggerid'];
- break;
- }
- }
+ if ($src_triggerids_up) {
+ $src_host_triggers_up = DBfetchArrayAssoc(DBselect(
+ 'SELECT DISTINCT t.triggerid,t.description,t.expression,t.recovery_expression'.
+ ' FROM triggers t,functions f,items i'.
+ ' WHERE t.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionId('t.triggerid', array_keys($src_triggerids_up)).
+ ' AND '.dbConditionId('i.hostid', [$src_host['hostid']])
+ ), 'triggerid');
- // If matching trigger was not found, raise exception.
- if ($dependency['triggerid'] === null) {
- $expr2 = triggerExpressionReplaceHost($dep_triggers[$dep_triggerid]['expression'],
- $new_trigger_prototype['src_host'],
- $new_trigger_prototype['new_host']
- );
- self::exception(ZBX_API_ERROR_PARAMETERS, _s(
- 'Cannot add dependency from trigger "%1$s:%2$s" to non existing trigger "%3$s:%4$s".',
- $trigger_prototype['description'],
- $trigger_prototype['expression'],
- $dep_triggers[$dep_triggerid]['description'],
- $expr2
- ));
- }
+ $src_host_triggers_up = CMacrosResolverHelper::resolveTriggerExpressions($src_host_triggers_up,
+ ['sources' => ['expression', 'recovery_expression']]
+ );
+
+ $src_host_dependencies = [];
+ $other_host_dependencies = [];
+
+ foreach ($dst_triggerids as $i => $dst_triggerid) {
+ $src_trigger = $src_triggers[$i];
+
+ foreach ($src_trigger['dependencies'] as $src_trigger_up) {
+ if (array_key_exists($src_trigger_up['triggerid'], $src_host_triggers_up)) {
+ $src_host_dependencies[$src_trigger_up['triggerid']][$src_trigger['triggerid']] = true;
+ }
+ else {
+ // Add dependency on the trigger of the other templates or hosts.
+ $dst_triggers[$dst_triggerid]['dependencies'][] = ['triggerid' => $src_trigger_up['triggerid']];
+ $other_host_dependencies[$src_trigger_up['triggerid']][$dst_triggerid] = true;
+ }
+ }
+ }
+
+ if ($src_host_dependencies) {
+ $dst_host_triggers = DBfetchArrayAssoc(DBselect(
+ 'SELECT DISTINCT t.triggerid,t.description,t.expression,t.recovery_expression'.
+ ' FROM items i,functions f,triggers t'.
+ ' WHERE i.itemid=f.itemid'.
+ ' AND f.triggerid=t.triggerid'.
+ ' AND '.dbConditionId('i.hostid', [$dst_host['hostid']]).
+ ' AND '.dbConditionString('t.description',
+ array_unique(array_column($src_host_triggers_up, 'description'))
+ )
+ ), 'triggerid');
+
+ $dst_host_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dst_host_triggers);
+
+ $dst_host_triggerids = [];
+
+ foreach ($dst_host_triggers as $i => $trigger) {
+ $expression = triggerExpressionReplaceHost($trigger['expression'], $dst_host['host'],
+ $src_host['host']
+ );
+ $recovery_expression = $trigger['recovery_expression'];
+
+ if ($recovery_expression !== '') {
+ $recovery_expression = triggerExpressionReplaceHost($trigger['recovery_expression'],
+ $dst_host['host'], $src_host['host']
+ );
+ }
+
+ $dst_host_triggerids[$trigger['description']][$expression][$recovery_expression] =
+ $trigger['triggerid'];
+ }
+
+ foreach ($src_host_triggers_up as $src_trigger_up) {
+ $description = $src_trigger_up['description'];
+ $expression = $src_trigger_up['expression'];
+ $recovery_expression = $src_trigger_up['recovery_expression'];
+
+ if (array_key_exists($description, $dst_host_triggerids)
+ && array_key_exists($expression, $dst_host_triggerids[$description])
+ && array_key_exists($recovery_expression, $dst_host_triggerids[$description][$expression])) {
+ $dst_triggerid_up = $dst_host_triggerids[$description][$expression][$recovery_expression];
+
+ foreach ($src_host_dependencies[$src_trigger_up['triggerid']] as $src_triggerid => $foo) {
+ $dst_triggerid = $dst_triggerids[$src_trigger_indexes[$src_triggerid]];
+
+ $dst_triggers[$dst_triggerid]['dependencies'][] = ['triggerid' => $dst_triggerid_up];
}
}
- unset($dependency);
+ else {
+ $src_triggerid = key($src_host_dependencies[$src_trigger_up['triggerid']]);
+ $src_trigger = $src_triggers[$src_trigger_indexes[$src_triggerid]];
+
+ $hosts = DB::select('hosts', [
+ 'output' => ['status'],
+ 'hostids' => $dst_host['hostid']
+ ]);
- $trigger_prototype['triggerid'] = $new_trigger_prototype['new_triggerid'];
+ $error = ($hosts[0]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Trigger prototype "%1$s" cannot depend on the non-existent trigger "%2$s" on the template "%3$s".')
+ : _('Trigger prototype "%1$s" cannot depend on the non-existent trigger "%2$s" on the host "%3$s".');
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $src_trigger['description'],
+ $src_trigger_up['description'], $dst_host['host']
+ ));
+ }
}
}
- unset($trigger_prototype);
- // If adding a dependency fails, the exception will be raised in TriggerPrototype API.
- API::TriggerPrototype()->addDependencies($trigger_prototypes);
+ if ($other_host_dependencies) {
+ $trigger_hosts = CTriggerGeneral::getTriggerHosts($other_host_dependencies);
+
+ CTriggerGeneral::checkDependenciesOfHostTriggers($other_host_dependencies, $trigger_hosts);
+ CTriggerGeneral::checkDependenciesOfTemplateTriggers($other_host_dependencies, $trigger_hosts);
+ }
+ }
+
+ if ($dst_triggers) {
+ $dst_triggers = array_values($dst_triggers);
+ CTriggerGeneral::updateDependencies($dst_triggers);
}
return $result;
diff --git a/ui/include/classes/api/services/CHostBase.php b/ui/include/classes/api/services/CHostBase.php
index 8d05cc8225c..14c0f2bf742 100644
--- a/ui/include/classes/api/services/CHostBase.php
+++ b/ui/include/classes/api/services/CHostBase.php
@@ -128,6 +128,7 @@ abstract class CHostBase extends CApiService {
$del_links = [];
$check_double_linkage = false;
$del_templates = [];
+ $del_links_clear = [];
foreach ($hosts as $host) {
if (array_key_exists('templates', $host)) {
@@ -187,13 +188,22 @@ abstract class CHostBase extends CApiService {
}
}
}
+
+ if (($this instanceof CHost || $this instanceof CTemplate) && array_key_exists('templates_clear', $host)) {
+ foreach ($host['templates_clear'] as $template) {
+ $del_links_clear[$template['templateid']][$host[$id_field_name]] = true;
+ }
+ }
}
if ($del_templates) {
- $this->checkTriggerDependenciesOfUpdTemplates($del_templates);
$this->checkTriggerExpressionsOfDelTemplates($del_templates);
}
+ if ($del_links_clear) {
+ $this->checkTriggerDependenciesOfHostTriggers($del_links_clear);
+ }
+
if ($ins_templates) {
if ($this instanceof CTemplate && $db_hosts !== null) {
self::checkCircularLinkageNew($ins_templates, $del_links);
@@ -209,32 +219,21 @@ abstract class CHostBase extends CApiService {
}
/**
- * Check whether triggers of existing templates have not dependencies on triggers of unlinked templates on target
- * hosts or templates.
+ * Check whether all templates of triggers of unlinking templates are unlinked from target hosts or templates.
*
* @param array $del_templates
* @param array $del_templates[<templateid>][<hostid>] Array of IDs of existing templates.
*
- * @throws APIException
+ * @throws APIException if not linked template is found.
*/
- protected function checkTriggerDependenciesOfUpdTemplates(array $del_templates): void {
- $all_upd_templates = [];
-
- foreach ($del_templates as $hosts) {
- foreach ($hosts as $hostid => $upd_templateids) {
- $all_upd_templates += array_flip($upd_templateids);
- }
- }
-
+ protected function checkTriggerExpressionsOfDelTemplates(array $del_templates): void {
$result = DBselect(
- 'SELECT DISTINCT i.hostid AS del_templateid,td.triggerid_down,ii.hostid'.
- ' FROM items i,functions f,trigger_depends td,functions ff,items ii'.
+ 'SELECT DISTINCT i.hostid AS del_templateid,f.triggerid,ii.hostid'.
+ ' FROM items i,functions f,functions ff,items ii'.
' WHERE i.itemid=f.itemid'.
- ' AND f.triggerid=td.triggerid_up'.
- ' AND td.triggerid_down=ff.triggerid'.
+ ' AND f.triggerid=ff.triggerid'.
' AND ff.itemid=ii.itemid'.
- ' AND '.dbConditionInt('i.hostid', array_keys($del_templates)).
- ' AND '.dbConditionInt('ii.hostid', array_keys($all_upd_templates))
+ ' AND '.dbConditionInt('i.hostid', array_keys($del_templates))
);
while ($row = DBfetch($result)) {
@@ -248,12 +247,12 @@ abstract class CHostBase extends CApiService {
$triggers = DB::select('triggers', [
'output' => ['description'],
- 'triggerids' => $row['triggerid_down']
+ 'triggerids' => $row['triggerid']
]);
$error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
- ? _('Cannot unlink template "%1$s" without template "%2$s" from template "%3$s" due to dependency of trigger "%4$s".')
- : _('Cannot unlink template "%1$s" without template "%2$s" from host "%3$s" due to dependency of trigger "%4$s".');
+ ? _('Cannot unlink template "%1$s" without template "%2$s" from template "%3$s" due to expression of trigger "%4$s".')
+ : _('Cannot unlink template "%1$s" without template "%2$s" from host "%3$s" due to expression of trigger "%4$s".');
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$row['del_templateid']]['host'],
$objects[$row['hostid']]['host'], $objects[$hostid]['host'], $triggers[0]['description']
@@ -264,47 +263,165 @@ abstract class CHostBase extends CApiService {
}
/**
- * Check whether all templates of triggers of unlinking templates are unlinked from target hosts or templates.
+ * Check whether the triggers of the target hosts or templates don't have a dependencies on the triggers of the
+ * unlinking (with cleaning) templates.
*
- * @param array $del_templates
- * @param array $del_templates[<templateid>][<hostid>] Array of IDs of existing templates.
+ * @param array $del_links_clear[<templateid>][<hostid>]
*
- * @throws APIException if not linked template is found.
+ * @throws APIException
*/
- protected function checkTriggerExpressionsOfDelTemplates(array $del_templates): void {
+ protected function checkTriggerDependenciesOfHostTriggers(array $del_links_clear): void {
+ $del_host_templates = [];
+
+ foreach ($del_links_clear as $templateid => $hosts) {
+ foreach ($hosts as $hostid => $foo) {
+ $del_host_templates[$hostid][] = $templateid;
+ }
+ }
+
$result = DBselect(
- 'SELECT DISTINCT i.hostid AS del_templateid,f.triggerid,ii.hostid'.
- ' FROM items i,functions f,functions ff,items ii'.
+ 'SELECT DISTINCT i.hostid AS templateid,t.triggerid,ii.hostid'.
+ ' FROM items i,functions f,triggers t,functions ff,items ii'.
' WHERE i.itemid=f.itemid'.
- ' AND f.triggerid=ff.triggerid'.
+ ' AND f.triggerid=t.templateid'.
+ ' AND t.triggerid=ff.triggerid'.
' AND ff.itemid=ii.itemid'.
- ' AND '.dbConditionInt('i.hostid', array_keys($del_templates))
+ ' AND '.dbConditionId('i.hostid', array_keys($del_links_clear)).
+ ' AND '.dbConditionId('ii.hostid', array_keys($del_host_templates))
);
+ $trigger_links = [];
+
while ($row = DBfetch($result)) {
- foreach ($del_templates[$row['del_templateid']] as $hostid => $upd_templateids) {
- if (in_array($row['hostid'], $upd_templateids)) {
+ if (in_array($row['templateid'], $del_host_templates[$row['hostid']])) {
+ $trigger_links[$row['triggerid']][$row['hostid']] = $row['templateid'];
+ }
+ }
+
+ if (!$trigger_links) {
+ return;
+ }
+
+ $result = DBselect(
+ 'SELECT DISTINCT td.triggerid_up,td.triggerid_down,i.hostid'.
+ ' FROM trigger_depends td,functions f,items i'.
+ ' WHERE td.triggerid_down=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionId('td.triggerid_up', array_keys($trigger_links)).
+ ' AND '.dbConditionId('td.triggerid_down', array_keys($trigger_links), true).
+ ' AND '.dbConditionId('i.hostid', array_keys($del_host_templates))
+ );
+
+ while ($row = DBfetch($result)) {
+ foreach ($trigger_links[$row['triggerid_up']] as $hostid => $templateid) {
+ if (bccomp($row['hostid'], $hostid) == 0) {
$objects = DB::select('hosts', [
'output' => ['host', 'status'],
- 'hostids' => [$row['del_templateid'], $row['hostid'], $hostid],
+ 'hostids' => [$templateid, $hostid],
'preservekeys' => true
]);
$triggers = DB::select('triggers', [
'output' => ['description'],
- 'triggerids' => $row['triggerid']
+ 'triggerids' => $row['triggerid_down']
]);
$error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
- ? _('Cannot unlink template "%1$s" without template "%2$s" from template "%3$s" due to expression of trigger "%4$s".')
- : _('Cannot unlink template "%1$s" without template "%2$s" from host "%3$s" due to expression of trigger "%4$s".');
+ ? _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s".')
+ : _('Cannot unlink template "%1$s" from host "%2$s" due to dependency of trigger "%3$s".');
- self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$row['del_templateid']]['host'],
- $objects[$row['hostid']]['host'], $objects[$hostid]['host'], $triggers[0]['description']
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$templateid]['host'],
+ $objects[$hostid]['host'], $triggers[0]['description']
));
}
}
}
+
+ if ($this instanceof CTemplate) {
+ $trigger_hosts = [];
+
+ foreach ($trigger_links as $triggerid => $hostids) {
+ $trigger_hosts[$triggerid] = array_keys($hostids);
+ }
+
+ $trigger_map = [];
+
+ while (true) {
+ $result = DBselect(
+ 'SELECT DISTINCT t.templateid,t.triggerid,i.hostid'.
+ ' FROM triggers t,functions f,items i'.
+ ' WHERE t.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionInt('t.templateid', array_keys($trigger_hosts))
+ );
+
+ $_trigger_hosts = [];
+ $hostids = [];
+
+ while ($row = DBfetch($result)) {
+ foreach ($trigger_hosts[$row['templateid']] as $hostid) {
+ if (array_key_exists($row['hostid'], $del_host_templates)
+ && in_array($hostid, $del_host_templates[$row['hostid']])) {
+ continue;
+ }
+
+ $trigger_map[$row['triggerid']] = $row['templateid'];
+ $_trigger_hosts[$row['triggerid']][] = $row['hostid'];
+ $hostids[$row['hostid']] = true;
+ }
+ }
+
+ if (!$_trigger_hosts) {
+ break;
+ }
+
+ $trigger_hosts = $_trigger_hosts;
+
+ $result = DBselect(
+ 'SELECT DISTINCT td.triggerid_up,td.triggerid_down,i.hostid'.
+ ' FROM trigger_depends td,functions f,items i'.
+ ' WHERE td.triggerid_down=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionId('td.triggerid_up', array_keys($trigger_hosts)).
+ ' AND '.dbConditionId('td.triggerid_down', array_keys($trigger_hosts), true).
+ ' AND '.dbConditionId('i.hostid', array_keys($hostids))
+ );
+
+ while ($row = DBfetch($result)) {
+ foreach ($trigger_hosts[$row['triggerid_up']] as $hostid) {
+ if (bccomp($row['hostid'], $hostid) == 0) {
+ $triggerid = $row['triggerid_up'];
+
+ do {
+ $triggerid = $trigger_map[$triggerid];
+ } while (array_key_exists($triggerid, $trigger_map));
+
+ $from_hostid = key($trigger_links[$triggerid]);
+ $templateid = $trigger_links[$triggerid][$from_hostid];
+
+ $objects = DB::select('hosts', [
+ 'output' => ['host', 'status'],
+ 'hostids' => [$templateid, $from_hostid, $hostid],
+ 'preservekeys' => true
+ ]);
+
+ $triggers = DB::select('triggers', [
+ 'output' => ['description'],
+ 'triggerids' => $row['triggerid_down']
+ ]);
+
+ $error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s" on template "%4$s".')
+ : _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s" on host "%4$s".');
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$templateid]['host'],
+ $objects[$from_hostid]['host'], $triggers[0]['description'], $objects[$hostid]['host']
+ ));
+ }
+ }
+ }
+ }
+ }
}
/**
@@ -367,18 +484,22 @@ abstract class CHostBase extends CApiService {
]);
foreach ($templates as $template) {
+ $description = '"'.$template['host'].'"';
+
if (bccomp($template['hostid'], $templateid) == 0) {
- $template_name = '"'.$template['host'].'"';
+ $template_name = $description;
}
else {
- $links_path[$template['hostid']] = '"'.$template['host'].'"';
+ $links_path[$template['hostid']] = $description;
}
}
- $circular_linkage = $template_name.' -> '.implode(' -> ', $links_path).' -> '.$template_name;
+ $circular_linkage = (bccomp($templateid, $hostid) == 0)
+ ? $template_name.' -> '.$template_name
+ : $template_name.' -> '.implode(' -> ', $links_path).' -> '.$template_name;
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
- 'Cannot link template "%1$s" to template "%2$s" because circular linkage (%3$s) will occurs.',
+ 'Cannot link template "%1$s" to template "%2$s", because a circular linkage (%3$s) would occur.',
$templates[$templateid]['host'], $templates[$hostid]['host'], $circular_linkage
));
}
@@ -412,7 +533,10 @@ abstract class CHostBase extends CApiService {
if ($hostid_links) {
$links_path[$hostid] = true;
- return self::circularLinkageExists($links, $templateid, $hostid_links, $links_path);
+
+ if (self::circularLinkageExists($links, $templateid, $hostid_links, $links_path)) {
+ return true;
+ }
}
}
}
@@ -511,13 +635,13 @@ abstract class CHostBase extends CApiService {
]);
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
- $error = _('Cannot link template "%1$s" to template "%2$s" because its parent template "%3$s" will be linked twice.');
+ $error = _('Cannot link template "%1$s" to template "%2$s", because its parent template "%3$s" would be linked twice.');
}
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
- $error = _('Cannot link template "%1$s" to host prototype "%2$s" because its parent template "%3$s" will be linked twice.');
+ $error = _('Cannot link template "%1$s" to host prototype "%2$s", because its parent template "%3$s" would be linked twice.');
}
else {
- $error = _('Cannot link template "%1$s" to host "%2$s" because its parent template "%3$s" will be linked twice.');
+ $error = _('Cannot link template "%1$s" to host "%2$s", because its parent template "%3$s" would be linked twice.');
}
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$ins_templateid]['host'],
diff --git a/ui/include/classes/api/services/CHostGeneral.php b/ui/include/classes/api/services/CHostGeneral.php
index 8961d3b508a..1f48b1e1952 100644
--- a/ui/include/classes/api/services/CHostGeneral.php
+++ b/ui/include/classes/api/services/CHostGeneral.php
@@ -158,11 +158,13 @@ abstract class CHostGeneral extends CHostBase {
* @param array $templateids
* @param array $db_hosts
*/
- protected function massCheckTemplatesLinks(string $method, array $templateids, array $db_hosts): void {
+ protected function massCheckTemplatesLinks(string $method, array $templateids, array $db_hosts,
+ array $templateids_clear = []): void {
$ins_templates = [];
$del_links = [];
$check_double_linkage = false;
$del_templates = [];
+ $del_links_clear = [];
foreach ($db_hosts as $hostid => $db_host) {
$db_templateids = array_column($db_host['templates'], 'templateid');
@@ -211,14 +213,21 @@ abstract class CHostGeneral extends CHostBase {
if ($upd_templateids) {
$del_templates[$db_templateid][$hostid] = $upd_templateids;
}
+
+ if (array_key_exists($db_templateid, $templateids_clear)) {
+ $del_links_clear[$db_templateid][$hostid] = true;
+ }
}
}
if ($del_templates) {
- $this->checkTriggerDependenciesOfUpdTemplates($del_templates);
$this->checkTriggerExpressionsOfDelTemplates($del_templates);
}
+ if ($del_links_clear) {
+ $this->checkTriggerDependenciesOfHostTriggers($del_links_clear);
+ }
+
if ($ins_templates) {
if ($this instanceof CTemplate) {
self::checkCircularLinkageNew($ins_templates, $del_links);
@@ -760,11 +769,7 @@ abstract class CHostGeneral extends CHostBase {
API::Graph()->syncTemplates($link_request);
- API::Trigger()->syncTemplateDependencies($link_request);
-
- if ($ruleids) {
- API::TriggerPrototype()->syncTemplateDependencies($link_request);
- }
+ CTriggerGeneral::syncTemplateDependencies($link_request['templateids'], $link_request['hostids']);
}
/**
@@ -998,8 +1003,7 @@ abstract class CHostGeneral extends CHostBase {
}
foreach ($link_requests as $link_request){
- API::Trigger()->syncTemplateDependencies($link_request);
- API::TriggerPrototype()->syncTemplateDependencies($link_request);
+ CTriggerGeneral::syncTemplateDependencies($link_request['templateids'], $link_request['hostids']);
}
return $hosts_linkage_inserts;
diff --git a/ui/include/classes/api/services/CTemplate.php b/ui/include/classes/api/services/CTemplate.php
index 2a5117aad1a..e08aeac8c7c 100644
--- a/ui/include/classes/api/services/CTemplate.php
+++ b/ui/include/classes/api/services/CTemplate.php
@@ -718,18 +718,36 @@ class CTemplate extends CHostGeneral {
' FROM hosts_templates ht,hosts_templates htt'.
' WHERE ht.hostid=htt.hostid'.
' AND ht.templateid!=htt.templateid'.
- ' AND '.dbConditionInt('ht.templateid', $templateids).
- ' AND '.dbConditionInt('htt.templateid', $templateids, true)
+ ' AND '.dbConditionId('ht.templateid', $templateids).
+ ' AND '.dbConditionId('htt.templateid', $templateids, true)
);
while ($row = DBfetch($result)) {
$del_templates[$row['del_templateid']][$row['hostid']][] = $row['templateid'];
}
+ $del_links_clear = [];
+ $options = [
+ 'output' => ['templateid', 'hostid'],
+ 'filter' => [
+ 'templateid' => $templateids
+ ]
+ ];
+ $result = DBselect(DB::makeSql('hosts_templates', $options));
+
+ while ($row = DBfetch($result)) {
+ if (!in_array($row['hostid'], $templateids)) {
+ $del_links_clear[$row['templateid']][$row['hostid']] = true;
+ }
+ }
+
if ($del_templates) {
- $this->checkTriggerDependenciesOfUpdTemplates($del_templates);
$this->checkTriggerExpressionsOfDelTemplates($del_templates);
}
+
+ if ($del_links_clear) {
+ $this->checkTriggerDependenciesOfHostTriggers($del_links_clear);
+ }
}
/**
@@ -1057,10 +1075,10 @@ class CTemplate extends CHostGeneral {
}
if (array_key_exists('templates_link', $data)) {
- $this->massCheckTemplatesLinks('massupdate', $templateids_link, $db_templates);
+ $this->massCheckTemplatesLinks('massupdate', $templateids_link, $db_templates, $templateids_clear);
}
else {
- $this->massCheckTemplatesLinks('massremove', $templateids_clear, $db_templates);
+ $this->massCheckTemplatesLinks('massremove', $templateids_clear, $db_templates, $templateids_clear);
}
}
}
@@ -1144,7 +1162,9 @@ class CTemplate extends CHostGeneral {
$this->massAddAffectedObjects('templates', $templateids, $db_templates);
- $this->massCheckTemplatesLinks('massremove', $templateids, $db_templates);
+ $this->massCheckTemplatesLinks('massremove', $templateids, $db_templates,
+ array_key_exists('templateids_clear', $data) ? $data['templateids_clear'] : []
+ );
}
}
diff --git a/ui/include/classes/api/services/CTrigger.php b/ui/include/classes/api/services/CTrigger.php
index 50c5b972f1e..db00f0c359a 100644
--- a/ui/include/classes/api/services/CTrigger.php
+++ b/ui/include/classes/api/services/CTrigger.php
@@ -524,29 +524,15 @@ class CTrigger extends CTriggerGeneral {
*/
public function create(array $triggers) {
$this->validateCreate($triggers);
+
$this->createReal($triggers);
+ $this->checkDependenciesLinks($triggers);
+
$this->inherit($triggers);
- // Clear all dependencies on inherited triggers.
- $this->deleteDependencies($triggers);
+ $this->updateDependencies($triggers);
- // Add new dependencies.
- foreach ($triggers as $trigger) {
- if (!array_key_exists('dependencies', $trigger) || !$trigger['dependencies']) {
- continue;
- }
-
- $new_dependencies = [];
- foreach ($trigger['dependencies'] as $dependency) {
- $new_dependencies[] = [
- 'triggerid' => $trigger['triggerid'],
- 'dependsOnTriggerid' => $dependency['triggerid']
- ];
- }
- $this->addDependencies($new_dependencies);
- }
-
- return ['triggerids' => zbx_objectValues($triggers, 'triggerid')];
+ return ['triggerids' => array_column($triggers, 'triggerid')];
}
/**
@@ -558,51 +544,17 @@ class CTrigger extends CTriggerGeneral {
*
* @return array
*/
- public function update(array $triggers) {
+ public function update(array $triggers): array {
$this->validateUpdate($triggers, $db_triggers);
- $validate_dependencies = [];
- foreach ($triggers as $tnum => $trigger) {
- $db_trigger = $db_triggers[$tnum];
-
- $expressions_changed = ($trigger['expression'] !== $db_trigger['expression']
- || $trigger['recovery_expression'] !== $db_trigger['recovery_expression']);
-
- if ($expressions_changed && $db_trigger['dependencies'] && !array_key_exists('dependencies', $trigger)) {
- $validate_dependencies[] = [
- 'triggerid' => $trigger['triggerid'],
- 'dependencies' => zbx_objectValues($db_trigger['dependencies'], 'triggerid')
- ];
- }
- }
-
- if ($validate_dependencies) {
- $this->checkDependencies($validate_dependencies);
- $this->checkDependencyParents($validate_dependencies);
- }
-
$this->updateReal($triggers, $db_triggers);
+ self::checkExistingDependencies($triggers, $db_triggers);
+
$this->inherit($triggers);
- foreach ($triggers as $trigger) {
- // Replace dependencies.
- if (array_key_exists('dependencies', $trigger)) {
- $this->deleteDependencies($trigger);
-
- if ($trigger['dependencies']) {
- $new_dependencies = [];
- foreach ($trigger['dependencies'] as $dependency) {
- $new_dependencies[] = [
- 'triggerid' => $trigger['triggerid'],
- 'dependsOnTriggerid' => $dependency['triggerid']
- ];
- }
- $this->addDependencies($new_dependencies);
- }
- }
- }
+ $this->updateDependencies($triggers, $db_triggers);
- return ['triggerids' => zbx_objectValues($triggers, 'triggerid')];
+ return ['triggerids' => array_column($triggers, 'triggerid')];
}
/**
@@ -663,498 +615,235 @@ class CTrigger extends CTriggerGeneral {
}
/**
- * Validates the input for the addDependencies() method.
- *
- * @param array $triggers_data
- * @param bool $inherited
+ * Check the existing dependencies if the trigger expressions were changed.
*
- * @throws APIException if the given dependencies are invalid.
+ * @param array $triggers
+ * @param array $db_triggers
*/
- protected function validateAddDependencies(array &$triggers_data, $inherited = false) {
- $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['triggerid', 'dependsOnTriggerid']], 'fields' => [
- 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED],
- 'dependsOnTriggerid' => ['type' => API_ID, 'flags' => API_REQUIRED]
- ]];
- if (!CApiInputValidator::validate($api_input_rules, $triggers_data, '/', $error)) {
- self::exception(ZBX_API_ERROR_PARAMETERS, $error);
- }
+ private static function checkExistingDependencies(array $triggers, array $db_triggers): void {
+ $triggerids = [];
+ $hostids = [];
- $triggerids = zbx_objectValues($triggers_data, 'triggerid');
- $triggerids = array_keys(array_flip($triggerids));
+ foreach ($triggers as $trigger) {
+ if (array_key_exists('hosts', $db_triggers[$trigger['triggerid']])) {
+ $triggerids[$trigger['triggerid']] = true;
+ $hostids += $db_triggers[$trigger['triggerid']]['hosts'];
+ }
+ }
- $permission_check = $inherited
- ? ['nopermissions' => true]
- : ['editable' => true];
+ if (!$triggerids) {
+ return;
+ }
- $triggers = $this->get([
- 'output' => ['triggerid', 'description', 'templateid', 'flags'],
- 'triggerids' => $triggerids,
- 'preservekeys' => true
- ] + $permission_check);
+ /*
+ * It's necessary to perform the check of existing dependencies only if, as the result of the expression change,
+ * the trigger no longer belongs to any of the previous hosts.
+ */
+ $result = DBselect(
+ 'SELECT DISTINCT f.triggerid,i.hostid'.
+ ' FROM functions f,items i'.
+ ' WHERE f.itemid=i.itemid'.
+ ' AND '.dbConditionId('f.triggerid', array_keys($triggerids)).
+ ' AND '.dbConditionId('i.hostid', array_keys($hostids))
+ );
- if (count($triggerids) != count($triggers)) {
- self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
+ while ($row = DBfetch($result)) {
+ if (array_key_exists($row['hostid'], $db_triggers[$row['triggerid']]['hosts'])) {
+ unset($db_triggers[$row['triggerid']]['hosts'][$row['hostid']]);
+ }
}
+ $trigger_dependencies = [];
+
foreach ($triggers as $trigger) {
- if ($trigger['templateid'] && !$inherited) {
- self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update dependencies of inherited trigger "%1$s".',
- $trigger['description']
- ));
+ if (!array_key_exists($trigger['triggerid'], $triggerids)
+ || !$db_triggers[$trigger['triggerid']]['hosts']) {
+ continue;
}
- if ($trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
- self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update "%2$s" for a discovered trigger "%1$s".',
- $trigger['description'], 'dependencies'
- ));
+ $dependencies = array_key_exists('dependencies', $trigger)
+ ? $trigger['dependencies']
+ : $db_triggers[$trigger['triggerid']]['dependencies'];
+
+ foreach ($dependencies as $trigger_up) {
+ $trigger_dependencies[$trigger_up['triggerid']][$trigger['triggerid']] = true;
}
}
- $dep_triggerids = [];
- $triggers = [];
- foreach ($triggers_data as $dep) {
- $triggerid = $dep['triggerid'];
-
- if (!array_key_exists($dep['triggerid'], $triggers)) {
- $triggers[$triggerid] = [
- 'triggerid' => $triggerid,
- 'dependencies' => []
- ];
- }
- $triggers[$triggerid]['dependencies'][] = $dep['dependsOnTriggerid'];
- $dep_triggerids[$dep['dependsOnTriggerid']] = $dep['dependsOnTriggerid'];
+ if (!$trigger_dependencies) {
+ return;
}
- if (!$inherited) {
- $count = $this->get([
- 'countOutput' => true,
- 'triggerids' => $dep_triggerids
- ]);
+ /*
+ * There is no need to perform a check for dependency duplicates, because we are checking for existing
+ * dependencies that cannot have them. Also there is no need to perform the check on circular
+ * dependencies, because the dependencies are based on trigger IDs. Even if the expressions have changed, the
+ * trigger IDs remain the same.
+ */
- if ($count != count($dep_triggerids)) {
- self::exception(ZBX_API_ERROR_PERMISSIONS,
- _('No permissions to referred object or it does not exist!')
- );
- }
- }
+ $trigger_hosts = self::getTriggerHosts($trigger_dependencies);
- $this->checkDependencies($triggers);
- $this->checkDependencyParents($triggers);
- $this->checkDependencyDuplicates($triggers);
+ /*
+ * The template trigger can become a host trigger. Therefore the template trigger dependencies may remain
+ * after the update.
+ */
+ self::checkDependenciesOfHostTriggers($trigger_dependencies, $trigger_hosts);
+
+ /*
+ * The template (or host) trigger can become a trigger of another template. If after the update
+ * the trigger became owned by another template and it also has a dependency on a trigger from another
+ * template remaining, then we should check that:
+ * - Such dependency did not become a dependency on a trigger from the parent template.
+ * - Such dependency did not become a dependency on a trigger from the child template or host.
+ * - The template of the trigger-up is linked to all child templates of the new trigger's template.
+ */
+ self::checkDependenciesOfTemplateTriggers($trigger_dependencies, $trigger_hosts);
}
/**
- * Add the given dependencies and inherit them on all child triggers.
+ * Validates the input for the addDependencies() method.
*
- * @param array $triggers_data An array of trigger dependency pairs, each pair in the form of
- * ['triggerid' => 1, 'dependsOnTriggerid' => 2].
- * @param bool $inherited Determines either to check permissions for added dependencies. Permissions are not
- * validated for inherited triggers.
+ * @param array $triggers_data
+ * @param array|null $triggers
+ * @param array|null $db_triggers
*
- * @return array
+ * @throws APIException if the given dependencies are invalid.
*/
- public function addDependencies(array $triggers_data, $inherited = false) {
- $this->validateAddDependencies($triggers_data, $inherited);
-
- foreach ($triggers_data as $dep) {
- $triggerId = $dep['triggerid'];
- $depTriggerId = $dep['dependsOnTriggerid'];
-
- DB::insert('trigger_depends', [[
- 'triggerid_down' => $triggerId,
- 'triggerid_up' => $depTriggerId
- ]]);
-
- // propagate the dependencies to the child triggers
- $childTriggers = API::getApiService()->select($this->tableName(), [
- 'output' => ['triggerid'],
- 'filter' => [
- 'templateid' => $triggerId
- ]
- ]);
- if ($childTriggers) {
- foreach ($childTriggers as $childTrigger) {
- $childHostsQuery = get_hosts_by_triggerid($childTrigger['triggerid']);
- while ($childHost = DBfetch($childHostsQuery)) {
- $newDep = [$childTrigger['triggerid'] => $depTriggerId];
- $newDep = replace_template_dependencies($newDep, $childHost['hostid']);
-
- $this->addDependencies([[
- 'triggerid' => $childTrigger['triggerid'],
- 'dependsOnTriggerid' => $newDep[$childTrigger['triggerid']]
- ]], true);
- }
- }
- }
+ protected function validateAddDependencies(array $triggers_data, ?array &$triggers, ?array &$db_triggers): void {
+ $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['triggerid', 'dependsOnTriggerid']], 'fields' => [
+ 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED],
+ 'dependsOnTriggerid' => ['type' => API_ID, 'flags' => API_REQUIRED]
+ ]];
+
+ if (!CApiInputValidator::validate($api_input_rules, $triggers_data, '/', $error)) {
+ self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
- return ['triggerids' => array_unique(zbx_objectValues($triggers_data, 'triggerid'))];
- }
+ $triggers = [];
+ $trigger_dependencies = [];
- /**
- * Validates the input for the deleteDependencies() method.
- *
- * @param array $triggers
- * @param bool $inherited
- *
- * @throws APIException if the given input is invalid
- */
- protected function validateDeleteDependencies(array $triggers, $inherited) {
- if (!$triggers) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
- }
+ foreach ($triggers_data as $dependency) {
+ $triggers[$dependency['triggerid']]['triggerid'] = $dependency['triggerid'];
+ $triggers[$dependency['triggerid']]['dependencies'][] = ['triggerid' => $dependency['dependsOnTriggerid']];
- foreach ($triggers as $trigger) {
- if (!check_db_fields(['triggerid' => null], $trigger)) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.'));
- }
+ $trigger_dependencies[$dependency['dependsOnTriggerid']][$dependency['triggerid']] = true;
}
- $triggerids = zbx_objectValues($triggers, 'triggerid');
- $triggerids = array_keys(array_flip($triggerids));
+ $triggers = array_values($triggers);
- $permission_check = $inherited
- ? ['nopermissions' => true]
- : ['editable' => true];
-
- $triggers = $this->get([
- 'output' => ['triggerid', 'description', 'templateid', 'flags'],
- 'triggerids' => $triggerids,
+ $db_triggers = $this->get([
+ 'output' => ['triggerid', 'description', 'flags'],
+ 'triggerids' => array_column($triggers, 'triggerid'),
+ 'editable' => true,
'preservekeys' => true
- ] + $permission_check);
+ ]);
- foreach ($triggerids as $triggerid) {
- if (!array_key_exists($triggerid, $triggers)) {
- self::exception(ZBX_API_ERROR_PERMISSIONS,
- _('No permissions to referred object or it does not exist!')
- );
- }
+ if (count($db_triggers) != count($triggers)) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
- foreach ($triggers as $trigger) {
- if ($trigger['templateid'] && !$inherited) {
- self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update dependencies of inherited trigger "%1$s".',
- $trigger['description']
- ));
- }
-
- if ($trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
+ foreach ($db_triggers as $db_trigger) {
+ if ($db_trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update "%2$s" for a discovered trigger "%1$s".',
- $trigger['description'], 'dependencies'
+ $db_trigger['description'], 'dependencies'
));
}
}
- }
- /**
- * Deletes all trigger dependencies from the given triggers and their children.
- *
- * @param array $triggers an array of triggers with the 'triggerid' field defined
- * @param bool $inherited Determines either to check permissions for deleted dependencies. Permissions are not
- * validated for inherited triggers.
- *
- * @return array
- */
- public function deleteDependencies(array $triggers, $inherited = false) {
- $triggers = zbx_toArray($triggers);
+ $count = $this->get([
+ 'countOutput' => true,
+ 'triggerids' => array_keys($trigger_dependencies)
+ ]);
- $this->validateDeleteDependencies($triggers, $inherited);
+ if ($count != count($trigger_dependencies)) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
+ }
- $triggerids = zbx_objectValues($triggers, 'triggerid');
+ self::checkDependencyDuplicates($trigger_dependencies);
+ self::checkCircularDependencies($trigger_dependencies);
- try {
- // delete the dependencies from the child triggers
- $childTriggers = DB::select($this->tableName(), [
- 'output' => ['triggerid'],
- 'filter' => [
- 'templateid' => $triggerids
- ]
- ]);
- if ($childTriggers) {
- $this->deleteDependencies($childTriggers, true);
- }
+ $trigger_hosts = self::getTriggerHosts($trigger_dependencies);
- DB::delete('trigger_depends', [
- 'triggerid_down' => $triggerids
- ]);
- }
- catch (APIException $e) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete dependency'));
- }
+ self::checkDependenciesOfHostTriggers($trigger_dependencies, $trigger_hosts);
+ self::checkDependenciesOfTemplateTriggers($trigger_dependencies, $trigger_hosts);
- return ['triggerids' => $triggerids];
+ foreach ($db_triggers as &$db_trigger) {
+ $db_trigger['dependencies'] = [];
+ }
+ unset($db_trigger);
}
/**
- * Synchronizes the templated trigger dependencies on the given hosts inherited from the given
- * templates.
- * Update dependencies, do it after all triggers that can be dependent were created/updated on
- * all child hosts/templates. Starting from highest level template triggers select triggers from
- * one level lower, then for each lower trigger look if it's parent has dependencies, if so
- * find this dependency trigger child on dependent trigger host and add new dependency.
+ * Add the given dependencies and inherit them on all child triggers.
*
- * @param array $data
+ * @param array $triggers_data An array of trigger dependency pairs, each pair in the form of
+ * ['triggerid' => 1, 'dependsOnTriggerid' => 2].
+ *
+ * @return array
*/
- public function syncTemplateDependencies(array $data) {
- $templateIds = zbx_toArray($data['templateids']);
- $hostIds = zbx_toArray($data['hostids']);
-
- $parentTriggers = $this->get([
- 'output' => ['triggerid'],
- 'selectDependencies' => ['triggerid'],
- 'hostids' => $templateIds,
- 'preservekeys' => true,
- 'nopermissions' => true
- ]);
-
- if ($parentTriggers) {
- $childTriggers = $this->get([
- 'output' => ['triggerid', 'templateid'],
- 'hostids' => ($hostIds) ? $hostIds : null,
- 'filter' => ['templateid' => array_keys($parentTriggers)],
- 'nopermissions' => true,
- 'preservekeys' => true,
- 'selectHosts' => ['hostid']
- ]);
+ public function addDependencies(array $triggers_data) {
+ $this->validateAddDependencies($triggers_data, $triggers, $db_triggers);
- if ($childTriggers) {
- $newDependencies = [];
- foreach ($childTriggers as $childTrigger) {
- $parentDependencies = $parentTriggers[$childTrigger['templateid']]['dependencies'];
- if ($parentDependencies) {
- $dependencies = [];
- foreach ($parentDependencies as $depTrigger) {
- $dependencies[] = $depTrigger['triggerid'];
- }
- $host = reset($childTrigger['hosts']);
- $dependencies = replace_template_dependencies($dependencies, $host['hostid']);
- foreach ($dependencies as $depTriggerId) {
- $newDependencies[] = [
- 'triggerid' => $childTrigger['triggerid'],
- 'dependsOnTriggerid' => $depTriggerId
- ];
- }
- }
- }
- $this->deleteDependencies($childTriggers, true);
+ self::updateDependencies($triggers, $db_triggers);
- if ($newDependencies) {
- $this->addDependencies($newDependencies, true);
- }
- }
- }
+ return ['triggerids' => array_column($triggers, 'triggerid')];
}
/**
- * Validates the dependencies of the given triggers.
+ * @param array $triggers
+ * @param array|null $db_triggers
*
- * @param array $triggers list of triggers and corresponding dependencies
- * @param int $triggers[]['triggerid'] trigger id
- * @param array $triggers[]['dependencies'] list of trigger ids on which depends given trigger
- *
- * @trows APIException if any of the dependencies is invalid
+ * @throws APIException if the given input is invalid
*/
- protected function checkDependencies(array $triggers) {
- foreach ($triggers as $trigger) {
- if (empty($trigger['dependencies'])) {
- continue;
- }
-
- // trigger templates
- $triggerTemplates = API::Template()->get([
- 'output' => ['status', 'hostid'],
- 'triggerids' => $trigger['triggerid'],
- 'nopermissions' => true
- ]);
-
- // forbid dependencies from hosts to templates
- if (!$triggerTemplates) {
- $triggerDependencyTemplates = API::Template()->get([
- 'output' => ['templateid'],
- 'triggerids' => $trigger['dependencies'],
- 'nopermissions' => true,
- 'limit' => 1
- ]);
- if ($triggerDependencyTemplates) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot add dependency from a host to a template.'));
- }
- }
-
- // the trigger can't depend on itself
- if (in_array($trigger['triggerid'], $trigger['dependencies'])) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot create dependency on trigger itself.'));
- }
-
- // check circular dependency
- $downTriggerIds = [$trigger['triggerid']];
- do {
- // triggerid_down depends on triggerid_up
- $res = DBselect(
- 'SELECT td.triggerid_up'.
- ' FROM trigger_depends td'.
- ' WHERE '.dbConditionInt('td.triggerid_down', $downTriggerIds)
- );
-
- // combine db dependencies with those to be added
- $upTriggersIds = [];
- while ($row = DBfetch($res)) {
- $upTriggersIds[] = $row['triggerid_up'];
- }
- foreach ($downTriggerIds as $id) {
- if (isset($triggers[$id]) && isset($triggers[$id]['dependencies'])) {
- $upTriggersIds = array_merge($upTriggersIds, $triggers[$id]['dependencies']);
- }
- }
+ private function validateDeleteDependencies(array &$triggers, ?array &$db_triggers): void {
+ $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['triggerid']], 'fields' => [
+ 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED]
+ ]];
- // if found trigger id is in dependent triggerids, there is a dependency loop
- $downTriggerIds = [];
- foreach ($upTriggersIds as $id) {
- if (bccomp($id, $trigger['triggerid']) == 0) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot create circular dependencies.'));
- }
- $downTriggerIds[] = $id;
- }
- } while (!empty($downTriggerIds));
+ if (!CApiInputValidator::validate($api_input_rules, $triggers, '/', $error)) {
+ self::exception(ZBX_API_ERROR_PARAMETERS, $error);
+ }
- // fetch all templates that are used in dependencies
- $triggerDependencyTemplates = API::Template()->get([
- 'output' => ['templateid'],
- 'triggerids' => $trigger['dependencies'],
- 'nopermissions' => true
- ]);
- $depTemplateIds = zbx_toHash(zbx_objectValues($triggerDependencyTemplates, 'templateid'));
-
- // run the check only if a templated trigger has dependencies on other templates
- $triggerTemplateIds = zbx_toHash(zbx_objectValues($triggerTemplates, 'templateid'));
- $tdiff = array_diff($depTemplateIds, $triggerTemplateIds);
- if (!empty($triggerTemplateIds) && !empty($depTemplateIds) && !empty($tdiff)) {
- $affectedTemplateIds = zbx_array_merge($triggerTemplateIds, $depTemplateIds);
-
- // create a list of all hosts, that are children of the affected templates
- $dbLowlvltpl = DBselect(
- 'SELECT DISTINCT ht.templateid,ht.hostid,h.host'.
- ' FROM hosts_templates ht,hosts h'.
- ' WHERE h.hostid=ht.hostid'.
- ' AND '.dbConditionInt('ht.templateid', $affectedTemplateIds)
- );
- $map = [];
- while ($lowlvltpl = DBfetch($dbLowlvltpl)) {
- if (!isset($map[$lowlvltpl['hostid']])) {
- $map[$lowlvltpl['hostid']] = [];
- }
- $map[$lowlvltpl['hostid']][$lowlvltpl['templateid']] = $lowlvltpl['host'];
- }
+ $db_triggers = $this->get([
+ 'output' => ['description', 'flags'],
+ 'triggerids' => array_column($triggers, 'triggerid'),
+ 'editable' => true,
+ 'preservekeys' => true
+ ]);
- // check that if some host is linked to the template, that the trigger belongs to,
- // the host must also be linked to all of the templates, that trigger dependencies point to
- foreach ($map as $templates) {
- foreach ($triggerTemplateIds as $triggerTemplateId) {
- // is the host linked to one of the trigger templates?
- if (isset($templates[$triggerTemplateId])) {
- // then make sure all of the dependency templates are also linked
- foreach ($depTemplateIds as $depTemplateId) {
- if (!isset($templates[$depTemplateId])) {
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Not all templates are linked to "%1$s".', reset($templates))
- );
- }
- }
- break;
- }
- }
- }
- }
+ if (count($db_triggers) != count($triggers)) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
- }
- /**
- * Check that none of the triggers have dependencies on their children. Checks only one level of inheritance, but
- * since it is called on each inheritance step, also works for multiple inheritance levels.
- *
- * @throws APIException if at least one trigger is dependent on its child
- *
- * @param array $triggers
- */
- protected function checkDependencyParents(array $triggers) {
- // fetch all templated dependency trigger parents
- $depTriggerIds = [];
- foreach ($triggers as $trigger) {
- foreach ($trigger['dependencies'] as $depTriggerId) {
- $depTriggerIds[$depTriggerId] = $depTriggerId;
+ foreach ($db_triggers as $db_trigger) {
+ if ($db_trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update "%2$s" for a discovered trigger "%1$s".',
+ $db_trigger['description'], 'dependencies'
+ ));
}
}
- $parentDepTriggers = DBfetchArray(DBSelect(
- 'SELECT templateid,triggerid'.
- ' FROM triggers'.
- ' WHERE templateid>0'.
- ' AND '.dbConditionInt('triggerid', $depTriggerIds)
- ));
- if ($parentDepTriggers) {
- $parentDepTriggers = zbx_toHash($parentDepTriggers, 'triggerid');
- foreach ($triggers as $trigger) {
- foreach ($trigger['dependencies'] as $depTriggerId) {
- // check if the current trigger is the parent of the dependency trigger
- if (isset($parentDepTriggers[$depTriggerId])
- && $parentDepTriggers[$depTriggerId]['templateid'] == $trigger['triggerid']) {
-
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Trigger cannot be dependent on a trigger that is inherited from it.')
- );
- }
- }
- }
+
+ foreach ($triggers as &$trigger) {
+ $trigger['dependencies'] = [];
}
+ unset($trigger);
+
+ self::addAffectedDependencies($triggers, $db_triggers);
}
/**
- * Checks if the given dependencies contain duplicates.
+ * Deletes all trigger dependencies from the given triggers and their children.
*
- * @throws APIException if the given dependencies contain duplicates
+ * @param array $triggers An array of triggers with the 'triggerid' field defined.
*
- * @param array $triggers
+ * @return array
*/
- protected function checkDependencyDuplicates(array $triggers) {
- // check duplicates in array
- $uniqueTriggers = [];
- $duplicateTriggerId = null;
- foreach ($triggers as $trigger) {
- foreach ($trigger['dependencies'] as $dep) {
- if (isset($uniqueTriggers[$trigger['triggerid']][$dep])) {
- $duplicateTriggerId = $trigger['triggerid'];
- break 2;
- }
- else {
- $uniqueTriggers[$trigger['triggerid']][$dep] = 1;
- }
- }
- }
+ public function deleteDependencies(array $triggers) {
+ $this->validateDeleteDependencies($triggers, $db_triggers);
- if ($duplicateTriggerId === null) {
- // check if dependency already exists in DB
- foreach ($triggers as $trigger) {
- $dbUpTriggers = DBselect(
- 'SELECT td.triggerid_up'.
- ' FROM trigger_depends td'.
- ' WHERE '.dbConditionInt('td.triggerid_up', $trigger['dependencies']).
- ' AND td.triggerid_down='.zbx_dbstr($trigger['triggerid'])
- , 1);
- if (DBfetch($dbUpTriggers)) {
- $duplicateTriggerId = $trigger['triggerid'];
- break;
- }
- }
- }
+ self::updateDependencies($triggers, $db_triggers);
- if ($duplicateTriggerId) {
- $dplTrigger = DBfetch(DBselect(
- 'SELECT t.description'.
- ' FROM triggers t'.
- ' WHERE t.triggerid='.zbx_dbstr($duplicateTriggerId)
- ));
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Duplicate dependencies in trigger "%1$s".', $dplTrigger['description'])
- );
- }
+ return ['triggerids' => array_column($triggers, 'triggerid')];
}
protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
diff --git a/ui/include/classes/api/services/CTriggerGeneral.php b/ui/include/classes/api/services/CTriggerGeneral.php
index 0a17831081b..1c01099ed24 100644
--- a/ui/include/classes/api/services/CTriggerGeneral.php
+++ b/ui/include/classes/api/services/CTriggerGeneral.php
@@ -203,7 +203,7 @@ abstract class CTriggerGeneral extends CApiService {
'triggerid' => $chd_trigger['triggerid'],
'templateid' => $tpl_trigger['triggerid']
];
- $db_triggers[] = $chd_trigger;
+ $db_triggers[$chd_trigger['triggerid']] = $chd_trigger;
$triggerids[] = $chd_trigger['triggerid'];
$check_duplicates = ($chd_trigger['description'] !== $new_trigger['description']
@@ -247,11 +247,12 @@ abstract class CTriggerGeneral extends CApiService {
];
}
- foreach ($db_triggers as $tnum => $db_trigger) {
- $db_triggers[$tnum]['tags'] = array_key_exists($db_trigger['triggerid'], $trigger_tags)
+ foreach ($db_triggers as &$db_trigger) {
+ $db_trigger['tags'] = array_key_exists($db_trigger['triggerid'], $trigger_tags)
? $trigger_tags[$db_trigger['triggerid']]
: [];
}
+ unset($db_trigger);
// Add discovery rule IDs.
if ($this instanceof CTriggerPrototype) {
@@ -268,9 +269,10 @@ abstract class CTriggerGeneral extends CApiService {
$drule_by_triggerid[$row['triggerid']] = $row['parent_itemid'];
}
- foreach ($db_triggers as $tnum => $db_trigger) {
- $db_triggers[$tnum]['discoveryRule']['itemid'] = $drule_by_triggerid[$db_trigger['triggerid']];
+ foreach ($db_triggers as &$db_trigger) {
+ $db_trigger['discoveryRule']['itemid'] = $drule_by_triggerid[$db_trigger['triggerid']];
}
+ unset($db_trigger);
}
}
@@ -508,9 +510,9 @@ abstract class CTriggerGeneral extends CApiService {
}
/**
- * Updates the children of the triggers on the given hosts and propagates the inheritance to all child hosts.
- * If the given triggers was assigned to a different template or a host, all of the child triggers, that became
- * obsolete will be deleted.
+ * Updates children of triggers on the given hosts and propagates the inheritance to all child hosts.
+ * All of the child triggers that became obsolete will be deleted if the given triggers were assigned to a different
+ * template or host.
*
* @param array $triggers
* @param string $triggers[]['triggerid']
@@ -914,6 +916,7 @@ abstract class CTriggerGeneral extends CApiService {
$descriptions = $this->populateHostIds($descriptions);
$this->checkAndAddUuid($triggers, $descriptions);
$this->checkDuplicates($descriptions);
+ $this->checkDependencies($triggers);
}
/**
@@ -963,8 +966,6 @@ abstract class CTriggerGeneral extends CApiService {
* @throws APIException if validation failed.
*/
protected function validateUpdate(array &$triggers, array &$db_triggers = null) {
- $db_triggers = [];
-
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['description', 'expression']], 'fields' => [
'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'description' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('triggers', 'description')],
@@ -1005,7 +1006,6 @@ abstract class CTriggerGeneral extends CApiService {
'templateid', 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag',
'manual_close', 'opdata', 'event_name'
],
- 'selectDependencies' => ['triggerid'],
'triggerids' => zbx_objectValues($triggers, 'triggerid'),
'editable' => true,
'preservekeys' => true
@@ -1035,21 +1035,17 @@ abstract class CTriggerGeneral extends CApiService {
self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.'));
}
- $_db_triggers = CMacrosResolverHelper::resolveTriggerExpressions($this->get($options),
- ['sources' => ['expression', 'recovery_expression']]
- );
+ $db_triggers = $this->get($options);
- $db_trigger_tags = $_db_triggers
- ? DB::select('trigger_tag', [
- 'output' => ['triggertagid', 'triggerid', 'tag', 'value'],
- 'filter' => ['triggerid' => array_keys($_db_triggers)],
- 'preservekeys' => true
- ])
- : [];
+ if (count($db_triggers) != count($triggers)) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
+ }
+
+ $db_triggers = CMacrosResolverHelper::resolveTriggerExpressions($db_triggers, [
+ 'sources' => ['expression', 'recovery_expression']
+ ]);
- $_db_triggers = $this
- ->createRelationMap($db_trigger_tags, 'triggerid', 'triggertagid')
- ->mapMany($_db_triggers, $db_trigger_tags, 'tags');
+ $this->addAffectedObjects($triggers, $db_triggers);
$read_only_fields = ['description', 'expression', 'recovery_mode', 'recovery_expression', 'correlation_mode',
'correlation_tag', 'manual_close'
@@ -1057,12 +1053,8 @@ abstract class CTriggerGeneral extends CApiService {
$descriptions = [];
- foreach ($triggers as $key => &$trigger) {
- if (!array_key_exists($trigger['triggerid'], $_db_triggers)) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
- }
-
- $db_trigger = $_db_triggers[$trigger['triggerid']];
+ foreach ($triggers as &$trigger) {
+ $db_trigger = $db_triggers[$trigger['triggerid']];
$description = array_key_exists('description', $trigger)
? $trigger['description']
: $db_trigger['description'];
@@ -1110,8 +1102,6 @@ abstract class CTriggerGeneral extends CApiService {
'recovery_expression' => $trigger['recovery_expression']
];
}
-
- $db_triggers[$key] = $db_trigger;
}
unset($trigger);
@@ -1119,6 +1109,303 @@ abstract class CTriggerGeneral extends CApiService {
$descriptions = $this->populateHostIds($descriptions);
$this->checkDuplicates($descriptions);
}
+
+ $this->checkDependencies($triggers, $db_triggers);
+ $this->checkDependenciesLinks($triggers, $db_triggers);
+ }
+
+ /**
+ * @param array $triggers
+ * @param array $db_triggers
+ */
+ private function addAffectedObjects(array $triggers, array &$db_triggers): void {
+ if ($this instanceof CTrigger) {
+ self::addAffectedHosts($triggers, $db_triggers);
+ }
+
+ self::addAffectedTags($triggers, $db_triggers);
+ self::addAffectedDependencies($triggers, $db_triggers);
+ }
+
+ /**
+ * @param array $triggers
+ * @param array $db_triggers
+ */
+ protected static function addAffectedHosts(array $triggers, array &$db_triggers): void {
+ $triggerids = [];
+
+ foreach ($triggers as $trigger) {
+ $db_trigger = $db_triggers[$trigger['triggerid']];
+
+ if ((array_key_exists('expression', $trigger) && $trigger['expression'] !== $db_trigger['expression'])
+ || (array_key_exists('recovery_expression', $trigger)
+ && $trigger['recovery_expression'] !== $db_trigger['recovery_expression'])) {
+ $triggerids[] = $trigger['triggerid'];
+ $db_triggers[$trigger['triggerid']]['hosts'] = [];
+ }
+ }
+
+ if (!$triggerids) {
+ return;
+ }
+
+ $result = DBselect(
+ 'SELECT DISTINCT f.triggerid,i.hostid'.
+ ' FROM functions f,items i'.
+ ' WHERE f.itemid=i.itemid'.
+ ' AND '.dbConditionId('f.triggerid', $triggerids)
+ );
+
+ while ($row = DBfetch($result)) {
+ $db_triggers[$row['triggerid']]['hosts'][$row['hostid']] = true;
+ }
+ }
+
+ /**
+ * @param array $triggers
+ * @param array $db_triggers
+ */
+ private static function addAffectedTags(array $triggers, array &$db_triggers): void {
+ $triggerids = [];
+
+ foreach ($triggers as $trigger) {
+ if (array_key_exists('tags', $trigger)) {
+ $triggerids[] = $trigger['triggerid'];
+ $db_triggers[$trigger['triggerid']]['tags'] = [];
+ }
+ }
+
+ if (!$triggerids) {
+ return;
+ }
+
+ $options = [
+ 'output' => ['triggertagid', 'triggerid', 'tag', 'value'],
+ 'filter' => ['triggerid' => $triggerids]
+ ];
+ $db_tags = DBselect(DB::makeSql('trigger_tag', $options));
+
+ while ($db_tag = DBfetch($db_tags)) {
+ $db_triggers[$db_tag['triggerid']]['tags'][$db_tag['triggertagid']] =
+ array_diff_key($db_tag, array_flip(['triggerid']));
+ }
+ }
+
+ /**
+ * @param array $triggers
+ * @param array $db_triggers
+ */
+ protected static function addAffectedDependencies(array $triggers, array &$db_triggers): void {
+ $triggerids = [];
+
+ foreach ($triggers as $trigger) {
+ $db_trigger = $db_triggers[$trigger['triggerid']];
+
+ if (array_key_exists('dependencies', $trigger) || array_key_exists('hosts', $db_trigger)) {
+ $triggerids[] = $trigger['triggerid'];
+ $db_triggers[$trigger['triggerid']]['dependencies'] = [];
+ }
+ }
+
+ if (!$triggerids) {
+ return;
+ }
+
+ $options = [
+ 'output' => ['triggerdepid', 'triggerid_down', 'triggerid_up'],
+ 'filter' => ['triggerid_down' => $triggerids]
+ ];
+ $db_deps = DBselect(DB::makeSql('trigger_depends', $options));
+
+ while ($db_dep = DBfetch($db_deps)) {
+ $db_triggers[$db_dep['triggerid_down']]['dependencies'][$db_dep['triggerdepid']] = [
+ 'triggerdepid' => $db_dep['triggerdepid'],
+ 'triggerid' => $db_dep['triggerid_up']
+ ];
+ }
+ }
+
+ /**
+ * Check trigger dependencies of the given triggers.
+ *
+ * @param array $triggers
+ * @param array|null $db_triggers
+ *
+ * @throws APIException
+ */
+ private function checkDependencies(array $triggers, array $db_triggers = null): void {
+ $edit_triggerids_up = [];
+
+ foreach ($triggers as $trigger) {
+ if (!array_key_exists('dependencies', $trigger)) {
+ continue;
+ }
+
+ $triggerids_up = array_column($trigger['dependencies'], 'triggerid');
+
+ if ($db_triggers === null) {
+ $edit_triggerids_up += array_flip($triggerids_up);
+ }
+ else {
+ $db_triggerids_up = array_column($db_triggers[$trigger['triggerid']]['dependencies'], 'triggerid');
+
+ $ins_triggerids_up = array_flip(array_diff($triggerids_up, $db_triggerids_up));
+ $del_triggerids_up = array_flip(array_diff($db_triggerids_up, $triggerids_up));
+
+ $edit_triggerids_up += $ins_triggerids_up + $del_triggerids_up;
+ }
+ }
+
+ if (!$edit_triggerids_up) {
+ return;
+ }
+
+ $count = API::Trigger()->get([
+ 'countOutput' => true,
+ 'triggerids' => array_keys($edit_triggerids_up)
+ ]);
+
+ if ($this instanceof CTriggerPrototype) {
+ $trigger_prototypes_up = API::TriggerPrototype()->get([
+ 'output' => [],
+ 'triggerids' => array_keys($edit_triggerids_up),
+ 'preservekeys' => true
+ ]);
+
+ $count += count($trigger_prototypes_up);
+ }
+
+ if ($count != count($edit_triggerids_up)) {
+ self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
+ }
+
+ if ($this instanceof CTriggerPrototype) {
+ $ins_dependencies = [];
+ $triggerids = [];
+
+ foreach ($triggers as $trigger) {
+ if (!array_key_exists('dependencies', $triggers)) {
+ continue;
+ }
+
+ $db_triggers_up = ($db_triggers !== null)
+ ? array_column($db_triggers[$trigger['triggerid']]['dependencies'], null, 'triggerid')
+ : [];
+
+ foreach ($trigger['dependencies'] as $trigger_up) {
+ if (!array_key_exists($trigger_up['triggerid'], $db_triggers_up)
+ && array_key_exists($trigger_up['triggerid'], $trigger_prototypes_up)) {
+ $ins_dependencies[$trigger_up['triggerid']][$trigger['triggerid']] = true;
+ $triggerids[$trigger['triggerid']] = true;
+ }
+ }
+ }
+
+ if (!$ins_dependencies) {
+ return;
+ }
+
+ $result = DBselect(
+ 'SELECT DISTINCT f.triggerid,id.parent_itemid'.
+ ' FROM functions f,item_discovery id'.
+ ' WHERE f.itemid=id.itemid'.
+ ' AND '.dbConditionId('f.triggerid', array_keys($ins_dependencies + $triggerids))
+ );
+
+ $lld_rules = [];
+
+ while ($row = DBfetch($result)) {
+ $lld_rules[$row['triggerid']] = $row['parent_itemid'];
+ }
+
+ foreach ($ins_dependencies as $triggerid_up => $triggerids) {
+ foreach ($triggerids as $triggerid) {
+ if (bccomp($lld_rules[$triggerid_up], $lld_rules[$triggerid]) != 0) {
+ self::exception(ZBX_API_ERROR_PARAMETERS,
+ _('Trigger prototype "%1$s" cannot depend on the trigger prototype "%2$s", because dependencies on trigger prototypes from another LLD rule are not allowed.')
+ );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Check linkage of dependencies.
+ *
+ * @param array $triggers
+ * @param array|null $db_triggers
+ */
+ protected function checkDependenciesLinks(array $triggers, array $db_triggers = null): void {
+ $ins_dependencies = [];
+ $del_dependencies = [];
+
+ foreach ($triggers as $trigger) {
+ if (!array_key_exists('dependencies', $trigger)) {
+ continue;
+ }
+
+ $db_triggers_up = ($db_triggers !== null)
+ ? array_column($db_triggers[$trigger['triggerid']]['dependencies'], null, 'triggerid')
+ : [];
+
+ foreach ($trigger['dependencies'] as $trigger_up) {
+ if (array_key_exists($trigger_up['triggerid'], $db_triggers_up)) {
+ unset($db_triggers_up[$trigger_up['triggerid']]);
+ }
+ else {
+ $ins_dependencies[$trigger_up['triggerid']][$trigger['triggerid']] = true;
+ }
+ }
+
+ foreach ($db_triggers_up as $db_trigger_up) {
+ $del_dependencies[$db_trigger_up['triggerid']][$trigger['triggerid']] = true;
+ }
+ }
+
+ if ($ins_dependencies) {
+ if ($this instanceof CTriggerPrototype) {
+ $options = [
+ 'output' => ['triggerid', 'flags'],
+ 'triggerids' => array_keys($ins_dependencies)
+ ];
+ $result = DBselect(DB::makeSql('triggers', $options));
+
+ $ins_dependencies_prototypes = [];
+
+ while ($row = DBfetch($result)) {
+ if ($row['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
+
+ /*
+ * Trigger cannot depend on trigger prototypes, so it's enough to check circular dependencies
+ * only among the trigger prototypes.
+ */
+ $ins_dependencies_prototypes[$row['triggerid']] = $ins_dependencies[$row['triggerid']];
+
+ /*
+ * Since the trigger prototypes can depend only on trigger prototypes from the same LLD rule,
+ * for such dependencies it is not necessary to check the dependencies of host and template
+ * triggers. Only dependencies on triggers are required to be checked.
+ */
+ unset($ins_dependencies[$row['triggerid']]);
+ }
+ }
+ }
+
+ if ($db_triggers !== null) {
+ if ($this instanceof CTriggerPrototype && $ins_dependencies_prototypes) {
+ self::checkCircularDependencies($ins_dependencies_prototypes, $del_dependencies);
+ }
+ else {
+ self::checkCircularDependencies($ins_dependencies, $del_dependencies);
+ }
+ }
+
+ $trigger_hosts = self::getTriggerHosts($ins_dependencies);
+
+ self::checkDependenciesOfHostTriggers($ins_dependencies, $trigger_hosts);
+ self::checkDependenciesOfTemplateTriggers($ins_dependencies, $trigger_hosts);
+ }
}
/**
@@ -1248,7 +1535,7 @@ abstract class CTriggerGeneral extends CApiService {
$this->implode_expressions($triggers, $db_triggers, $triggers_functions, $inherited);
foreach ($triggers as $tnum => $trigger) {
- $db_trigger = $db_triggers[$tnum];
+ $db_trigger = $db_triggers[$trigger['triggerid']];
$upd_trigger = ['values' => [], 'where' => ['triggerid' => $trigger['triggerid']]];
if (array_key_exists($tnum, $triggers_functions)) {
@@ -1361,7 +1648,7 @@ abstract class CTriggerGeneral extends CApiService {
if (!$inherited) {
$resource = ($this instanceof CTrigger) ? CAudit::RESOURCE_TRIGGER : CAudit::RESOURCE_TRIGGER_PROTOTYPE;
- $this->addAuditBulk(CAudit::ACTION_UPDATE, $resource, $save_triggers, zbx_toHash($db_triggers, 'triggerid'));
+ $this->addAuditBulk(CAudit::ACTION_UPDATE, $resource, $save_triggers, $db_triggers);
}
}
@@ -1379,9 +1666,9 @@ abstract class CTriggerGeneral extends CApiService {
* @param int $triggers[<tnum>]['recovery_mode'] [IN]
* @param string $triggers[<tnum>]['recovery_expression'] [IN/OUT]
* @param array|null $db_triggers [IN]
- * @param string $db_triggers[<tnum>]['triggerid'] [IN]
- * @param string $db_triggers[<tnum>]['expression'] [IN]
- * @param string $db_triggers[<tnum>]['recovery_expression'] [IN]
+ * @param string $db_triggers[<triggerid>]['triggerid'] [IN]
+ * @param string $db_triggers[<triggerid>]['expression'] [IN]
+ * @param string $db_triggers[<triggerid>]['recovery_expression'] [IN]
* @param array $triggers_functions [OUT] array of the new functions which must be
* inserted into DB
* @param string $triggers_functions[<tnum>][]['functionid'] [OUT]
@@ -1438,10 +1725,10 @@ abstract class CTriggerGeneral extends CApiService {
$hosts_keys = [];
$functions_num = 0;
- foreach ($triggers as $tnum => $trigger) {
+ foreach ($triggers as $trigger) {
$expressions_changed = ($db_triggers === null
- || ($trigger['expression'] !== $db_triggers[$tnum]['expression']
- || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression']));
+ || ($trigger['expression'] !== $db_triggers[$trigger['triggerid']]['expression']
+ || $trigger['recovery_expression'] !== $db_triggers[$trigger['triggerid']]['recovery_expression']));
if (!$expressions_changed) {
continue;
@@ -1577,8 +1864,8 @@ abstract class CTriggerGeneral extends CApiService {
foreach ($triggers as $tnum => &$trigger) {
$expressions_changed = ($db_triggers === null
- || ($trigger['expression'] !== $db_triggers[$tnum]['expression']
- || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression']));
+ || ($trigger['expression'] !== $db_triggers[$trigger['triggerid']]['expression']
+ || $trigger['recovery_expression'] !== $db_triggers[$trigger['triggerid']]['recovery_expression']));
if (!$expressions_changed) {
continue;
@@ -1672,12 +1959,12 @@ abstract class CTriggerGeneral extends CApiService {
// Triggers with children cannot be moved from one template to another host or template.
if ($class === 'CTrigger' && $db_triggers !== null && $expressions_changed) {
- $expression_parser->parse($db_triggers[$tnum]['expression']);
+ $expression_parser->parse($db_triggers[$trigger['triggerid']]['expression']);
$old_hosts1 = $expression_parser->getResult()->getHosts();
$old_hosts2 = [];
if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) {
- $expression_parser->parse($db_triggers[$tnum]['recovery_expression']);
+ $expression_parser->parse($db_triggers[$trigger['triggerid']]['recovery_expression']);
$old_hosts2 = $expression_parser->getResult()->getHosts();
}
@@ -1690,7 +1977,7 @@ abstract class CTriggerGeneral extends CApiService {
}
if ($is_moved) {
- $moved_triggers[$db_triggers[$tnum]['triggerid']] = ['description' => $trigger['description']];
+ $moved_triggers[$trigger['triggerid']] = ['description' => $trigger['description']];
}
}
@@ -1717,7 +2004,7 @@ abstract class CTriggerGeneral extends CApiService {
));
}
elseif ($db_triggers !== null
- && !idcmp($lld_ruleids[0], $db_triggers[$tnum]['discoveryRule']['itemid'])) {
+ && !idcmp($lld_ruleids[0], $db_triggers[$trigger['triggerid']]['discoveryRule']['itemid'])) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot update trigger prototype "%1$s": %2$s.',
$trigger['description'], _('trigger prototype cannot be moved to another template or host')
));
@@ -1742,8 +2029,8 @@ abstract class CTriggerGeneral extends CApiService {
// Replace func(/host/item) macros with {<functionid>}.
foreach ($triggers as $tnum => &$trigger) {
$expressions_changed = ($db_triggers === null
- || ($trigger['expression'] !== $db_triggers[$tnum]['expression']
- || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression']));
+ || ($trigger['expression'] !== $db_triggers[$trigger['triggerid']]['expression']
+ || $trigger['recovery_expression'] !== $db_triggers[$trigger['triggerid']]['recovery_expression']));
if (!$expressions_changed) {
continue;
@@ -1921,4 +2208,1039 @@ abstract class CTriggerGeneral extends CApiService {
$this->inherit($triggers, $data['hostids']);
}
+
+ /**
+ * Checks if the given dependencies already exist.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ *
+ * @throws APIException
+ */
+ protected static function checkDependencyDuplicates(array $trigger_dependencies) {
+ $all_triggerids = [];
+
+ foreach ($trigger_dependencies as $triggerids) {
+ $all_triggerids += $triggerids;
+ }
+
+ $options = [
+ 'output' => ['triggerid_down', 'triggerid_up'],
+ 'filter' => [
+ 'triggerid_down' => array_keys($all_triggerids),
+ 'triggerid_up' => array_keys($trigger_dependencies)
+ ]
+ ];
+ $result = DBselect(DB::makeSql('trigger_depends', $options));
+
+ while ($row = DBfetch($result)) {
+ if (array_key_exists($row['triggerid_up'], $trigger_dependencies)
+ && in_array($row['triggerid_down'], $trigger_dependencies[$row['triggerid_up']])) {
+ $duplicates = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => [$row['triggerid_down'], $row['triggerid_up']],
+ 'preservekeys' => true
+ ]);
+
+ if ($duplicates[$row['triggerid_down']]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) {
+ $error = _('The dependency of trigger "%1$s" on trigger "%2$s" already exists.');
+ }
+ else {
+ $error = ($duplicates[$row['triggerid_up']]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('The dependency of trigger prototype "%1$s" on trigger "%2$s" already exists.')
+ : _('The dependency of trigger prototype "%1$s" on trigger prototype "%2$s" already exists.');
+ }
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
+ $duplicates[$row['triggerid_down']]['description'],
+ $duplicates[$row['triggerid_up']]['description']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Check whether circular linkage occurs as a result of the given changes in trigger dependencies.
+ *
+ * @param array $ins_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $del_dependencies[<triggerid_up>][<triggerid>]
+ * @param bool $inherited Whether the check gets performed during inherit.
+ *
+ * @throws APIException
+ */
+ protected static function checkCircularDependencies(array $ins_dependencies, array $del_dependencies = [],
+ bool $inherited = false): void {
+ $links = [];
+ $_triggerids_down = $ins_dependencies;
+
+ do {
+ $options = [
+ 'output' => ['triggerid_up', 'triggerid_down'],
+ 'filter' => [
+ 'triggerid_down' => array_keys($_triggerids_down)
+ ]
+ ];
+ $result = DBselect(DB::makeSql('trigger_depends', $options));
+
+ $_triggerids_down = [];
+
+ while ($row = DBfetch($result)) {
+ if (array_key_exists($row['triggerid_up'], $del_dependencies)
+ && array_key_exists($row['triggerid_down'], $del_dependencies[$row['triggerid_up']])) {
+ continue;
+ }
+
+ if (!array_key_exists($row['triggerid_up'], $links)) {
+ $_triggerids_down[$row['triggerid_up']] = true;
+ }
+
+ $links[$row['triggerid_up']][$row['triggerid_down']] = true;
+ }
+ } while ($_triggerids_down);
+
+ foreach ($ins_dependencies as $triggerid_up => $triggerids) {
+ if (array_key_exists($triggerid_up, $links)) {
+ $links[$triggerid_up] += $triggerids;
+ }
+ else {
+ $links[$triggerid_up] = $triggerids;
+ }
+ }
+
+ foreach ($ins_dependencies as $triggerid_up => $triggerids) {
+ foreach ($triggerids as $triggerid => $foo) {
+ if (array_key_exists($triggerid, $links)) {
+ $links_path = [$triggerid => true];
+
+ if (self::circularLinkageExists($links, $triggerid_up, $links[$triggerid], $links_path)) {
+ $trigger_up_name = '';
+
+ $triggers = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => array_keys($links_path + [$triggerid_up => true]),
+ 'preservekeys' => true
+ ]);
+
+ foreach ($triggers as $_triggerid => $trigger) {
+ $description = '"'.$trigger['description'].'"';
+
+ if (bccomp($_triggerid, $triggerid_up) == 0) {
+ $trigger_up_name = $description;
+ }
+ else {
+ $links_path[$_triggerid] = $description;
+ }
+ }
+
+ $circular_linkage = (bccomp($triggerid_up, $triggerid) == 0)
+ ? $trigger_up_name.' -> '.$trigger_up_name
+ : $trigger_up_name.' -> '.implode(' -> ', $links_path).' -> '.$trigger_up_name;
+
+ if ($inherited) {
+ $host = DBfetch(DBselect(
+ 'SELECT DISTINCT h.host,h.status'.
+ ' FROM functions f,items i,hosts h'.
+ ' WHERE f.itemid=i.itemid'.
+ ' AND i.hostid=h.hostid'.
+ ' AND '.dbConditionId('f.triggerid', [$triggerid_up])
+ ));
+
+ if ($host['status'] == HOST_STATUS_TEMPLATE) {
+ $error = ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s", because a circular linkage (%3$s) would occur for template "%4$s".')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger prototype "%2$s", because a circular linkage (%3$s) would occur for template "%4$s".');
+ }
+ else {
+ $error = ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s", because a circular linkage (%3$s) would occur for host "%4$s".')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger prototype "%2$s", because a circular linkage (%3$s) would occur for host "%4$s".');
+ }
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
+ $triggers[$triggerid]['description'], $triggers[$triggerid_up]['description'],
+ $circular_linkage, $host['host']
+ ));
+ }
+ else {
+ $error = ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s", because a circular linkage (%3$s) would occur.')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger prototype "%2$s", because a circular linkage (%3$s) would occur.');
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
+ $triggers[$triggerid]['description'], $triggers[$triggerid_up]['description'],
+ $circular_linkage
+ ));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Recursively check whether the trigger, which a dependency is being set on, produces a circular linkage.
+ *
+ * @param array $links[<triggerid_up>][<triggerid>]
+ * @param string $triggerid_up
+ * @param array $triggerids[<triggerid>]
+ * @param array $links_path Circular linkage path, collected performing the check.
+ *
+ * @return bool
+ */
+ private static function circularLinkageExists(array $links, string $triggerid_up, array $triggerids,
+ array &$links_path): bool {
+ if (array_key_exists($triggerid_up, $triggerids)) {
+ return true;
+ }
+
+ $_links_path = $links_path;
+
+ foreach ($triggerids as $triggerid => $foo) {
+ if (array_key_exists($triggerid, $links)) {
+ $links_path = $_links_path;
+ $triggerid_links = array_diff_key($links[$triggerid], $links_path);
+
+ if ($triggerid_links) {
+ $links_path[$triggerid] = true;
+
+ if (self::circularLinkageExists($links, $triggerid_up, $triggerid_links, $links_path)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get hosts data of all triggers given in trigger dependencies array.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ *
+ * @return array
+ */
+ public static function getTriggerHosts(array $trigger_dependencies): array {
+ $all_triggerids = $trigger_dependencies;
+
+ foreach ($trigger_dependencies as $triggerids) {
+ $all_triggerids += $triggerids;
+ }
+
+ $result = DBselect(
+ 'SELECT DISTINCT f.triggerid,h.hostid,h.status'.
+ ' FROM functions f,items i,hosts h'.
+ ' WHERE f.itemid=i.itemid'.
+ ' AND i.hostid=h.hostid'.
+ ' AND '.dbConditionId('f.triggerid', array_keys($all_triggerids))
+ );
+
+ $trigger_hosts = [];
+
+ while ($row = DBfetch($result)) {
+ // Each trigger can have either only templateids or only hostids.
+ if ($row['status'] == HOST_STATUS_TEMPLATE) {
+ $trigger_hosts[$row['triggerid']]['templateids'][$row['hostid']] = true;
+ }
+ else {
+ $trigger_hosts[$row['triggerid']]['hostids'][$row['hostid']] = true;
+ }
+ }
+
+ return $trigger_hosts;
+ }
+
+ /**
+ * Check whether the trigger dependencies are correctly set for host triggers.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $trigger_hosts
+ *
+ * @throws APIException
+ */
+ public static function checkDependenciesOfHostTriggers(array $trigger_dependencies, array $trigger_hosts): void {
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ if (array_key_exists('templateids', $trigger_hosts[$triggerid_up])) {
+ foreach ($triggerids as $triggerid => $foo) {
+ if (array_key_exists('hostids', $trigger_hosts[$triggerid])) {
+ $triggers = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => [$triggerid, $triggerid_up],
+ 'preservekeys' => true
+ ]);
+
+ $error = ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s", because dependencies of host triggers on template triggers are not allowed.')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s", because dependencies of host triggers on template triggers are not allowed.');
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $triggers[$triggerid]['description'],
+ $triggers[$triggerid_up]['description']
+ ));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Check whether the trigger dependencies are correctly set for template triggers.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $trigger_hosts
+ *
+ * @throws APIException
+ */
+ public static function checkDependenciesOfTemplateTriggers(array $trigger_dependencies,
+ array $trigger_hosts): void {
+ /*
+ * From the given trigger dependencies we should keep only the dependencies of the template triggers. There is
+ * also no need to check the dependencies on triggers from the same template, because that is considered a
+ * valid case. Thus, among the triggers that the template triggers depends on, we should only keep triggers from
+ * other templates and hosts.
+ */
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ $templateids_up = array_key_exists('templateids', $trigger_hosts[$triggerid_up])
+ ? $trigger_hosts[$triggerid_up]['templateids']
+ : [];
+
+ foreach ($triggerids as $triggerid => $foo) {
+ /*
+ * If trigger-up and dependent trigger have at least one common template, even if they also have
+ * other templates, it is important to understand that at the moment of the trigger dependency
+ * validation all of those different templates are already linked to all child templates,
+ * so there is no need to check them again.
+ */
+ if (!array_key_exists('templateids', $trigger_hosts[$triggerid])
+ || array_intersect_key($templateids_up, $trigger_hosts[$triggerid]['templateids'])) {
+ unset($trigger_dependencies[$triggerid_up][$triggerid]);
+ }
+ }
+
+ if (!$trigger_dependencies[$triggerid_up]) {
+ unset($trigger_dependencies[$triggerid_up]);
+ }
+ }
+
+ if (!$trigger_dependencies) {
+ return;
+ }
+
+ self::checkTriggersUpNotFromParentTemplates($trigger_dependencies, $trigger_hosts);
+ self::checkTriggersUpNotFromChildTemplatesOrHosts($trigger_dependencies, $trigger_hosts);
+ self::checkTriggersUpTemplatesAreLinkedToChildTemplates($trigger_dependencies, $trigger_hosts);
+ }
+
+ /**
+ * Check that the triggers-up of the given trigger dependencies do not come from parent templates of dependent
+ * triggers.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $trigger_hosts
+ *
+ * @throws APIException
+ */
+ private static function checkTriggersUpNotFromParentTemplates(array $trigger_dependencies, array $trigger_hosts): void {
+ $templateids = [];
+ $template_triggers_up = [];
+ $dependency_templates = [];
+
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ if (!array_key_exists('templateids', $trigger_hosts[$triggerid_up])) {
+ continue;
+ }
+
+ $templateids_up = [];
+
+ foreach ($trigger_hosts[$triggerid_up]['templateids'] as $templateid => $foo) {
+ $template_triggers_up[$templateid][] = $triggerid_up;
+ $templateids_up[$templateid] = true;
+ }
+
+ foreach ($triggerids as $triggerid => $foo) {
+ foreach ($trigger_hosts[$triggerid]['templateids'] as $templateid => $foo) {
+ if (!array_key_exists($templateid, $dependency_templates)) {
+ $dependency_templates[$templateid] = [];
+ }
+
+ $dependency_templates[$templateid] += $templateids_up;
+ }
+
+ $templateids += $trigger_hosts[$triggerid]['templateids'];
+ }
+ }
+
+ $_templateids = $templateids;
+ $template_links = [];
+
+ do {
+ $options = [
+ 'output' => ['hostid', 'templateid'],
+ 'filter' => ['hostid' => array_keys($_templateids)]
+ ];
+ $result = DBselect(DB::makeSql('hosts_templates', $options));
+
+ $_templateids = [];
+
+ while ($row = DBfetch($result)) {
+ $template_links[$row['hostid']][$row['templateid']] = true;
+
+ if (!array_key_exists($row['templateid'], $template_links)) {
+ $_templateids[$row['templateid']] = true;
+ }
+ }
+ } while ($_templateids);
+
+ if (!$template_links) {
+ return;
+ }
+
+ // Check if the trigger-up is a trigger from the parent template of the dependent trigger.
+ foreach ($dependency_templates as $templateid => $templateids_up) {
+ if (array_key_exists($templateid, $template_links)) {
+ foreach ($templateids_up as $templateid_up => $foo) {
+ if (self::checkTemplateUpExistsInTemplateLinks($template_links, $templateid, $templateid_up)) {
+ foreach ($template_triggers_up[$templateid_up] as $triggerid_up) {
+ foreach ($trigger_dependencies[$triggerid_up] as $triggerid => $foo) {
+ if (array_key_exists($templateid, $trigger_hosts[$triggerid]['templateids'])) {
+ break 2;
+ }
+ }
+ }
+
+ $triggers = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => [$triggerid, $triggerid_up],
+ 'preservekeys' => true
+ ]);
+
+ $templates = DB::select('hosts', [
+ 'output' => ['host'],
+ 'hostids' => $templateid_up
+ ]);
+
+ $error = ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s" from the template "%3$s", because dependencies on triggers from the parent template are not allowed.')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s" from the template "%3$s", because dependencies on triggers from the parent template are not allowed.');
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $triggers[$triggerid]['description'],
+ $triggers[$triggerid_up]['description'], $templates[0]['host']
+ ));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Check that the triggers-up of the given trigger dependencies are not from the child templates or hosts of the
+ * dependent triggers.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $trigger_hosts
+ *
+ * @throws APIException
+ */
+ private static function checkTriggersUpNotFromChildTemplatesOrHosts(array $trigger_dependencies,
+ array $trigger_hosts): void {
+ $templateids = [];
+ $template_triggers_up = [];
+ $dependency_templates = [];
+
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ $templateids_up = [];
+
+ if (array_key_exists('templateids', $trigger_hosts[$triggerid_up])) {
+ foreach ($trigger_hosts[$triggerid_up]['templateids'] as $templateid => $foo) {
+ $template_triggers_up[$templateid][] = $triggerid_up;
+ $templateids_up[$templateid] = true;
+ }
+ }
+ else {
+ foreach ($trigger_hosts[$triggerid_up]['hostids'] as $hostid => $foo) {
+ $template_triggers_up[$hostid][] = $triggerid_up;
+ $templateids_up[$hostid] = true;
+ }
+ }
+
+ foreach ($triggerids as $triggerid => $foo) {
+ foreach ($trigger_hosts[$triggerid]['templateids'] as $templateid => $foo) {
+ if (!array_key_exists($templateid, $dependency_templates)) {
+ $dependency_templates[$templateid] = [];
+ }
+
+ $dependency_templates[$templateid] += $templateids_up;
+ }
+
+ $templateids += $trigger_hosts[$triggerid]['templateids'];
+ }
+ }
+
+ $template_links = [];
+
+ do {
+ $options = [
+ 'output' => ['hostid', 'templateid'],
+ 'filter' => ['templateid' => array_keys($templateids)]
+ ];
+ $result = DBselect(DB::makeSql('hosts_templates', $options));
+
+ $templateids = [];
+
+ while ($row = DBfetch($result)) {
+ $template_links[$row['templateid']][$row['hostid']] = true;
+
+ if (!array_key_exists($row['hostid'], $template_links)) {
+ $templateids[$row['hostid']] = true;
+ }
+ }
+ } while ($templateids);
+
+ if (!$template_links) {
+ return;
+ }
+
+ // Check if each trigger-up is a trigger from the child template or host of the dependent trigger.
+ foreach ($dependency_templates as $templateid => $templateids_up) {
+ if (array_key_exists($templateid, $template_links)) {
+ foreach ($templateids_up as $templateid_up => $foo) {
+ if (self::checkTemplateUpExistsInTemplateLinks($template_links, $templateid, $templateid_up)) {
+ foreach ($template_triggers_up[$templateid_up] as $triggerid_up) {
+ foreach ($trigger_dependencies[$triggerid_up] as $triggerid => $foo) {
+ if (array_key_exists($templateid, $trigger_hosts[$triggerid]['templateids'])) {
+ break 2;
+ }
+ }
+ }
+
+ $triggers = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => [$triggerid, $triggerid_up],
+ 'preservekeys' => true
+ ]);
+
+ $templates = DB::select('hosts', [
+ 'output' => ['host', 'status'],
+ 'hostids' => $templateid_up
+ ]);
+
+ if ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) {
+ $error = ($templates[0]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s" from the template "%3$s", because dependencies on triggers from a child template or host are not allowed.')
+ : _('Trigger "%1$s" cannot depend on the trigger "%2$s" from the host "%3$s", because dependencies on triggers from a child template or host are not allowed.');
+ }
+ else {
+ $error = ($templates[0]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s" from the template "%3$s", because dependencies on triggers from a child template or host are not allowed.')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s" from the host "%3$s", because dependencies on triggers from a child template or host are not allowed.');
+ }
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $triggers[$triggerid]['description'],
+ $triggers[$triggerid_up]['description'], $templates[0]['host']
+ ));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Recursively check if the given template-up exists in the chain of the given template links.
+ *
+ * @param array $template_links[<templateid>][<templateid_up>]
+ * @param string $templateid
+ * @param string $templateid_up
+ *
+ * @return bool
+ */
+ private static function checkTemplateUpExistsInTemplateLinks(array $template_links, string $templateid,
+ string $templateid_up): bool {
+ if (array_key_exists($templateid_up, $template_links[$templateid])) {
+ return true;
+ }
+
+ foreach ($template_links[$templateid] as $_templateid => $foo) {
+ if (array_key_exists($_templateid, $template_links)
+ && self::checkTemplateUpExistsInTemplateLinks($template_links, $_templateid, $templateid_up)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the triggers-up templates of the given trigger dependencies are linked to child templates of the
+ * dependent triggers.
+ *
+ * @param array $trigger_dependencies[<triggerid_up>][<triggerid>]
+ * @param array $trigger_hosts
+ *
+ * @throws APIException
+ */
+ private static function checkTriggersUpTemplatesAreLinkedToChildTemplates(array $trigger_dependencies,
+ array $trigger_hosts): void {
+ $templateids_up = [];
+ $templateids = [];
+
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ if (!array_key_exists('templateids', $trigger_hosts[$triggerid_up])) {
+ unset($trigger_dependencies[$triggerid_up]);
+ continue;
+ }
+
+ $templateids_up += $trigger_hosts[$triggerid_up]['templateids'];
+
+ foreach ($triggerids as $triggerid => $foo) {
+ $templateids += $trigger_hosts[$triggerid]['templateids'];
+ }
+ }
+
+ $options = [
+ 'output' => ['templateid', 'hostid'],
+ 'filter' => [
+ 'templateid' => array_keys($templateids)
+ ]
+ ];
+ $result = DBselect(DB::makeSql('hosts_templates', $options));
+
+ $template_links = [];
+
+ while ($row = DBfetch($result)) {
+ $template_links[$row['templateid']][$row['hostid']] = [];
+ }
+
+ $result = DBselect(
+ 'SELECT ht.templateid,ht.hostid,htt.templateid AS host_templateid'.
+ ' FROM hosts_templates ht,hosts_templates htt'.
+ ' WHERE ht.hostid=htt.hostid'.
+ ' AND ht.templateid!=htt.templateid'.
+ ' AND '.dbConditionId('ht.templateid', array_keys($templateids)).
+ ' AND '.dbConditionId('htt.templateid', array_keys($templateids_up))
+ );
+
+ while ($row = DBfetch($result)) {
+ $template_links[$row['templateid']][$row['hostid']][$row['host_templateid']] = true;
+ }
+
+ foreach ($trigger_dependencies as $triggerid_up => $triggerids) {
+ /*
+ * If trigger belongs to more than one template, then it is not possible to link only part of them to
+ * another host or template. That means each template ID of that trigger would have the same hosts
+ * in template links. And vice versa, if at least one of the trigger templates was not found in template
+ * links, then the trigger is not inherited further.
+ */
+ $templateid_up = key($trigger_hosts[$triggerid_up]['templateids']);
+
+ foreach ($triggerids as $triggerid => $foo) {
+ $templateid = key($trigger_hosts[$triggerid]['templateids']);
+
+ if (!array_key_exists($templateid, $template_links)) {
+ continue;
+ }
+
+ foreach ($template_links[$templateid] as $hostid => $host_templateids) {
+ if (!array_key_exists($templateid_up, $host_templateids)) {
+ $triggers = DB::select('triggers', [
+ 'output' => ['description', 'flags'],
+ 'triggerids' => [$triggerid, $triggerid_up],
+ 'preservekeys' => true
+ ]);
+
+ $hosts = DB::select('hosts', [
+ 'output' => ['host', 'status'],
+ 'hostids' => [$templateid_up, $hostid],
+ 'preservekeys' => true
+ ]);
+
+ if ($triggers[$triggerid]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) {
+ $error = ($hosts[$hostid]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Trigger "%1$s" cannot depend on the trigger "%2$s", because the template "%3$s" is not linked to the template "%4$s".')
+ : _('Trigger "%1$s" cannot depend on the trigger "%2$s", because the template "%3$s" is not linked to the host "%4$s".');
+ }
+ else {
+ $error = ($hosts[$hostid]['status'] == HOST_STATUS_TEMPLATE)
+ ? _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s", because the template "%3$s" is not linked to the template "%4$s".')
+ : _('Trigger prototype "%1$s" cannot depend on the trigger "%2$s", because the template "%3$s" is not linked to the host "%4$s".');
+ }
+
+ self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $triggers[$triggerid]['description'],
+ $triggers[$triggerid_up]['description'], $hosts[$templateid_up]['host'],
+ $hosts[$hostid]['host']
+ ));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the trigger dependencies.
+ *
+ * @param array $triggers
+ * @param array|null $db_triggers
+ */
+ public static function updateDependencies(array &$triggers, array $db_triggers = null): void {
+ $ins_trigger_deps = [];
+ $del_triggerdepids = [];
+ $edit_dependencies = [];
+
+ foreach ($triggers as &$trigger) {
+ if (!array_key_exists('dependencies', $trigger)) {
+ continue;
+ }
+
+ $db_triggers_up = ($db_triggers !== null)
+ ? array_column($db_triggers[$trigger['triggerid']]['dependencies'], null, 'triggerid')
+ : [];
+
+ foreach ($trigger['dependencies'] as &$trigger_up) {
+ if (array_key_exists($trigger_up['triggerid'], $db_triggers_up)) {
+ $trigger_up['triggerdepid'] = $db_triggers_up[$trigger_up['triggerid']]['triggerdepid'];
+ unset($db_triggers_up[$trigger_up['triggerid']]);
+ }
+ else {
+ $ins_trigger_deps[] = [
+ 'triggerid_down' => $trigger['triggerid'],
+ 'triggerid_up' => $trigger_up['triggerid']
+ ];
+
+ $edit_dependencies[$trigger['triggerid']][$trigger_up['triggerid']] = true;
+ }
+ }
+ unset($trigger_up);
+
+ foreach ($db_triggers_up as $db_trigger_up) {
+ $del_triggerdepids[] = $db_trigger_up['triggerdepid'];
+
+ $edit_dependencies[$trigger['triggerid']][$db_trigger_up['triggerid']] = false;
+ }
+ }
+ unset($trigger);
+
+ if ($del_triggerdepids) {
+ DB::delete('trigger_depends', ['triggerdepid' => $del_triggerdepids]);
+ }
+
+ if ($ins_trigger_deps) {
+ $triggerdepids = DB::insertBatch('trigger_depends', $ins_trigger_deps);
+ }
+
+ foreach ($triggers as &$trigger) {
+ if (!array_key_exists('dependencies', $trigger)) {
+ continue;
+ }
+
+ foreach ($trigger['dependencies'] as &$trigger_up) {
+ if (!array_key_exists('triggerdepid', $trigger_up)) {
+ $trigger_up['triggerdepid'] = array_shift($triggerdepids);
+ }
+ }
+ unset($trigger_up);
+ }
+ unset($trigger);
+
+ if ($edit_dependencies) {
+ $edit_dependencies = self::getTemplatedDependencies($edit_dependencies);
+
+ if ($edit_dependencies) {
+ self::inheritDependencies($edit_dependencies);
+ }
+ }
+ }
+
+ /**
+ * Filter for dependencies whose dependent triggers belong to templates that are further linked
+ * to some templates or hosts.
+ *
+ * @param array $edit_dependencies[<triggerid>][<triggerid_up>]
+ *
+ * @return array
+ */
+ protected static function getTemplatedDependencies(array $edit_dependencies): array {
+ $triggerids = DBfetchColumn(DBselect(
+ 'SELECT DISTINCT f.triggerid'.
+ ' FROM functions f,items i,hosts h'.
+ ' WHERE f.itemid=i.itemid'.
+ ' AND i.hostid=h.hostid'.
+ ' AND '.dbConditionId('f.triggerid', array_keys($edit_dependencies)).
+ ' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE]).
+ ' AND EXISTS('.
+ 'SELECT NULL'.
+ ' FROM hosts_templates ht'.
+ ' WHERE ht.templateid=i.hostid'.
+ ')'
+ ), 'triggerid');
+
+ return array_intersect_key($edit_dependencies, array_flip($triggerids));
+ }
+
+ /**
+ * Inherit the given trigger dependencies.
+ *
+ * @param array $edit_dependencies[<triggerid>][<triggerid_up>]
+ * @param array $hostids
+ */
+ protected static function inheritDependencies(array $edit_dependencies, array $hostids = null): void {
+ $all_triggerids = $edit_dependencies;
+ $all_triggerids_up = [];
+
+ foreach ($edit_dependencies as $triggerids_up) {
+ $all_triggerids += $triggerids_up;
+ $all_triggerids_up += $triggerids_up;
+ }
+
+ $hostids_condition = ($hostids !== null) ? ' AND '.dbConditionId('i.hostid', $hostids) : '';
+
+ $result = DBselect(
+ 'SELECT DISTINCT t.templateid,t.triggerid,t.flags,i.hostid'.
+ ' FROM triggers t,functions f,items i'.
+ ' WHERE t.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionId('t.templateid', array_keys($all_triggerids)).
+ $hostids_condition
+ );
+
+ $trigger_flags = [];
+ $trigger_links = [];
+ $tpl_triggerids_up = [];
+
+ while ($row = DBfetch($result)) {
+ $trigger_links[$row['templateid']][$row['hostid']] = $row['triggerid'];
+ $trigger_flags[$row['triggerid']] = $row['flags'];
+
+ if (array_key_exists($row['templateid'], $all_triggerids_up)) {
+ $tpl_triggerids_up[$row['templateid']] = true;
+ }
+ }
+
+ $del_triggerdepids = [];
+ $_edit_dependencies = [];
+
+ if ($tpl_triggerids_up) {
+ if ($hostids === null) {
+ $result = DBselect(
+ 'SELECT DISTINCT t.triggerid,td.triggerid_up,td.triggerdepid,i.hostid'.
+ ' FROM triggers t,trigger_depends td,triggers tt,functions f,items i'.
+ ' WHERE t.triggerid=td.triggerid_down'.
+ ' AND td.triggerid_up=tt.triggerid'.
+ ' AND tt.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND '.dbConditionId('t.templateid', array_keys($edit_dependencies)).
+ ' AND '.dbConditionId('tt.templateid', array_keys($tpl_triggerids_up))
+ );
+ }
+ else {
+ $result = DBselect(
+ 'SELECT DISTINCT t.triggerid,td.triggerid_up,td.triggerdepid,ii.hostid'.
+ ' FROM triggers t,functions f,items i,trigger_depends td,triggers tt,functions ff,items ii'.
+ ' WHERE t.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND t.triggerid=td.triggerid_down'.
+ ' AND td.triggerid_up=tt.triggerid'.
+ ' AND tt.triggerid=ff.triggerid'.
+ ' AND ff.itemid=ii.itemid'.
+ ' AND '.dbConditionId('t.templateid', array_keys($edit_dependencies)).
+ ' AND '.dbConditionId('i.hostid', $hostids).
+ ' AND '.dbConditionId('tt.templateid', array_keys($tpl_triggerids_up))
+ );
+ }
+
+ $tpl_child_dependencies = [];
+
+ while ($row = DBfetch($result)) {
+ $tpl_child_dependencies[$row['triggerid']][$row['triggerid_up']][$row['hostid']] = $row['triggerdepid'];
+ }
+
+ foreach ($edit_dependencies as $triggerid => $triggerids_up) {
+ $triggerids_up = array_intersect_key($triggerids_up, $tpl_triggerids_up);
+
+ if (!$triggerids_up) {
+ continue;
+ }
+
+ foreach ($trigger_links[$triggerid] as $hostid => $child_triggerid) {
+ $upd_child_triggerids_up = [];
+
+ if (array_key_exists($child_triggerid, $tpl_child_dependencies)) {
+ foreach ($tpl_child_dependencies[$child_triggerid] as $child_triggerid_up => $hostids_up) {
+ $hostid_up = key($hostids_up);
+ $triggerdepid = reset($hostids_up);
+
+ if (in_array($child_triggerid_up, $upd_child_triggerids_up)
+ || in_array($triggerdepid, $del_triggerdepids)) {
+ continue;
+ }
+
+ foreach ($triggerids_up as $triggerid_up => $add) {
+ if (array_key_exists($hostid_up, $trigger_links[$triggerid_up])
+ && bccomp($child_triggerid_up, $trigger_links[$triggerid_up][$hostid_up]) == 0) {
+ if ($add) {
+ $upd_child_triggerids_up[] = $child_triggerid_up;
+ }
+ else {
+ $del_triggerdepids[] = $hostids_up[$hostid_up];
+
+ $_edit_dependencies[$child_triggerid][$child_triggerid_up] = false;
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($triggerids_up as $triggerid_up => $add) {
+ if ($add) {
+ $child_triggerid_up = $trigger_links[$triggerid_up][$hostid];
+
+ if (!in_array($child_triggerid_up, $upd_child_triggerids_up)) {
+ $_edit_dependencies[$child_triggerid][$child_triggerid_up] = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $host_triggerids_up = array_diff_key($all_triggerids_up, $tpl_triggerids_up);
+
+ if ($host_triggerids_up) {
+ if ($hostids === null) {
+ $result = DBselect(
+ 'SELECT DISTINCT t.triggerid,td.triggerid_up,td.triggerdepid'.
+ ' FROM triggers t,trigger_depends td'.
+ ' WHERE t.triggerid=td.triggerid_down'.
+ ' AND '.dbConditionId('t.templateid', array_keys($edit_dependencies)).
+ ' AND '.dbConditionId('td.triggerid_up', array_keys($host_triggerids_up))
+ );
+ }
+ else {
+ $result = DBselect(
+ 'SELECT DISTINCT t.triggerid,td.triggerid_up,td.triggerdepid'.
+ ' FROM triggers t,functions f,items i,trigger_depends td'.
+ ' WHERE t.triggerid=f.triggerid'.
+ ' AND f.itemid=i.itemid'.
+ ' AND t.triggerid=td.triggerid_down'.
+ ' AND '.dbConditionId('t.templateid', array_keys($edit_dependencies)).
+ ' AND '.dbConditionId('i.hostid', $hostids).
+ ' AND '.dbConditionId('td.triggerid_up', array_keys($host_triggerids_up))
+ );
+ }
+
+ $host_child_dependencies = [];
+
+ while ($row = DBfetch($result)) {
+ $host_child_dependencies[$row['triggerid']][$row['triggerid_up']] = $row['triggerdepid'];
+ }
+
+ foreach ($edit_dependencies as $triggerid => $triggerids_up) {
+ $triggerids_up = array_intersect_key($triggerids_up, $host_triggerids_up);
+
+ if (!$triggerids_up) {
+ continue;
+ }
+
+ foreach ($trigger_links[$triggerid] as $hostid => $child_triggerid) {
+ $upd_child_triggerids_up = [];
+
+ if (array_key_exists($child_triggerid, $host_child_dependencies)) {
+ foreach ($host_child_dependencies[$child_triggerid] as $child_triggerid_up => $triggerdepid) {
+ if (array_key_exists($child_triggerid_up, $triggerids_up)) {
+ $add = $triggerids_up[$child_triggerid_up];
+
+ if ($add) {
+ $upd_child_triggerids_up[] = $child_triggerid_up;
+ }
+ else {
+ $del_triggerdepids[] = $triggerdepid;
+
+ $_edit_dependencies[$child_triggerid][$child_triggerid_up] = false;
+ }
+ }
+ }
+ }
+
+ foreach ($triggerids_up as $triggerid_up => $add) {
+ if ($add && !in_array($triggerid_up, $upd_child_triggerids_up)) {
+ $_edit_dependencies[$child_triggerid][$triggerid_up] = true;
+ }
+ }
+ }
+ }
+ }
+
+ $ins_dependencies = [];
+ $del_dependencies = [];
+ $ins_trigger_deps = [];
+
+ foreach ($_edit_dependencies as $triggerid => $triggerids_up) {
+ foreach ($triggerids_up as $triggerid_up => $add) {
+ if ($add) {
+ if ($trigger_flags[$triggerid] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
+ if ($trigger_flags[$triggerid_up] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
+ $ins_dependencies[$triggerid_up][$triggerid] = true;
+ }
+ }
+ else {
+ $ins_dependencies[$triggerid_up][$triggerid] = true;
+ }
+
+ $ins_trigger_deps[] = [
+ 'triggerid_down' => $triggerid,
+ 'triggerid_up' => $triggerid_up
+ ];
+ }
+ else {
+ $del_dependencies[$triggerid_up][$triggerid] = true;
+ }
+ }
+ }
+
+ if ($ins_dependencies) {
+ self::checkCircularDependencies($ins_dependencies, $del_dependencies, true);
+ }
+
+ if ($del_triggerdepids) {
+ DB::delete('trigger_depends', ['triggerdepid' => $del_triggerdepids]);
+ }
+
+ if ($ins_trigger_deps) {
+ DB::insertBatch('trigger_depends', $ins_trigger_deps);
+ }
+
+ if ($_edit_dependencies) {
+ $_edit_dependencies = self::getTemplatedDependencies($_edit_dependencies);
+
+ if ($_edit_dependencies) {
+ self::inheritDependencies($_edit_dependencies);
+ }
+ }
+ }
+
+ /**
+ * Inherit the trigger dependencies of the given templates to the given hosts.
+ *
+ * @param array $templateids
+ * @param array $hostids
+ */
+ public static function syncTemplateDependencies(array $templateids, array $hostids): void {
+ $result = DBselect(
+ 'SELECT DISTINCT f.triggerid,td.triggerid_up'.
+ ' FROM items i,functions f,trigger_depends td'.
+ ' WHERE i.itemid=f.itemid'.
+ ' AND f.triggerid=td.triggerid_down'.
+ ' AND '.dbConditionId('i.hostid', $templateids)
+ );
+
+ $edit_dependencies = [];
+
+ while ($row = DBfetch($result)) {
+ $edit_dependencies[$row['triggerid']][$row['triggerid_up']] = true;
+ }
+
+ if ($edit_dependencies) {
+ $edit_dependencies = self::getTemplatedDependencies($edit_dependencies);
+
+ if ($edit_dependencies) {
+ self::inheritDependencies($edit_dependencies, $hostids);
+ }
+ }
+ }
}
diff --git a/ui/include/classes/api/services/CTriggerPrototype.php b/ui/include/classes/api/services/CTriggerPrototype.php
index 3ffc3760604..3ccaa2db145 100644
--- a/ui/include/classes/api/services/CTriggerPrototype.php
+++ b/ui/include/classes/api/services/CTriggerPrototype.php
@@ -421,22 +421,13 @@ class CTriggerPrototype extends CTriggerGeneral {
*/
public function create(array $trigger_prototypes) {
$this->validateCreate($trigger_prototypes);
- $this->createReal($trigger_prototypes);
- $this->inherit($trigger_prototypes);
- $addDependencies = false;
+ $this->createReal($trigger_prototypes);
+ $this->checkDependenciesLinks($trigger_prototypes);
- foreach ($trigger_prototypes as $trigger_prototype) {
- if (isset($trigger_prototype['dependencies']) && is_array($trigger_prototype['dependencies'])
- && $trigger_prototype['dependencies']) {
- $addDependencies = true;
- break;
- }
- }
+ $this->inherit($trigger_prototypes);
- if ($addDependencies) {
- $this->addDependencies($trigger_prototypes);
- }
+ $this->updateDependencies($trigger_prototypes);
return ['triggerids' => zbx_objectValues($trigger_prototypes, 'triggerid')];
}
@@ -448,25 +439,15 @@ class CTriggerPrototype extends CTriggerGeneral {
*
* @return array
*/
- public function update(array $trigger_prototypes) {
+ public function update(array $trigger_prototypes): array {
$this->validateUpdate($trigger_prototypes, $db_triggers);
+
$this->updateReal($trigger_prototypes, $db_triggers);
$this->inherit($trigger_prototypes);
- $updateDependencies = false;
-
- foreach ($trigger_prototypes as $trigger_prototype) {
- if (isset($trigger_prototype['dependencies']) && is_array($trigger_prototype['dependencies'])) {
- $updateDependencies = true;
- break;
- }
- }
-
- if ($updateDependencies) {
- $this->updateDependencies($trigger_prototypes);
- }
+ $this->updateDependencies($trigger_prototypes, $db_triggers);
- return ['triggerids' => zbx_objectValues($trigger_prototypes, 'triggerid')];
+ return ['triggerids' => array_column($trigger_prototypes, 'triggerid')];
}
/**
@@ -529,539 +510,6 @@ class CTriggerPrototype extends CTriggerGeneral {
}
/**
- * Update the given dependencies and inherit them on all child triggers.
- *
- * @param array $triggerPrototypes
- */
- protected function updateDependencies(array $triggerPrototypes) {
- $this->deleteDependencies($triggerPrototypes);
-
- $this->addDependencies($triggerPrototypes);
- }
-
- /**
- * Deletes all trigger and trigger prototype dependencies from the given trigger prototypes and their children.
- *
- * @param array $triggerPrototypes
- * @param string $triggerPrototypes[]['triggerid']
- */
- protected function deleteDependencies(array $triggerPrototypes) {
- $triggerPrototypeIds = zbx_objectValues($triggerPrototypes, 'triggerid');
-
- try {
- // Delete the dependencies from the child trigger prototypes.
-
- $childTriggerPrototypes = API::getApiService()->select($this->tableName(), [
- 'output' => ['triggerid'],
- 'filter' => [
- 'templateid' => $triggerPrototypeIds
- ]
- ]);
-
- if ($childTriggerPrototypes) {
- $this->deleteDependencies($childTriggerPrototypes);
- }
-
- DB::delete('trigger_depends', [
- 'triggerid_down' => $triggerPrototypeIds
- ]);
- }
- catch (APIException $e) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete dependency'));
- }
- }
-
- /**
- * Add the given dependencies and inherit them on all child triggers.
- *
- * @param array $triggerPrototypes
- * @param string $triggerPrototypes[]['triggerid']
- * @param array $triggerPrototypes[]['dependencies']
- * @param string $triggerPrototypes[]['dependencies'][]['triggerid']
- * @param bool $inherited Determines either to check permissions for
- * added dependencies. Permissions are not
- * validated for inherited triggers.
- */
- public function addDependencies(array $triggerPrototypes, bool $inherited = false) {
- $this->validateAddDependencies($triggerPrototypes, $inherited);
-
- $insert = [];
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- if (!array_key_exists('dependencies', $triggerPrototype)) {
- continue;
- }
-
- foreach ($triggerPrototype['dependencies'] as $dependency) {
- $insert[] = [
- 'triggerid_down' => $triggerPrototype['triggerid'],
- 'triggerid_up' => $dependency['triggerid']
- ];
- }
- }
-
- DB::insertBatch('trigger_depends', $insert);
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- // Propagate the dependencies to the child triggers.
-
- $childTriggers = API::getApiService()->select($this->tableName(), [
- 'output' => ['triggerid'],
- 'filter' => [
- 'templateid' => $triggerPrototype['triggerid']
- ]
- ]);
-
- if ($childTriggers) {
- foreach ($childTriggers as &$childTrigger) {
- $childTrigger['dependencies'] = [];
- $childHostsQuery = get_hosts_by_triggerid($childTrigger['triggerid']);
-
- while ($childHost = DBfetch($childHostsQuery)) {
- foreach ($triggerPrototype['dependencies'] as $dependency) {
- $newDependency = [$childTrigger['triggerid'] => $dependency['triggerid']];
- $newDependency = replace_template_dependencies($newDependency, $childHost['hostid']);
-
- $childTrigger['dependencies'][] = [
- 'triggerid' => $newDependency[$childTrigger['triggerid']]
- ];
- }
- }
- }
- unset($childTrigger);
-
- $this->addDependencies($childTriggers, true);
- }
- }
- }
-
- /**
- * Validates the input for the addDependencies() method.
- *
- * @param array $trigger_prototypes
- * @param string $trigger_prototypes[]['triggerid']
- * @param array $trigger_prototypes[]['dependencies']
- * @param string $trigger_prototypes[]['dependencies'][]['triggerid']
- * @param bool $inherited
- *
- * @throws APIException if the given dependencies are invalid.
- */
- protected function validateAddDependencies(array $trigger_prototypes, bool $inherited = false): void {
- $depTriggerIds = [];
-
- foreach ($trigger_prototypes as $trigger_prototype) {
- if (!array_key_exists('dependencies', $trigger_prototype)) {
- continue;
- }
-
- foreach ($trigger_prototype['dependencies'] as $dependency) {
- $depTriggerIds[$dependency['triggerid']] = $dependency['triggerid'];
- }
- }
-
- if (!$depTriggerIds) {
- return;
- }
-
- // Check if given IDs are actual trigger prototypes and get discovery rules if they are.
- $depTriggerPrototypes = $this->get([
- 'output' => ['triggerid'],
- 'selectDiscoveryRule' => ['itemid'],
- 'triggerids' => $depTriggerIds,
- 'nopermissions' => $inherited ? true : null,
- 'preservekeys' => true
- ]);
-
- $dep_triggerids = array_diff($depTriggerIds, array_keys($depTriggerPrototypes));
-
- if ($depTriggerPrototypes) {
- // Get current trigger prototype discovery rules.
- $dRules = $this->get([
- 'output' => ['triggerid'],
- 'selectDiscoveryRule' => ['itemid'],
- 'triggerids' => zbx_objectValues($trigger_prototypes, 'triggerid'),
- 'nopermissions' => $inherited ? true : null,
- 'preservekeys' => true
- ]);
-
- foreach ($trigger_prototypes as $trigger_prototype) {
- if (!array_key_exists('dependencies', $trigger_prototype)) {
- continue;
- }
-
- $dRuleId = $dRules[$trigger_prototype['triggerid']]['discoveryRule']['itemid'];
-
- // Check if current trigger prototype rules match dependent trigger prototype rules.
- foreach ($trigger_prototype['dependencies'] as $dependency) {
- if (isset($depTriggerPrototypes[$dependency['triggerid']])) {
- $depTriggerDRuleId = $depTriggerPrototypes[$dependency['triggerid']]['discoveryRule']['itemid'];
-
- if (bccomp($depTriggerDRuleId, $dRuleId) != 0) {
- self::exception(ZBX_API_ERROR_PERMISSIONS,
- _('No permissions to referred object or it does not exist!')
- );
- }
- }
- }
- }
- }
- elseif (!$dep_triggerids) {
- self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
- }
-
- if ($dep_triggerids && !$inherited) {
- // Check other dependency IDs if those are normal triggers.
- $count = API::Trigger()->get([
- 'countOutput' => true,
- 'triggerids' => $dep_triggerids,
- 'filter' => [
- 'flags' => [ZBX_FLAG_DISCOVERY_NORMAL]
- ]
- ]);
-
- if ($count != count($dep_triggerids)) {
- self::exception(ZBX_API_ERROR_PERMISSIONS,
- _('No permissions to referred object or it does not exist!')
- );
- }
- }
-
- $this->checkDependencies($trigger_prototypes);
- $this->checkDependencyParents($trigger_prototypes);
- $this->checkDependencyDuplicates($trigger_prototypes);
- }
-
- /**
- * Check the dependencies of the given trigger prototypes.
- *
- * @param array $triggerPrototypes
- * @param string $triggerPrototypes[]['triggerid']
- * @param array $triggerPrototypes[]['dependencies']
- * @param string $triggerPrototypes[]['dependencies'][]['triggerid']
- *
- * @throws APIException if any of the dependencies are invalid.
- */
- protected function checkDependencies(array $triggerPrototypes) {
- $triggerPrototypes = zbx_toHash($triggerPrototypes, 'triggerid');
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- if (!array_key_exists('dependencies', $triggerPrototype)) {
- continue;
- }
-
- $triggerid_down = $triggerPrototype['triggerid'];
- $triggerids_up = zbx_objectValues($triggerPrototype['dependencies'], 'triggerid');
-
- foreach ($triggerids_up as $triggerid_up) {
- if (bccomp($triggerid_down, $triggerid_up) == 0) {
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _('Cannot create dependency on trigger prototype itself.')
- );
- }
- }
- }
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- if (!array_key_exists('dependencies', $triggerPrototype)) {
- continue;
- }
-
- $depTriggerIds = zbx_objectValues($triggerPrototype['dependencies'], 'triggerid');
-
- $triggerTemplates = API::Template()->get([
- 'output' => ['hostid', 'status'],
- 'triggerids' => [$triggerPrototype['triggerid']],
- 'nopermissions' => true
- ]);
-
- if (!$triggerTemplates) {
- // Current trigger prototype belongs to a host, so forbid dependencies from a host to a template.
-
- $triggerDepTemplates = API::Template()->get([
- 'output' => ['templateid'],
- 'triggerids' => $depTriggerIds,
- 'nopermissions' => true,
- 'limit' => 1
- ]);
-
- if ($triggerDepTemplates) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot add dependency from a host to a template.'));
- }
- }
-
- // check circular dependency
- $downTriggerIds = [$triggerPrototype['triggerid']];
- do {
- // triggerid_down depends on triggerid_up
- $res = DBselect(
- 'SELECT td.triggerid_up'.
- ' FROM trigger_depends td'.
- ' WHERE '.dbConditionInt('td.triggerid_down', $downTriggerIds)
- );
-
- // combine db dependencies with those to be added
- $upTriggersIds = [];
- while ($row = DBfetch($res)) {
- $upTriggersIds[] = $row['triggerid_up'];
- }
- foreach ($downTriggerIds as $id) {
- if (isset($triggerPrototypes[$id]) && isset($triggerPrototypes[$id]['dependencies'])) {
- $upTriggersIds = array_merge($upTriggersIds,
- zbx_objectValues($triggerPrototypes[$id]['dependencies'], 'triggerid')
- );
- }
- }
-
- // if found trigger id is in dependent triggerids, there is a dependency loop
- $downTriggerIds = [];
- foreach ($upTriggersIds as $id) {
- if (bccomp($id, $triggerPrototype['triggerid']) == 0) {
- self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot create circular dependencies.'));
- }
- $downTriggerIds[] = $id;
- }
- } while (!empty($downTriggerIds));
-
- // fetch all templates that are used in dependencies
- $triggerDepTemplates = API::Template()->get([
- 'output' => ['templateid'],
- 'triggerids' => $depTriggerIds,
- 'nopermissions' => true,
- 'preservekeys' => true
- ]);
-
- $depTemplateIds = array_keys($triggerDepTemplates);
-
- // run the check only if a templated trigger has dependencies on other templates
- $triggerTemplateIds = zbx_toHash(zbx_objectValues($triggerTemplates, 'templateid'));
- $tdiff = array_diff($depTemplateIds, $triggerTemplateIds);
-
- if (!empty($triggerTemplateIds) && !empty($depTemplateIds) && !empty($tdiff)) {
- $affectedTemplateIds = zbx_array_merge($triggerTemplateIds, $depTemplateIds);
-
- // create a list of all hosts, that are children of the affected templates
- $dbLowlvltpl = DBselect(
- 'SELECT DISTINCT ht.templateid,ht.hostid,h.host'.
- ' FROM hosts_templates ht,hosts h'.
- ' WHERE h.hostid=ht.hostid'.
- ' AND '.dbConditionInt('ht.templateid', $affectedTemplateIds)
- );
- $map = [];
- while ($lowlvltpl = DBfetch($dbLowlvltpl)) {
- if (!isset($map[$lowlvltpl['hostid']])) {
- $map[$lowlvltpl['hostid']] = [];
- }
- $map[$lowlvltpl['hostid']][$lowlvltpl['templateid']] = $lowlvltpl['host'];
- }
-
- // check that if some host is linked to the template, that the trigger belongs to,
- // the host must also be linked to all of the templates, that trigger dependencies point to
- foreach ($map as $templates) {
- foreach ($triggerTemplateIds as $triggerTemplateId) {
- // is the host linked to one of the trigger templates?
- if (isset($templates[$triggerTemplateId])) {
- // then make sure all of the dependency templates are also linked
- foreach ($depTemplateIds as $depTemplateId) {
- if (!isset($templates[$depTemplateId])) {
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Not all templates are linked to "%1$s".', reset($templates))
- );
- }
- }
- break;
- }
- }
- }
- }
- }
- }
-
- /**
- * Check that none of the triggers have dependencies on their children. Checks only one level of inheritance, but
- * since it is called on each inheritance step, also works for multiple inheritance levels.
- *
- * @param array $triggerPrototypes
- * @param string $triggerPrototypes[]['triggerid']
- * @param array $triggerPrototypes[]['dependencies']
- * @param string $triggerPrototypes[]['dependencies'][]['triggerid']
- *
- * @throws APIException if at least one trigger is dependent on its child.
- */
- protected function checkDependencyParents(array $triggerPrototypes) {
- // fetch all templated dependency trigger parents
- $depTriggerIds = [];
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- if (!array_key_exists('dependencies', $triggerPrototype)) {
- continue;
- }
-
- foreach ($triggerPrototype['dependencies'] as $dependency) {
- $depTriggerIds[$dependency['triggerid']] = $dependency['triggerid'];
- }
- }
-
- $parentDepTriggers = DBfetchArray(DBSelect(
- 'SELECT templateid,triggerid'.
- ' FROM triggers'.
- ' WHERE templateid>0'.
- ' AND '.dbConditionInt('triggerid', $depTriggerIds)
- ));
-
- if ($parentDepTriggers) {
- $parentDepTriggers = zbx_toHash($parentDepTriggers, 'triggerid');
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- foreach ($triggerPrototype['dependencies'] as $dependency) {
- // Check if the current trigger is the parent of the dependency trigger.
-
- $depTriggerId = $dependency['triggerid'];
-
- if (isset($parentDepTriggers[$depTriggerId])
- && $parentDepTriggers[$depTriggerId]['templateid'] == $triggerPrototype['triggerid']) {
-
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Trigger prototype cannot be dependent on a trigger that is inherited from it.')
- );
- }
- }
- }
- }
- }
-
- /**
- * Checks if the given dependencies contain duplicates.
- *
- * @param array $triggerPrototypes
- * @param string $triggerPrototypes[]['triggerid']
- * @param array $triggerPrototypes[]['dependencies']
- * @param string $triggerPrototypes[]['dependencies'][]['triggerid']
- *
- * @throws APIException if the given dependencies contain duplicates.
- */
- protected function checkDependencyDuplicates(array $triggerPrototypes) {
- // check duplicates in array
- $uniqueTriggers = [];
- $depTriggerIds = [];
- $duplicateTriggerId = null;
-
- foreach ($triggerPrototypes as $triggerPrototype) {
- if (!array_key_exists('dependencies', $triggerPrototype)) {
- continue;
- }
-
- foreach ($triggerPrototype['dependencies'] as $dependency) {
- $depTriggerIds[$dependency['triggerid']] = $dependency['triggerid'];
-
- if (isset($uniqueTriggers[$triggerPrototype['triggerid']][$dependency['triggerid']])) {
- $duplicateTriggerId = $triggerPrototype['triggerid'];
- break 2;
- }
- else {
- $uniqueTriggers[$triggerPrototype['triggerid']][$dependency['triggerid']] = 1;
- }
- }
- }
-
- if ($duplicateTriggerId === null) {
- // check if dependency already exists in DB
- foreach ($triggerPrototypes as $triggerPrototype) {
- $dbUpTriggers = DBselect(
- 'SELECT td.triggerid_up'.
- ' FROM trigger_depends td'.
- ' WHERE '.dbConditionInt('td.triggerid_up', $depTriggerIds).
- ' AND td.triggerid_down='.zbx_dbstr($triggerPrototype['triggerid'])
- , 1);
- if (DBfetch($dbUpTriggers)) {
- $duplicateTriggerId = $triggerPrototype['triggerid'];
- break;
- }
- }
- }
-
- if ($duplicateTriggerId) {
- $duplicateTrigger = DBfetch(DBselect(
- 'SELECT t.description'.
- ' FROM triggers t'.
- ' WHERE t.triggerid='.zbx_dbstr($duplicateTriggerId)
- ));
- self::exception(ZBX_API_ERROR_PARAMETERS,
- _s('Duplicate dependencies in trigger prototype "%1$s".', $duplicateTrigger['description'])
- );
- }
- }
-
- /**
- * Synchronizes the templated trigger prototype dependencies on the given hosts inherited from the given templates.
- * Update dependencies, do it after all triggers and trigger prototypes that can be dependent were created/updated
- * on all child hosts/templates. Starting from highest level template trigger prototypes select trigger prototypes
- * from one level lower, then for each lower trigger prototype look if it's parent has dependencies, if so
- * find this dependency trigger prototype child on dependent trigger prototype host and add new dependency.
- *
- * @param array $data
- * @param array|string $data['templateids']
- * @param array|string $data['hostids']
- */
- public function syncTemplateDependencies(array $data) {
- $templateIds = zbx_toArray($data['templateids']);
- $hostIds = zbx_toArray($data['hostids']);
-
- $parentTriggers = $this->get([
- 'output' => ['triggerid'],
- 'selectDependencies' => ['triggerid'],
- 'hostids' => $templateIds,
- 'preservekeys' => true,
- 'nopermissions' => true
- ]);
-
- if ($parentTriggers) {
- $childTriggers = $this->get([
- 'output' => ['triggerid', 'templateid'],
- 'selectHosts' => ['hostid'],
- 'hostids' => ($hostIds) ? $hostIds : null,
- 'filter' => ['templateid' => array_keys($parentTriggers)],
- 'nopermissions' => true,
- 'preservekeys' => true
- ]);
-
- if ($childTriggers) {
- $newDependencies = [];
-
- foreach ($childTriggers as $childTrigger) {
- $parentDependencies = $parentTriggers[$childTrigger['templateid']]['dependencies'];
-
- if ($parentDependencies) {
- $newDependencies[$childTrigger['triggerid']] = [
- 'triggerid' => $childTrigger['triggerid'],
- 'dependencies' => []
- ];
-
- $dependencies = [];
- foreach ($parentDependencies as $depTrigger) {
- $dependencies[] = $depTrigger['triggerid'];
- }
-
- $host = reset($childTrigger['hosts']);
- $dependencies = replace_template_dependencies($dependencies, $host['hostid']);
-
- foreach ($dependencies as $depTriggerId) {
- $newDependencies[$childTrigger['triggerid']]['dependencies'][] = [
- 'triggerid' => $depTriggerId
- ];
- }
- }
- }
-
- $this->deleteDependencies($childTriggers, true);
-
- if ($newDependencies) {
- $this->addDependencies($newDependencies, true);
- }
- }
- }
- }
-
- /**
* Retrieves and adds additional requested data (options 'selectHosts', 'selectGroups', etc.) to result set.
*
* @param array $options
diff --git a/ui/include/forms.inc.php b/ui/include/forms.inc.php
index 2075ce4e3a3..0c194700f7d 100644
--- a/ui/include/forms.inc.php
+++ b/ui/include/forms.inc.php
@@ -1573,9 +1573,16 @@ function getCopyElementsFormData($elements_field, $title = null) {
'elements' => getRequest($elements_field, []),
'copy_type' => getRequest('copy_type', COPY_TYPE_TO_HOST_GROUP),
'copy_targetids' => getRequest('copy_targetids', []),
- 'hostid' => getRequest('hostid', 0)
+ 'hostid' => 0
];
+ $prefix = (getRequest('context') === 'host') ? 'web.hosts.' : 'web.templates.';
+ $filter_hostids = getRequest('filter_hostids', CProfile::getArray($prefix.'triggers.filter_hostids', []));
+
+ if (count($filter_hostids) == 1) {
+ $data['hostid'] = reset($filter_hostids);
+ }
+
if (!$data['elements'] || !is_array($data['elements'])) {
show_error_message(_('Incorrect list of items.'));
diff --git a/ui/include/triggers.inc.php b/ui/include/triggers.inc.php
index 2bdd76b93ad..bbd85dc35f5 100644
--- a/ui/include/triggers.inc.php
+++ b/ui/include/triggers.inc.php
@@ -93,18 +93,6 @@ function get_trigger_by_triggerid($triggerid) {
return false;
}
-function get_hosts_by_triggerid($triggerids) {
- zbx_value2array($triggerids);
-
- return DBselect(
- 'SELECT DISTINCT h.*'.
- ' FROM hosts h,functions f,items i'.
- ' WHERE i.itemid=f.itemid'.
- ' AND h.hostid=i.hostid'.
- ' AND '.dbConditionInt('f.triggerid', $triggerids)
- );
-}
-
function get_triggers_by_hostid($hostid) {
return DBselect(
'SELECT DISTINCT t.*'.
@@ -148,27 +136,68 @@ function utf8RawUrlDecode($source) {
}
/**
- * Copies the given triggers to the given hosts or templates.
- *
- * Without the $src_hostid parameter it will only be able to copy triggers that belong to only one host. If the
- * $src_hostid parameter is not passed, and a trigger has multiple hosts, it will throw an error. If the
- * $src_hostid parameter is passed, the given host will be replaced with the destination host.
+ * Copy the given triggers to the target hosts or templates, taking care of copied trigger dependencies.
*
- * This function takes care of copied trigger dependencies.
- * If trigger is copied alongside with trigger on which it depends, then dependencies is replaced directly using new ids,
- * If there is target host within dependency trigger, algorithm will search for potential matching trigger in target host,
- * if matching trigger is found, then id from this trigger is used, if not rise exception,
- * otherwise original dependency will be left.
+ * If the $src_hostid parameter is passed, the given host will be replaced with the destination host.
+ * Without $src_hostid, only triggers that belong to a single host or template can be copied.
*
+ * If a trigger is copied alongside with the trigger which it depends on, then dependencies are replaced directly,
+ * using new IDs.
+ * If the source trigger depends on the trigger from the same host or template, the same trigger-up should exist on the
+ * target host or template.
*
- * @param array $src_triggerids Triggers which will be copied to $dst_hostids
- * @param array $dst_hostids Hosts and templates to whom add triggers. IDs not present in DB (host table)
- * will be ignored.
- * @param int $src_hostid Host ID in which context trigger with multiple hosts will be treated.
+ * @param array $dst_hostids Hosts and templates to copy triggers to.
+ * IDs not present in the database will be ignored.
+ * @param string|null $src_hostid ID of host to use as context for trigger when multiple hosts are involved.
+ * @param array|null $src_triggerids Triggers which will be copied to destination host(s).
*
* @return bool
*/
-function copyTriggersToHosts($src_triggerids, $dst_hostids, $src_hostid = null) {
+function copyTriggersToHosts(array $dst_hostids, ?string $src_hostid, array $src_triggerids = null): bool {
+ $dst_templates = API::Template()->get([
+ 'output' => ['host'],
+ 'templateids' => $dst_hostids,
+ 'editable' => true,
+ 'preservekeys' => true
+ ]);
+
+ $_dst_hostids = array_diff($dst_hostids, array_keys($dst_templates));
+
+ $dst_hosts = $_dst_hostids
+ ? API::Host()->get([
+ 'output' => ['host', 'status'],
+ 'hostids' => $_dst_hostids,
+ 'editable' => true,
+ 'preservekeys' => true
+ ])
+ : [];
+
+ $dst_hosts = $dst_templates + $dst_hosts;
+
+ if (!$dst_hosts || count($dst_hosts) != count($dst_hostids)) {
+ return false;
+ }
+
+ if ($src_hostid) {
+ $src_hosts = API::Template()->get([
+ 'output' => ['host'],
+ 'templateids' => $src_hostid
+ ]);
+
+ $src_hosts = $src_hosts
+ ? $src_hosts
+ : API::Host()->get([
+ 'output' => ['host'],
+ 'hostids' => $src_hostid
+ ]);
+
+ if (!$src_hosts) {
+ return false;
+ }
+
+ $src_host = $src_hosts[0]['host'];
+ }
+
$options = [
'output' => ['triggerid', 'expression', 'description', 'url', 'status', 'priority', 'comments', 'type',
'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag', 'manual_close', 'opdata',
@@ -176,225 +205,249 @@ function copyTriggersToHosts($src_triggerids, $dst_hostids, $src_hostid = null)
],
'selectDependencies' => ['triggerid'],
'selectTags' => ['tag', 'value'],
- 'triggerids' => $src_triggerids
+ 'preservekeys' => true
];
- if ($src_hostid) {
- $srcHost = API::Host()->get([
- 'output' => ['host'],
+ if (!$src_hostid) {
+ $options += ['selectHosts' => ['hostid', 'host']];
+ }
+
+ if ($src_triggerids) {
+ $options += ['triggerids' => $src_triggerids];
+ }
+ else {
+ $options += [
'hostids' => $src_hostid,
- 'preservekeys' => true,
- 'templated_hosts' => true
- ]);
+ 'inherited' => false,
+ 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
+ ];
+ }
- if (!$srcHost = reset($srcHost)) {
+ $src_triggers = API::Trigger()->get($options);
+
+ if ($src_triggerids) {
+ if (count($src_triggers) != count($src_triggerids)) {
return false;
}
}
else {
- // Select source trigger first host 'host'.
- $options['selectHosts'] = ['host'];
+ if (!$src_triggers) {
+ return true;
+ }
}
- $dbSrcTriggers = API::Trigger()->get($options);
-
- $dbSrcTriggers = CMacrosResolverHelper::resolveTriggerExpressions($dbSrcTriggers,
+ $src_triggers = CMacrosResolverHelper::resolveTriggerExpressions($src_triggers,
['sources' => ['expression', 'recovery_expression']]
);
- $dbDstHosts = API::Host()->get([
- 'output' => ['hostid', 'host'],
- 'hostids' => $dst_hostids,
- 'preservekeys' => true,
- 'templated_hosts' => true
- ]);
+ if (!$src_hostid) {
+ foreach ($src_triggers as $src_trigger) {
+ if (count($src_trigger['hosts']) > 1) {
+ error(_s('Cannot copy trigger "%1$s", because it has multiple hosts in the expression.',
+ $src_trigger['description']
+ ));
+
+ return false;
+ }
+ }
+ }
- $newTriggers = [];
+ $dst_triggers = [];
+ $trigger_links = [];
+ $i = 0;
- foreach ($dbDstHosts as $dstHost) {
- // Create each trigger for each host.
+ foreach ($dst_hosts as $dst_hostid => $dst_host) {
+ foreach ($src_triggers as $src_triggerid => $src_trigger) {
+ $dst_trigger = array_intersect_key($src_trigger, array_flip(['expression', 'description', 'url', 'status',
+ 'priority', 'comments', 'type', 'recovery_mode', 'recovery_expression', 'correlation_mode',
+ 'correlation_tag', 'manual_close', 'opdata', 'event_name', 'tags'
+ ]));
- foreach ($dbSrcTriggers as $srcTrigger) {
- if ($src_hostid) {
- // Get host 'host' for triggerExpressionReplaceHost().
+ $_src_host = $src_hostid ? $src_host : $src_trigger['hosts'][0]['host'];
- $host = $srcHost['host'];
- $srcTriggerContextHostId = $src_hostid;
+ $dst_trigger['expression'] =
+ triggerExpressionReplaceHost($src_trigger['expression'], $_src_host, $dst_host['host']);
+
+ if ($src_trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) {
+ $dst_trigger['recovery_expression'] =
+ triggerExpressionReplaceHost($src_trigger['recovery_expression'], $_src_host, $dst_host['host']);
}
- else {
- if (count($srcTrigger['hosts']) > 1) {
- error(_s('Cannot copy trigger "%1$s:%2$s", because it has multiple hosts in the expression.',
- $srcTrigger['description'], $srcTrigger['expression']
- ));
- return false;
- }
+ $dst_triggers[] = $dst_trigger;
+ $trigger_links[$src_triggerid][$dst_hostid] = $i;
- // Use source trigger first host 'host'.
- $host = $srcTrigger['hosts'][0]['host'];
- $srcTriggerContextHostId = $srcTrigger['hosts'][0]['hostid'];
- }
+ $i++;
+ }
+ }
- $srcTrigger['expression'] = triggerExpressionReplaceHost($srcTrigger['expression'], $host,
- $dstHost['host']
- );
+ $result = API::Trigger()->create($dst_triggers);
- if ($srcTrigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) {
- $srcTrigger['recovery_expression'] = triggerExpressionReplaceHost($srcTrigger['recovery_expression'],
- $host, $dstHost['host']
- );
- }
+ if (!$result) {
+ return false;
+ }
- // The dependencies must be added after all triggers are created.
- $result = API::Trigger()->create([[
- 'description' => $srcTrigger['description'],
- 'event_name' => $srcTrigger['event_name'],
- 'opdata' => $srcTrigger['opdata'],
- 'expression' => $srcTrigger['expression'],
- 'url' => $srcTrigger['url'],
- 'status' => $srcTrigger['status'],
- 'priority' => $srcTrigger['priority'],
- 'comments' => $srcTrigger['comments'],
- 'type' => $srcTrigger['type'],
- 'recovery_mode' => $srcTrigger['recovery_mode'],
- 'recovery_expression' => $srcTrigger['recovery_expression'],
- 'correlation_mode' => $srcTrigger['correlation_mode'],
- 'correlation_tag' => $srcTrigger['correlation_tag'],
- 'tags' => $srcTrigger['tags'],
- 'manual_close' => $srcTrigger['manual_close']
- ]]);
-
- if (!$result) {
- return false;
+ $dst_triggerids = $result['triggerids'];
+
+ $dst_triggers = [];
+ $src_triggerids_up = [];
+
+ foreach ($trigger_links as $src_triggerid => $links) {
+ foreach ($links as $dst_hostid => $i) {
+ if (!$src_triggers[$src_triggerid]['dependencies']) {
+ continue;
}
- $newTriggers[$srcTrigger['triggerid']][] = [
- 'newTriggerId' => reset($result['triggerids']),
- 'newTriggerExpression' => $srcTrigger['expression'],
- 'newTriggerHostId' => $dstHost['hostid'],
- 'newTriggerHost' => $dstHost['host'],
- 'srcTriggerContextHostId' => $srcTriggerContextHostId,
- 'srcTriggerContextHost' => $host
- ];
+ $dst_triggers[$i] = ['triggerid' => $dst_triggerids[$i]];
+
+ foreach ($src_triggers[$src_triggerid]['dependencies'] as $src_trigger_up) {
+ if (array_key_exists($src_trigger_up['triggerid'], $trigger_links)) {
+ $dst_triggers[$i]['dependencies'][] = [
+ 'triggerid' => $dst_triggerids[$trigger_links[$src_trigger_up['triggerid']][$dst_hostid]]
+ ];
+ }
+ elseif ($src_triggerids) {
+ $src_triggerids_up[$src_trigger_up['triggerid']] = true;
+ }
+ else {
+ $dst_triggers[$i]['dependencies'][] = ['triggerid' => $src_trigger_up['triggerid']];
+ }
+ }
}
}
- $depids = [];
- foreach ($dbSrcTriggers as $srcTrigger) {
- foreach ($srcTrigger['dependencies'] as $depTrigger) {
- $depids[] = $depTrigger['triggerid'];
+ if ($src_triggerids_up) {
+ $src_triggers_up = API::Trigger()->get([
+ 'output' => ['description', 'expression', 'recovery_mode', 'recovery_expression'],
+ 'selectHosts' => ['hostid'],
+ 'triggerids' => array_keys($src_triggerids_up),
+ 'preservekeys' => true
+ ]);
+
+ $src_triggers_up = CMacrosResolverHelper::resolveTriggerExpressions($src_triggers_up,
+ ['sources' => ['expression', 'recovery_expression']]
+ );
+
+ $src_host_dependencies = [];
+
+ foreach ($trigger_links as $src_triggerid => $links) {
+ $_src_hostid = $src_hostid ? $src_hostid : $src_triggers[$src_triggerid]['hosts'][0]['hostid'];
+
+ foreach ($links as $dst_hostid => $i) {
+ foreach ($src_triggers[$src_triggerid]['dependencies'] as $src_trigger_up) {
+ if (!array_key_exists($src_trigger_up['triggerid'], $src_triggers_up)) {
+ continue;
+ }
+
+ $src_hostids_up = array_column($src_triggers_up[$src_trigger_up['triggerid']]['hosts'], 'hostid');
+
+ if (in_array($_src_hostid, $src_hostids_up)) {
+ $src_host_dependencies[$src_trigger_up['triggerid']][$src_triggerid] = true;
+ }
+ else {
+ $dst_triggers[$i]['dependencies'][] = ['triggerid' => $src_trigger_up['triggerid']];
+ }
+ }
+ }
}
- }
- $depTriggers = API::Trigger()->get([
- 'triggerids' => $depids,
- 'output' => ['description', 'expression', 'recovery_mode', 'recovery_expression'],
- 'selectHosts' => ['hostid'],
- 'preservekeys' => true
- ]);
- $depTriggers = CMacrosResolverHelper::resolveTriggerExpressions($depTriggers,
- ['sources' => ['expression', 'recovery_expression']]
- );
+ if ($src_host_dependencies) {
+ $descriptions = array_unique(array_column(array_intersect_key($src_triggers_up, $src_host_dependencies),
+ 'description'
+ ));
- if ($newTriggers) {
- // Map dependencies to the new trigger IDs and save.
-
- $dependencies = [];
-
- foreach ($dbSrcTriggers as $srcTrigger) {
- if ($srcTrigger['dependencies']) {
- // Get corresponding created triggers.
- $dst_triggers = $newTriggers[$srcTrigger['triggerid']];
-
- foreach ($dst_triggers as $dst_trigger) {
- foreach ($srcTrigger['dependencies'] as $depTrigger) {
- /*
- * We have added $depTrigger trigger and we know corresponding trigger ID for newly
- * created trigger.
- */
- if (array_key_exists($depTrigger['triggerid'], $newTriggers)) {
- $dst_dep_triggers = $newTriggers[$depTrigger['triggerid']];
-
- foreach ($dst_dep_triggers as $dst_dep_trigger) {
- /*
- * Dependency is within same host according to $src_hostid parameter or dep trigger has
- * single host.
- */
- if ($dst_trigger['srcTriggerContextHostId'] == $dst_dep_trigger['srcTriggerContextHostId']
- && $dst_dep_trigger['newTriggerHostId'] == $dst_trigger['newTriggerHostId']) {
- $depTriggerId = $dst_dep_trigger['newTriggerId'];
- break;
- }
- // Dependency is to trigger from another host.
- else {
- $depTriggerId = $depTrigger['triggerid'];
- }
- }
- }
- // We need to search for $depTrigger trigger if target host is within dependency hosts.
- elseif (in_array(['hostid' => $dst_trigger['srcTriggerContextHostId']],
- $depTriggers[$depTrigger['triggerid']]['hosts'])) {
- // Get all possible $depTrigger matching triggers by description.
- $targetHostTriggersByDescription = API::Trigger()->get([
- 'hostids' => $dst_trigger['newTriggerHostId'],
- 'output' => ['hosts', 'triggerid', 'expression'],
- 'filter' => ['description' => $depTriggers[$depTrigger['triggerid']]['description']],
- 'preservekeys' => true
- ]);
-
- $targetHostTriggersByDescription =
- CMacrosResolverHelper::resolveTriggerExpressions($targetHostTriggersByDescription);
-
- // Compare exploded expressions for exact match.
- $expr1 = $depTriggers[$depTrigger['triggerid']]['expression'];
- $depTriggerId = null;
-
- foreach ($targetHostTriggersByDescription as $potentialTargetTrigger) {
- $expr2 = triggerExpressionReplaceHost($potentialTargetTrigger['expression'],
- $dst_trigger['newTriggerHost'], $dst_trigger['srcTriggerContextHost']
- );
-
- if ($expr2 == $expr1) {
- // Matching trigger has been found.
- $depTriggerId = $potentialTargetTrigger['triggerid'];
- break;
- }
- }
+ $dst_host_triggers = API::Trigger()->get([
+ 'output' => ['triggerid', 'description', 'expression', 'recovery_expression'],
+ 'selectHosts' => ['hostid'],
+ 'hostids' => array_keys($dst_hosts),
+ 'filter' => ['description' => $descriptions],
+ 'preservekeys' => true
+ ]);
- // If matching trigger wasn't found raise exception.
- if ($depTriggerId === null) {
- $expr2 = triggerExpressionReplaceHost($expr1, $dst_trigger['srcTriggerContextHost'],
- $dst_trigger['newTriggerHost']
- );
+ if (!$dst_host_triggers) {
+ $src_triggerid_up = key($src_host_dependencies);
+ $src_triggerid = key($src_host_dependencies[$src_triggerid_up]);
+ $dst_hostid = key($trigger_links[$src_triggerid]);
- error(_s(
- 'Cannot add dependency from trigger "%1$s:%2$s" to non existing trigger "%3$s:%4$s".',
- $srcTrigger['description'], $dst_trigger['newTriggerExpression'],
- $depTriggers[$depTrigger['triggerid']]['description'], $expr2
- ));
+ $error = array_key_exists('status', $dst_hosts[$dst_hostid])
+ ? _('Trigger "%1$s" cannot depend on the non-existent trigger "%2$s" on the host "%3$s".')
+ : _('Trigger "%1$s" cannot depend on the non-existent trigger "%2$s" on the template "%3$s".');
- return false;
- }
+ error(sprintf($error, $src_triggers[$src_triggerid]['description'],
+ $src_triggers_up[$src_triggerid_up]['description'], $dst_hosts[$dst_hostid]['host']
+ ));
+
+ return false;
+ }
+
+ $dst_host_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dst_host_triggers,
+ ['sources' => ['expression', 'recovery_expression']]
+ );
+
+ $dst_host_triggerids = [];
+
+ foreach ($dst_host_triggers as $i => $trigger) {
+ $description = $trigger['description'];
+ $expression = $trigger['expression'];
+ $recovery_expression = $trigger['recovery_expression'];
+
+ if ($src_hostid) {
+ foreach ($trigger['hosts'] as $host) {
+ if (array_key_exists($host['hostid'], $dst_hosts)) {
+ $dst_host_triggerids[$host['hostid']][$description][$expression][$recovery_expression] =
+ $trigger['triggerid'];
}
- else {
- // Leave original dependency.
+ }
+ }
+ else {
+ $dst_hostid = $trigger['hosts'][0]['hostid'];
- $depTriggerId = $depTrigger['triggerid'];
+ $dst_host_triggerids[$dst_hostid][$description][$expression][$recovery_expression] =
+ $trigger['triggerid'];
+ }
+ }
+
+ foreach ($src_host_dependencies as $src_triggerid_up => $src_triggerids) {
+ foreach ($src_triggerids as $src_triggerid => $foo) {
+ foreach ($trigger_links[$src_triggerid] as $dst_hostid => $i) {
+ $src_trigger_up = $src_triggers_up[$src_triggerid_up];
+ $_src_host = $src_hostid ? $src_host : $src_trigger['hosts'][0]['host'];
+ $dst_host = $dst_hosts[$dst_hostid]['host'];
+
+ $description = $src_trigger_up['description'];
+ $expression =
+ triggerExpressionReplaceHost($src_trigger_up['expression'], $_src_host, $dst_host);
+ $recovery_expression =
+ triggerExpressionReplaceHost($src_trigger_up['recovery_expression'], $_src_host, $dst_host);
+
+ if (array_key_exists($dst_hostid, $dst_host_triggerids)
+ && array_key_exists($description, $dst_host_triggerids[$dst_hostid])
+ && array_key_exists($expression, $dst_host_triggerids[$dst_hostid][$description])
+ && array_key_exists($recovery_expression, $dst_host_triggerids[$dst_hostid][$description][$expression])) {
+ $dst_triggerid_up =
+ $dst_host_triggerids[$dst_hostid][$description][$expression][$recovery_expression];
+
+ $dst_triggers[$i]['dependencies'][] = ['triggerid' => $dst_triggerid_up];
}
+ else {
+ $error = array_key_exists('status', $dst_hosts[$dst_hostid])
+ ? _('Trigger "%1$s" cannot depend on the non-existent trigger "%2$s" on the host "%3$s".')
+ : _('Trigger "%1$s" cannot depend on the non-existent trigger "%2$s" on the template "%3$s".');
+
+ error(sprintf($error, $description, $src_trigger_up['description'], $dst_host));
- $dependencies[] = [
- 'triggerid' => $dst_trigger['newTriggerId'],
- 'dependsOnTriggerid' => $depTriggerId
- ];
+ return false;
+ }
}
}
}
}
+ }
- if ($dependencies) {
- if (!API::Trigger()->addDependencies($dependencies)) {
- return false;
- }
+ if ($dst_triggers) {
+ $result = API::Trigger()->update(array_values($dst_triggers));
+
+ if (!$result) {
+ return false;
}
}
@@ -433,22 +486,6 @@ function triggerExpressionReplaceHost(string $expression, string $src_host, stri
return $expression;
}
-function replace_template_dependencies($deps, $hostid) {
- foreach ($deps as $id => $val) {
- $sql = 'SELECT t.triggerid'.
- ' FROM triggers t,functions f,items i'.
- ' WHERE t.triggerid=f.triggerid'.
- ' AND f.itemid=i.itemid'.
- ' AND t.templateid='.zbx_dbstr($val).
- ' AND i.hostid='.zbx_dbstr($hostid);
- if ($db_new_dep = DBfetch(DBselect($sql))) {
- $deps[$id] = $db_new_dep['triggerid'];
- }
- }
-
- return $deps;
-}
-
/**
* Prepare arrays containing only hosts and triggers that will be shown results table.
*
diff --git a/ui/include/views/configuration.triggers.edit.php b/ui/include/views/configuration.triggers.edit.php
index 83e6255c3b3..fc3efc04ca9 100644
--- a/ui/include/views/configuration.triggers.edit.php
+++ b/ui/include/views/configuration.triggers.edit.php
@@ -600,7 +600,7 @@ $dependenciesFormList = new CFormList('dependenciesFormList');
$dependenciesTable = (new CTable())
->setId('dependency-table')
->setAttribute('style', 'width: 100%;')
- ->setHeader([_('Name'), $readonly ? null : _('Action')]);
+ ->setHeader([_('Name'), $discovered_trigger ? null : _('Action')]);
foreach ($data['db_dependencies'] as $dependency) {
$triggersForm->addVar('dependencies[]', $dependency['triggerid'], 'dependencies_'.$dependency['triggerid']);
@@ -618,7 +618,7 @@ foreach ($data['db_dependencies'] as $dependency) {
->setArgument('context', $data['context'])
))->setTarget('_blank'),
(new CCol(
- $readonly
+ $discovered_trigger
? null
: (new CButton('remove', _('Remove')))
->onClick('view.removeDependency('.json_encode($dependency['triggerid']).')')
@@ -632,7 +632,7 @@ foreach ($data['db_dependencies'] as $dependency) {
$dependenciesFormList->addRow(_('Dependencies'),
(new CDiv([
$dependenciesTable,
- $readonly
+ $discovered_trigger
? null
: (new CButton('bnt1', _('Add')))
->onClick(
diff --git a/ui/include/views/monitoring.sysmap.edit.php b/ui/include/views/monitoring.sysmap.edit.php
index c0345f7682c..a7731419c56 100644
--- a/ui/include/views/monitoring.sysmap.edit.php
+++ b/ui/include/views/monitoring.sysmap.edit.php
@@ -344,7 +344,7 @@ $user_groups = [];
foreach ($data['sysmap']['userGroups'] as $user_group) {
$user_groupid = $user_group['usrgrpid'];
- $user_groups[$user_groupid] = [
+ $user_groups[] = [
'usrgrpid' => $user_groupid,
'name' => $data['user_groups'][$user_groupid]['name'],
'permission' => $user_group['permission']
@@ -381,14 +381,14 @@ $users = [];
foreach ($data['sysmap']['users'] as $user) {
$userid = $user['userid'];
- $users[$userid] = [
+ $users[] = [
'id' => $userid,
'name' => getUserFullname($data['users'][$userid]),
'permission' => $user['permission']
];
}
-$js_insert .= 'window.addPopupValues('.zbx_jsvalue(['object' => 'userid', 'values' => $users]).');';
+$js_insert .= 'window.addPopupValues('.json_encode(['object' => 'userid', 'values' => $users]).');';
zbx_add_post_js($js_insert);
diff --git a/ui/templates.php b/ui/templates.php
index 400c9ac5d99..f5623caeb35 100644
--- a/ui/templates.php
+++ b/ui/templates.php
@@ -333,17 +333,8 @@ elseif (hasRequest('add') || hasRequest('update')) {
}
// copy triggers
- $dbTriggers = API::Trigger()->get([
- 'output' => ['triggerid'],
- 'hostids' => $cloneTemplateId,
- 'inherited' => false
- ]);
-
- if ($dbTriggers) {
- if (!copyTriggersToHosts(zbx_objectValues($dbTriggers, 'triggerid'), $input_templateid,
- $cloneTemplateId)) {
- throw new Exception();
- }
+ if (!copyTriggersToHosts([$input_templateid], $cloneTemplateId)) {
+ throw new Exception();
}
// copy graphs
diff --git a/ui/tests/api_json/testTemplate.php b/ui/tests/api_json/testTemplate.php
index 14bec3e5311..8441ad15ca1 100644
--- a/ui/tests/api_json/testTemplate.php
+++ b/ui/tests/api_json/testTemplate.php
@@ -272,7 +272,7 @@ class testTemplate extends CAPITest {
['templateid' => $templateids[2]]
]
]
- ], 'Cannot link template "test-template-double-link-01" to template "test-template-double-link-04" because its parent template "test-template-double-link-01" will be linked twice.');
+ ], 'Cannot link template "test-template-double-link-01" to template "test-template-double-link-04", because its parent template "test-template-double-link-01" would be linked twice.');
}
public function testTemplate_CreateTriggerDependency() {
diff --git a/ui/tests/api_json/testTriggerValidation.php b/ui/tests/api_json/testTriggerValidation.php
index cae85a10781..fb59239a390 100644
--- a/ui/tests/api_json/testTriggerValidation.php
+++ b/ui/tests/api_json/testTriggerValidation.php
@@ -107,7 +107,7 @@ class testTriggerValidation extends CAPITest {
]
]
],
- 'expected_error' => 'Cannot create circular dependencies.'
+ 'expected_error' => 'Trigger "test-trigger-1" cannot depend on the trigger "test-trigger-2", because a circular linkage ("test-trigger-2" -> "test-trigger-1" -> "test-trigger-2") would occur.'
],
'delete trigger name' => [
'triggers' => [
@@ -129,7 +129,7 @@ class testTriggerValidation extends CAPITest {
]
]
],
- 'expected_error' => 'Cannot create dependency on trigger itself.'
+ 'expected_error' => 'Trigger "test-trigger-1" cannot depend on the trigger "test-trigger-1", because a circular linkage ("test-trigger-1" -> "test-trigger-1") would occur.'
],
'update read-only properties' => [
'triggers' => [
@@ -392,7 +392,7 @@ class testTriggerValidation extends CAPITest {
]]
]
],
- 'expected_error' => 'Cannot add dependency from a host to a template.'
+ 'expected_error' => 'Trigger "Trigger with invalid dependencies" cannot depend on the trigger "template-trigger", because dependencies of host triggers on template triggers are not allowed.'
],
'Trigger with invalid dependencies #3' => [
'triggers' => [
diff --git a/ui/tests/include/web/CElement.php b/ui/tests/include/web/CElement.php
index 6cd81b83988..1fd95f14912 100644
--- a/ui/tests/include/web/CElement.php
+++ b/ui/tests/include/web/CElement.php
@@ -500,7 +500,8 @@ class CElement extends CBaseElement implements IWaitable {
* @inheritdoc
*/
public function isEnabled($enabled = true) {
- $classes = explode(' ', parent::getAttribute('class'));
+ $attribute = parent::getAttribute('class');
+ $classes = ($attribute !== null) ? explode(' ', $attribute) : [];
$is_enabled = parent::isEnabled()
&& (parent::getAttribute('disabled') === null)
diff --git a/ui/tests/include/web/CElementCollection.php b/ui/tests/include/web/CElementCollection.php
index 67ab8c9e349..76835c261f2 100644
--- a/ui/tests/include/web/CElementCollection.php
+++ b/ui/tests/include/web/CElementCollection.php
@@ -61,13 +61,14 @@ class CElementCollection implements Iterator {
/**
* @inheritdoc
*/
- public function rewind() {
+ public function rewind(): void {
reset($this->elements);
}
/**
* @inheritdoc
*/
+ #[\ReturnTypeWillChange]
public function current() {
return current($this->elements);
}
@@ -75,6 +76,7 @@ class CElementCollection implements Iterator {
/**
* @inheritdoc
*/
+ #[\ReturnTypeWillChange]
public function key() {
return key($this->elements);
}
@@ -82,14 +84,14 @@ class CElementCollection implements Iterator {
/**
* @inheritdoc
*/
- public function next() {
- return next($this->elements);
+ public function next(): void {
+ next($this->elements);
}
/**
* @inheritdoc
*/
- public function valid() {
+ public function valid(): bool {
$key = $this->key();
return ($key !== null && $key !== false);
@@ -100,7 +102,7 @@ class CElementCollection implements Iterator {
*
* @return integer
*/
- public function count() {
+ public function count(): int {
return count($this->elements);
}
@@ -109,7 +111,7 @@ class CElementCollection implements Iterator {
*
* @return boolean
*/
- public function isEmpty() {
+ public function isEmpty(): bool {
return ($this->elements === []);
}
@@ -120,7 +122,7 @@ class CElementCollection implements Iterator {
*
* @throws Exception
*/
- public function first() {
+ public function first(): CElement {
$element = reset($this->elements);
if ($element === false) {
@@ -137,7 +139,7 @@ class CElementCollection implements Iterator {
*
* @throws Exception
*/
- public function last() {
+ public function last(): CElement {
$element = end($this->elements);
if ($element === false) {
@@ -154,7 +156,7 @@ class CElementCollection implements Iterator {
*
* @return boolean
*/
- public function exists($key) {
+ public function exists($key): bool {
return array_key_exists($key, $this->elements);
}
@@ -163,9 +165,9 @@ class CElementCollection implements Iterator {
*
* @param mixed $key array key
*
- * @return boolean
+ * @return CElement
*/
- public function get($key) {
+ public function get($key): CElement {
return $this->elements[$key];
}
@@ -175,7 +177,7 @@ class CElementCollection implements Iterator {
* @param mixed $key array key
* @param mixed $element element to be set
*/
- public function set($key, $element) {
+ public function set($key, $element): void {
$this->elements[$key] = $element;
}
diff --git a/ui/tests/include/web/elements/CTableElement.php b/ui/tests/include/web/elements/CTableElement.php
index 44d4bc764e5..41215230a25 100644
--- a/ui/tests/include/web/elements/CTableElement.php
+++ b/ui/tests/include/web/elements/CTableElement.php
@@ -186,7 +186,7 @@ class CTableElement extends CElement {
public function findRows($param, $data = []) {
$rows = [];
- if (is_callable($param)) {
+ if ($param instanceof \Closure) {
foreach ($this->getRows() as $i => $row) {
if (call_user_func($param, $row)) {
$rows[$i] = $row;
diff --git a/ui/tests/selenium/testTriggerDependencies.php b/ui/tests/selenium/testTriggerDependencies.php
index fe4c9aa408a..1e4404740ba 100644
--- a/ui/tests/selenium/testTriggerDependencies.php
+++ b/ui/tests/selenium/testTriggerDependencies.php
@@ -20,6 +20,7 @@
require_once dirname(__FILE__).'/../include/CLegacyWebTest.php';
require_once dirname(__FILE__).'/../include/helpers/CDataHelper.php';
+require_once dirname(__FILE__).'/behaviors/CMessageBehavior.php';
use Facebook\WebDriver\WebDriverBy;
@@ -29,6 +30,14 @@ use Facebook\WebDriver\WebDriverBy;
* @onBefore prepareTemplateData
*/
class testTriggerDependencies extends CLegacyWebTest {
+
+ /**
+ * Attach MessageBehavior to the test.
+ */
+ public function getBehaviors() {
+ return [CMessageBehavior::class];
+ }
+
const TEMPLATE_AGENT = 'Zabbix agent';
const TEMPLATE_FREEBSD = 'FreeBSD by Zabbix agent';
const TEMPLATE_APACHE = 'Apache by HTTP';
@@ -64,58 +73,80 @@ class testTriggerDependencies extends CLegacyWebTest {
/**
* @dataProvider getTriggerDependenciesData
*/
- public function testTriggerDependenciesFromHost_SimpleTest($trigger, $template, $dependencies, $expected) {
+ public function testTriggerDependenciesFromHost_SimpleTest($data) {
// Get the id of template to be updated based on the template that owns the trigger in dependencies tab.
$ids = [
self::TEMPLATE_AGENT => self::$agent_templateid,
self::TEMPLATE_APACHE => self::$apache_templateid
];
- $update_id = ($template === self::TEMPLATE_APACHE) ? $ids[self::TEMPLATE_APACHE] : $ids[self::TEMPLATE_AGENT];
+ $update_id = ($data['template'] === self::TEMPLATE_APACHE) ? $ids[self::TEMPLATE_APACHE] : $ids[self::TEMPLATE_AGENT];
$this->zbxTestLogin('triggers.php?filter_set=1&context=template&filter_hostids[0]='.$update_id);
$this->zbxTestCheckTitle('Configuration of triggers');
- $this->zbxTestClickLinkTextWait($trigger);
+ $this->zbxTestClickLinkTextWait($data['trigger']);
$this->zbxTestClickWait('tab_dependenciesTab');
$this->zbxTestClick('bnt1');
$this->zbxTestLaunchOverlayDialog('Triggers');
$host = COverlayDialogElement::find()->one()->query('class:multiselect-control')->asMultiselect()->one();
$host->fill([
- 'values' => $template,
+ 'values' => $data['template'],
'context' => 'Templates'
]);
- $this->zbxTestClickLinkTextWait($dependencies);
+ $this->zbxTestClickLinkTextWait($data['dependency']);
$this->zbxTestWaitUntilElementVisible(WebDriverBy::id('bnt1'));
$this->zbxTestClickWait('update');
- $this->zbxTestTextPresent($expected);
+ if ($data['expected'] === TEST_BAD) {
+ $this->assertMessage(TEST_BAD, 'Cannot update trigger', $data['error_message']);
+ }
+ else {
+ $this->assertMessage(TEST_GOOD, 'Trigger updated');
+ }
}
public function getTriggerDependenciesData() {
return [
[
- 'Zabbix agent is not available',
- self::TEMPLATE_FREEBSD,
- '/etc/passwd has been changed on FreeBSD by Zabbix agent',
- 'Not all templates are linked to'
+ [
+ 'expected' => TEST_BAD,
+ 'trigger' => 'Zabbix agent is not available',
+ 'template' => self::TEMPLATE_FREEBSD,
+ 'dependency' => '/etc/passwd has been changed on FreeBSD by Zabbix agent',
+ 'error_message' => 'Trigger "Zabbix agent is not available" cannot depend on the trigger "/etc/passwd has been changed'.
+ ' on {HOST.NAME}" from the template "FreeBSD by Zabbix agent", because dependencies on triggers'.
+ ' from a child template or host are not allowed.'
+ ]
],
[
- 'Apache: Service is down',
- self::TEMPLATE_APACHE,
- 'Apache: Service response time is too high',
- 'Cannot create circular dependencies.'
+ [
+ 'expected' => TEST_BAD,
+ 'trigger' => 'Apache: Service is down',
+ 'template' => self::TEMPLATE_APACHE,
+ 'dependency' => 'Apache: Service response time is too high',
+ 'error_message' => 'Trigger "Apache: Service is down" cannot depend on the trigger "Apache: Service response'.
+ ' time is too high", because a circular linkage ("Apache: Service response time is too high" ->'.
+ ' "Apache: Service is down" -> "Apache: Service response time is too high") would occur.'
+ ]
],
[
- 'Apache: has been restarted',
- self::TEMPLATE_APACHE,
- 'Apache: has been restarted',
- 'Cannot create dependency on trigger itself.'
+ [
+ 'expected' => TEST_BAD,
+ 'trigger' => 'Apache: has been restarted',
+ 'template' => self::TEMPLATE_APACHE,
+ 'dependency' => 'Apache: has been restarted',
+ 'error_message' => 'Trigger "Apache: has been restarted" cannot depend on the trigger "Apache: '.
+ 'has been restarted", because a circular linkage ("Apache: has been restarted" -> "Apache: '.
+ 'has been restarted") would occur.'
+ ]
],
[
- 'Apache: has been restarted',
- self::TEMPLATE_APACHE,
- 'Apache: Service is down',
- 'Trigger updated'
+ [
+ 'expected' => TEST_GOOD,
+ 'trigger' => 'Apache: has been restarted',
+ 'template' => self::TEMPLATE_APACHE,
+ 'dependency' => 'Apache: Service is down'
+ ]
]
];
}
diff --git a/ui/triggers.php b/ui/triggers.php
index f79c209ca10..62ec7d9506b 100644
--- a/ui/triggers.php
+++ b/ui/triggers.php
@@ -523,7 +523,7 @@ elseif (hasRequest('action') && getRequest('action') === 'trigger.masscopyto' &&
DBstart();
- $result = copyTriggersToHosts(getRequest('g_triggerid'), $hosts_ids, getRequest('hostid'));
+ $result = copyTriggersToHosts($hosts_ids, getRequest('hostid'), getRequest('g_triggerid'));
$result = DBend($result);
$triggers_count = count(getRequest('g_triggerid'));