You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1085 lines
34 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
/**
* Class containing methods for operations with triggers.
*/
class CTrigger extends CTriggerGeneral {
public const ACCESS_RULES = [
'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
];
protected const FLAGS = ZBX_FLAG_DISCOVERY_NORMAL;
protected $tableName = 'triggers';
protected $tableAlias = 't';
protected $sortColumns = ['triggerid', 'description', 'status', 'priority', 'lastchange', 'hostname'];
public const OUTPUT_FIELDS = ['triggerid', 'expression', 'description', 'url', 'status', 'value', 'priority',
'lastchange', 'comments', 'error', 'templateid', 'type', 'state', 'flags', 'recovery_mode',
'recovery_expression', 'correlation_mode', 'correlation_tag', 'manual_close', 'opdata', 'event_name', 'url_name'
];
/**
* Get Triggers data.
*
* @param array $options
* @param array $options['itemids']
* @param array $options['hostids']
* @param array $options['groupids']
* @param array $options['triggerids']
* @param array $options['status']
* @param bool $options['editable']
* @param array $options['count']
* @param array $options['pattern']
* @param array $options['limit']
* @param array $options['order']
*
* @return array|int item data as array or false if error
*/
public function get(array $options = []) {
$result = [];
$sqlParts = [
'select' => ['triggers' => 't.triggerid'],
'from' => ['t' => 'triggers t'],
'where' => [],
'group' => [],
'order' => [],
'limit' => null
];
$defOptions = [
'groupids' => null,
'templateids' => null,
'hostids' => null,
'triggerids' => null,
'itemids' => null,
'functions' => null,
'inherited' => null,
'dependent' => null,
'templated' => null,
'monitored' => null,
'active' => null,
'maintenance' => null,
'withUnacknowledgedEvents' => null,
'withAcknowledgedEvents' => null,
'withLastEventUnacknowledged' => null,
'skipDependent' => null,
'nopermissions' => null,
'editable' => false,
// timing
'lastChangeSince' => null,
'lastChangeTill' => null,
// filter
'group' => null,
'host' => null,
'only_true' => null,
'min_severity' => null,
'evaltype' => TAG_EVAL_TYPE_AND_OR,
'tags' => null,
'filter' => null,
'search' => null,
'searchByAny' => null,
'startSearch' => false,
'excludeSearch' => false,
'searchWildcardsEnabled' => null,
// output
'expandDescription' => null,
'expandComment' => null,
'expandExpression' => null,
'output' => API_OUTPUT_EXTEND,
'selectGroups' => null,
'selectHostGroups' => null,
'selectTemplateGroups' => null,
'selectHosts' => null,
'selectItems' => null,
'selectFunctions' => null,
'selectDependencies' => null,
'selectDiscoveryRule' => null,
'selectLastEvent' => null,
'selectTags' => null,
'selectTriggerDiscovery' => null,
'countOutput' => false,
'groupCount' => false,
'preservekeys' => false,
'sortfield' => '',
'sortorder' => '',
'limit' => null,
'limitSelects' => null
];
$options = zbx_array_merge($defOptions, $options);
$this->checkDeprecatedParam($options, 'selectGroups');
// editable + PERMISSION CHECK
if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
$userGroups = getUserGroupsByUserId(self::$userData['userid']);
$sqlParts['where'][] = 'NOT EXISTS ('.
'SELECT NULL'.
' FROM functions f,items i,hosts_groups hgg'.
' LEFT JOIN rights r'.
' ON r.id=hgg.groupid'.
' AND '.dbConditionInt('r.groupid', $userGroups).
' WHERE t.triggerid=f.triggerid '.
' AND f.itemid=i.itemid'.
' AND i.hostid=hgg.hostid'.
' GROUP BY i.hostid'.
' HAVING MAX(permission)<'.zbx_dbstr($permission).
' OR MIN(permission) IS NULL'.
' OR MIN(permission)='.PERM_DENY.
')';
}
// groupids
if ($options['groupids'] !== null) {
zbx_value2array($options['groupids']);
sort($options['groupids']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
$sqlParts['where']['hgi'] = 'hg.hostid=i.hostid';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['where']['groupid'] = dbConditionInt('hg.groupid', $options['groupids']);
if ($options['groupCount']) {
$sqlParts['group']['hg'] = 'hg.groupid';
}
}
// templateids
if ($options['templateids'] !== null) {
zbx_value2array($options['templateids']);
if ($options['hostids'] !== null) {
zbx_value2array($options['hostids']);
$options['hostids'] = array_merge($options['hostids'], $options['templateids']);
}
else {
$options['hostids'] = $options['templateids'];
}
}
// hostids
if ($options['hostids'] !== null) {
zbx_value2array($options['hostids']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['hostids']);
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
if ($options['groupCount']) {
$sqlParts['group']['i'] = 'i.hostid';
}
}
// triggerids
if ($options['triggerids'] !== null) {
zbx_value2array($options['triggerids']);
$sqlParts['where']['triggerid'] = dbConditionInt('t.triggerid', $options['triggerids']);
}
// itemids
if ($options['itemids'] !== null) {
zbx_value2array($options['itemids']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['where']['itemid'] = dbConditionInt('f.itemid', $options['itemids']);
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
if ($options['groupCount']) {
$sqlParts['group']['f'] = 'f.itemid';
}
}
// functions
if ($options['functions'] !== null) {
zbx_value2array($options['functions']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where'][] = dbConditionString('f.name', $options['functions']);
}
// monitored
if ($options['monitored'] !== null) {
$sqlParts['where']['monitored'] = 'NOT EXISTS ('.
'SELECT NULL'.
' FROM functions f,items i,hosts h'.
' WHERE t.triggerid=f.triggerid'.
' AND f.itemid=i.itemid'.
' AND i.hostid=h.hostid'.
' AND ('.
'i.status<>'.ITEM_STATUS_ACTIVE.
' OR h.status<>'.HOST_STATUS_MONITORED.
')'.
')';
$sqlParts['where']['status'] = 't.status='.TRIGGER_STATUS_ENABLED;
}
// active
if ($options['active'] !== null) {
$sqlParts['where']['active'] = 'NOT EXISTS ('.
'SELECT NULL'.
' FROM functions f,items i,hosts h'.
' WHERE t.triggerid=f.triggerid'.
' AND f.itemid=i.itemid'.
' AND i.hostid=h.hostid'.
' AND h.status<>'.HOST_STATUS_MONITORED.
')';
$sqlParts['where']['status'] = 't.status='.TRIGGER_STATUS_ENABLED;
}
// maintenance
if ($options['maintenance'] !== null) {
$sqlParts['where'][] = ($options['maintenance'] == 0 ? 'NOT ' : '').
'EXISTS ('.
'SELECT NULL'.
' FROM functions f,items i,hosts h'.
' WHERE t.triggerid=f.triggerid'.
' AND f.itemid=i.itemid'.
' AND i.hostid=h.hostid'.
' AND h.maintenance_status='.HOST_MAINTENANCE_STATUS_ON.
')';
$sqlParts['where'][] = 't.status='.TRIGGER_STATUS_ENABLED;
}
// lastChangeSince
if ($options['lastChangeSince'] !== null) {
$sqlParts['where']['lastchangesince'] = 't.lastchange>'.zbx_dbstr($options['lastChangeSince']);
}
// lastChangeTill
if ($options['lastChangeTill'] !== null) {
$sqlParts['where']['lastchangetill'] = 't.lastchange<'.zbx_dbstr($options['lastChangeTill']);
}
// withUnacknowledgedEvents
if ($options['withUnacknowledgedEvents'] !== null) {
$sqlParts['where']['unack'] = 'EXISTS ('.
'SELECT NULL'.
' FROM events e'.
' WHERE t.triggerid=e.objectid'.
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND e.object='.EVENT_OBJECT_TRIGGER.
' AND e.value='.TRIGGER_VALUE_TRUE.
' AND e.acknowledged='.EVENT_NOT_ACKNOWLEDGED.
')';
}
// withAcknowledgedEvents
if ($options['withAcknowledgedEvents'] !== null) {
$sqlParts['where']['ack'] = 'NOT EXISTS ('.
'SELECT NULL'.
' FROM events e'.
' WHERE e.objectid=t.triggerid'.
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND e.object='.EVENT_OBJECT_TRIGGER.
' AND e.value='.TRIGGER_VALUE_TRUE.
' AND e.acknowledged='.EVENT_NOT_ACKNOWLEDGED.
')';
}
// templated
if ($options['templated'] !== null) {
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
if ($options['templated']) {
$sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE;
}
else {
$sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE;
}
}
// inherited
if ($options['inherited'] !== null) {
if ($options['inherited']) {
$sqlParts['where'][] = 't.templateid IS NOT NULL';
}
else {
$sqlParts['where'][] = 't.templateid IS NULL';
}
}
// dependent
if ($options['dependent'] !== null) {
if ($options['dependent']) {
$sqlParts['where'][] = 'EXISTS ('.
'SELECT NULL'.
' FROM trigger_depends td'.
' WHERE td.triggerid_down=t.triggerid'.
')';
}
else {
$sqlParts['where'][] = 'NOT EXISTS ('.
'SELECT NULL'.
' FROM trigger_depends td'.
' WHERE td.triggerid_down=t.triggerid'.
')';
}
}
// search
if (is_array($options['search'])) {
zbx_db_search('triggers t', $options, $sqlParts);
}
// filter
if ($options['filter'] === null) {
$options['filter'] = [];
}
if (is_array($options['filter'])) {
if (!array_key_exists('flags', $options['filter'])) {
$options['filter']['flags'] = [
ZBX_FLAG_DISCOVERY_NORMAL,
ZBX_FLAG_DISCOVERY_CREATED
];
}
$this->dbFilter('triggers t', $options, $sqlParts);
if (array_key_exists('host', $options['filter']) && $options['filter']['host'] !== null) {
zbx_value2array($options['filter']['host']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
$sqlParts['where']['host'] = dbConditionString('h.host', $options['filter']['host']);
}
if (array_key_exists('hostid', $options['filter']) && $options['filter']['hostid'] !== null) {
zbx_value2array($options['filter']['hostid']);
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['filter']['hostid']);
}
}
// group
if ($options['group'] !== null) {
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
$sqlParts['from']['hstgrp'] = 'hstgrp g';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['where']['hgi'] = 'hg.hostid=i.hostid';
$sqlParts['where']['ghg'] = 'g.groupid = hg.groupid';
$sqlParts['where']['group'] = ' g.name='.zbx_dbstr($options['group']);
}
// host
if ($options['host'] !== null) {
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where']['ft'] = 'f.triggerid=t.triggerid';
$sqlParts['where']['fi'] = 'f.itemid=i.itemid';
$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
$sqlParts['where']['host'] = ' h.host='.zbx_dbstr($options['host']);
}
// only_true
if ($options['only_true'] !== null) {
$sqlParts['where']['ot'] = '((t.value='.TRIGGER_VALUE_TRUE.')'.
' OR ((t.value='.TRIGGER_VALUE_FALSE.')'.
' AND (t.lastchange>'.
(time() - timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::OK_PERIOD))).
'))'.
')';
}
// min_severity
if ($options['min_severity'] !== null) {
$sqlParts['where'][] = 't.priority>='.zbx_dbstr($options['min_severity']);
}
// tags
if ($options['tags'] !== null && $options['tags']) {
$sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 't',
'trigger_tag', 'triggerid'
);
}
// limit
if (!zbx_ctype_digit($options['limit']) || !$options['limit']) {
$options['limit'] = null;
}
$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
// return count or grouped counts via direct SQL count
if ($options['countOutput'] && !$this->requiresPostSqlFiltering($options)) {
$dbRes = DBselect(self::createSelectQueryFromParts($sqlParts), $options['limit']);
while ($trigger = DBfetch($dbRes)) {
if ($options['groupCount']) {
$result[] = $trigger;
}
else {
$result = $trigger['rowscount'];
}
}
return $result;
}
$result = zbx_toHash($this->customFetch(self::createSelectQueryFromParts($sqlParts), $options), 'triggerid');
// return count for post SQL filtered result sets
if ($options['countOutput']) {
return (string) count($result);
}
if ($result) {
$result = $this->addRelatedObjects($options, $result);
}
// expandDescription
if ($options['expandDescription'] !== null && $result && array_key_exists('description', reset($result))) {
$result = CMacrosResolverHelper::resolveTriggerNames($result);
}
// expandComment
if ($options['expandComment'] !== null && $result && array_key_exists('comments', reset($result))) {
$result = CMacrosResolverHelper::resolveTriggerDescriptions($result, ['sources' => ['comments']]);
}
// expand expressions
if ($options['expandExpression'] !== null && $result) {
$sources = [];
if (array_key_exists('expression', reset($result))) {
$sources[] = 'expression';
}
if (array_key_exists('recovery_expression', reset($result))) {
$sources[] = 'recovery_expression';
}
if ($sources) {
$result = CMacrosResolverHelper::resolveTriggerExpressions($result,
['resolve_usermacros' => true, 'resolve_macros' => true, 'sources' => $sources]
);
}
}
// removing keys (hash -> array)
if (!$options['preservekeys']) {
$result = zbx_cleanHashes($result);
}
$result = $this->unsetExtraFields($result, ['state', 'expression'], $options['output']);
// Triggers share table with trigger prototypes. Therefore remove trigger unrelated fields.
if ($this->outputIsRequested('discover', $options['output'])) {
foreach ($result as &$row) {
unset($row['discover']);
}
unset($row);
}
return $result;
}
/**
* Add triggers.
*
* Trigger params: expression, description, type, priority, status, comments, url_name, url, templateid
*
* @param array $triggers
*
* @return array
*/
public function create(array $triggers) {
$this->validateCreate($triggers);
$this->createReal($triggers);
$this->checkDependenciesLinks($triggers);
$this->inherit($triggers);
$this->updateDependencies($triggers);
return ['triggerids' => array_column($triggers, 'triggerid')];
}
/**
* Update triggers.
*
* If a trigger expression is passed in any of the triggers, it must be in it's exploded form.
*
* @param array $triggers
*
* @return array
*/
public function update(array $triggers): array {
$this->validateUpdate($triggers, $db_triggers);
$this->updateReal($triggers, $db_triggers);
self::checkExistingDependencies($triggers, $db_triggers);
$this->inherit($triggers);
$this->updateDependencies($triggers, $db_triggers);
return ['triggerids' => array_column($triggers, 'triggerid')];
}
/**
* Delete triggers.
*
* @param array $triggerids
*
* @return array
*/
public function delete(array $triggerids) {
$this->validateDelete($triggerids, $db_triggers);
CTriggerManager::delete($triggerids);
$this->addAuditBulk(CAudit::ACTION_DELETE, CAudit::RESOURCE_TRIGGER, $db_triggers);
return ['triggerids' => $triggerids];
}
/**
* Validates the input parameters for the delete() method.
*
* @param array $triggerids [IN/OUT]
* @param array $db_triggers [OUT]
*
* @throws APIException if the input is invalid.
*/
protected function validateDelete(array &$triggerids, array &$db_triggers = null) {
$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
if (!CApiInputValidator::validate($api_input_rules, $triggerids, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_triggers = $this->get([
'output' => ['triggerid', 'description', 'expression', 'templateid'],
'triggerids' => $triggerids,
'editable' => true,
'preservekeys' => true
]);
foreach ($triggerids as $triggerid) {
if (!array_key_exists($triggerid, $db_triggers)) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_('No permissions to referred object or it does not exist!')
);
}
$db_trigger = $db_triggers[$triggerid];
if ($db_trigger['templateid'] != 0) {
self::exception(ZBX_API_ERROR_PARAMETERS,
_s('Cannot delete templated trigger "%1$s:%2$s".', $db_trigger['description'],
CMacrosResolverHelper::resolveTriggerExpression($db_trigger['expression'])
)
);
}
}
}
/**
* Check the existing dependencies if the trigger expressions were changed.
*
* @param array $triggers
* @param array $db_triggers
*/
private static function checkExistingDependencies(array $triggers, array $db_triggers): void {
$triggerids = [];
$hostids = [];
foreach ($triggers as $trigger) {
if (array_key_exists('hosts', $db_triggers[$trigger['triggerid']])) {
$triggerids[$trigger['triggerid']] = true;
$hostids += $db_triggers[$trigger['triggerid']]['hosts'];
}
}
if (!$triggerids) {
return;
}
/*
* 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))
);
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 (!array_key_exists($trigger['triggerid'], $triggerids)
|| !$db_triggers[$trigger['triggerid']]['hosts']) {
continue;
}
$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;
}
}
if (!$trigger_dependencies) {
return;
}
/*
* 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.
*/
$trigger_hosts = self::getTriggerHosts($trigger_dependencies);
/*
* 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);
}
protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
if (!$options['countOutput'] && $options['expandDescription'] !== null || $options['expandComment'] !== null) {
$sqlParts = $this->addQuerySelect($this->fieldId('expression'), $sqlParts);
}
return $sqlParts;
}
protected function addRelatedObjects(array $options, array $result) {
$result = parent::addRelatedObjects($options, $result);
if (!$result) {
return $result;
}
$triggerids = array_keys($result);
// adding trigger dependencies
if ($options['selectDependencies'] !== null && $options['selectDependencies'] != API_OUTPUT_COUNT) {
$dependencies = [];
$relationMap = new CRelationMap();
$res = DBselect(
'SELECT td.triggerid_up,td.triggerid_down'.
' FROM trigger_depends td'.
' WHERE '.dbConditionInt('td.triggerid_down', $triggerids)
);
while ($relation = DBfetch($res)) {
$relationMap->addRelation($relation['triggerid_down'], $relation['triggerid_up']);
}
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$dependencies = $this->get([
'output' => $options['selectDependencies'],
'triggerids' => $related_ids,
'preservekeys' => true
]);
}
$result = $relationMap->mapMany($result, $dependencies, 'dependencies');
}
// adding items
if ($options['selectItems'] !== null && $options['selectItems'] != API_OUTPUT_COUNT) {
$relationMap = $this->createRelationMap($result, 'triggerid', 'itemid', 'functions');
$items = API::Item()->get([
'output' => $options['selectItems'],
'itemids' => $relationMap->getRelatedIds(),
'webitems' => true,
'nopermissions' => true,
'preservekeys' => true
]);
$result = $relationMap->mapMany($result, $items, 'items');
}
// adding discoveryrule
if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) {
$discoveryRules = [];
$relationMap = new CRelationMap();
$dbRules = DBselect(
'SELECT id.parent_itemid,td.triggerid'.
' FROM trigger_discovery td,item_discovery id,functions f'.
' WHERE '.dbConditionInt('td.triggerid', $triggerids).
' AND td.parent_triggerid=f.triggerid'.
' AND f.itemid=id.itemid'
);
while ($rule = DBfetch($dbRules)) {
$relationMap->addRelation($rule['triggerid'], $rule['parent_itemid']);
}
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$discoveryRules = API::DiscoveryRule()->get([
'output' => $options['selectDiscoveryRule'],
'itemids' => $related_ids,
'nopermissions' => true,
'preservekeys' => true
]);
}
$result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule');
}
// adding last event
if ($options['selectLastEvent'] !== null) {
foreach ($result as $triggerId => $trigger) {
$result[$triggerId]['lastEvent'] = [];
}
if (is_array($options['selectLastEvent'])) {
$pkFieldId = $this->pk('events');
$outputFields = [
'objectid' => $this->fieldId('objectid', 'e'),
'ns' => $this->fieldId('ns', 'e'),
$pkFieldId => $this->fieldId($pkFieldId, 'e')
];
foreach ($options['selectLastEvent'] as $field) {
if ($this->hasField($field, 'events')) {
$outputFields[$field] = $this->fieldId($field, 'e');
}
}
$outputFields = implode(',', $outputFields);
}
else {
$outputFields = 'e.*';
}
// Due to performance issues, avoid using 'ORDER BY' for outer SELECT.
$dbEvents = DBselect(
'SELECT '.$outputFields.
' FROM events e'.
' JOIN ('.
'SELECT e2.source,e2.object,e2.objectid,MAX(clock) AS clock'.
' FROM events e2'.
' WHERE e2.source='.EVENT_SOURCE_TRIGGERS.
' AND e2.object='.EVENT_OBJECT_TRIGGER.
' AND '.dbConditionInt('e2.objectid', $triggerids).
' GROUP BY e2.source,e2.object,e2.objectid'.
') e3 ON e3.source=e.source'.
' AND e3.object=e.object'.
' AND e3.objectid=e.objectid'.
' AND e3.clock=e.clock'
);
// in case there are multiple records with same 'clock' for one trigger, we'll get different 'ns'
$lastEvents = [];
while ($dbEvent = DBfetch($dbEvents)) {
$triggerId = $dbEvent['objectid'];
$ns = $dbEvent['ns'];
// unset fields, that were not requested
if (is_array($options['selectLastEvent'])) {
if (!in_array('objectid', $options['selectLastEvent'])) {
unset($dbEvent['objectid']);
}
if (!in_array('ns', $options['selectLastEvent'])) {
unset($dbEvent['ns']);
}
}
$lastEvents[$triggerId][$ns] = $dbEvent;
}
foreach ($lastEvents as $triggerId => $events) {
// find max 'ns' for each trigger and that will be the 'lastEvent'
$maxNs = max(array_keys($events));
$result[$triggerId]['lastEvent'] = $events[$maxNs];
}
}
// adding trigger discovery
if ($options['selectTriggerDiscovery'] !== null && $options['selectTriggerDiscovery'] !== API_OUTPUT_COUNT) {
foreach ($result as &$trigger) {
$trigger['triggerDiscovery'] = [];
}
unset($trigger);
$sql_select = ['triggerid'];
foreach (['parent_triggerid', 'ts_delete'] as $field) {
if ($this->outputIsRequested($field, $options['selectTriggerDiscovery'])) {
$sql_select[] = $field;
}
}
$trigger_discoveries = DBselect(
'SELECT '.implode(',', $sql_select).
' FROM trigger_discovery'.
' WHERE '.dbConditionInt('triggerid', $triggerids)
);
while ($trigger_discovery = DBfetch($trigger_discoveries)) {
$triggerid = $trigger_discovery['triggerid'];
unset($trigger_discovery['triggerid']);
$result[$triggerid]['triggerDiscovery'] = $trigger_discovery;
}
}
return $result;
}
protected function applyQuerySortField($sortfield, $sortorder, $alias, array $sqlParts) {
if ($sortfield === 'hostname') {
$sqlParts['select']['hostname'] = 'h.name AS hostname';
$sqlParts['from']['functions'] = 'functions f';
$sqlParts['from']['items'] = 'items i';
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where'][] = 't.triggerid = f.triggerid';
$sqlParts['where'][] = 'f.itemid = i.itemid';
$sqlParts['where'][] = 'i.hostid = h.hostid';
$sqlParts['order'][] = 'h.name '.$sortorder;
}
else {
$sqlParts = parent::applyQuerySortField($sortfield, $sortorder, $alias, $sqlParts);
}
return $sqlParts;
}
protected function requiresPostSqlFiltering(array $options) {
return $options['skipDependent'] !== null || $options['withLastEventUnacknowledged'] !== null;
}
protected function applyPostSqlFiltering(array $triggers, array $options) {
$triggers = zbx_toHash($triggers, 'triggerid');
// unset triggers which depend on at least one problem trigger upstream into dependency tree
if ($options['skipDependent'] !== null) {
// Result trigger IDs of all triggers in results.
$resultTriggerIds = zbx_objectValues($triggers, 'triggerid');
// Will contain IDs of all triggers on which some other trigger depends.
$allUpTriggerIds = [];
// Trigger dependency map.
$downToUpTriggerIds = [];
// Values (state) of each "up" trigger ID is stored in here.
$upTriggerValues = [];
// Will contain IDs of all triggers either disabled directly, or by having disabled item or disabled host.
$disabledTriggerIds = [];
// First loop uses result trigger IDs.
$triggerIds = $resultTriggerIds;
do {
// Fetch all dependency records where "down" trigger IDs are in current iteration trigger IDs.
$dbResult = DBselect(
'SELECT d.triggerid_down,d.triggerid_up,t.value'.
' FROM trigger_depends d,triggers t'.
' WHERE d.triggerid_up=t.triggerid'.
' AND '.dbConditionInt('d.triggerid_down', $triggerIds)
);
// Add trigger IDs as keys and empty arrays as values.
$downToUpTriggerIds = $downToUpTriggerIds + array_fill_keys($triggerIds, []);
$triggerIds = [];
while ($dependency = DBfetch($dbResult)) {
// Trigger ID for "down" trigger, which has dependencies.
$downTriggerId = $dependency['triggerid_down'];
// Trigger ID for "up" trigger, on which the other ("up") trigger depends.
$upTriggerId = $dependency['triggerid_up'];
// Add "up" trigger ID to mapping. We also index by $upTrigger because later these arrays
// are combined with + and this way indexes and values do not break.
$downToUpTriggerIds[$downTriggerId][$upTriggerId] = $upTriggerId;
// Add ID of this "up" trigger to all known "up" triggers.
$allUpTriggerIds[] = $upTriggerId;
// Remember value of this "up" trigger.
$upTriggerValues[$upTriggerId] = $dependency['value'];
// Add ID of this "up" trigger to the list of trigger IDs which should be mapped.
$triggerIds[] = $upTriggerId;
}
} while ($triggerIds);
// Fetch trigger IDs for triggers that are disabled, have disabled items or disabled item hosts.
$dbResult = DBSelect(
'SELECT t.triggerid'.
' FROM triggers t,functions f,items i,hosts h'.
' WHERE t.triggerid=f.triggerid'.
' AND f.itemid=i.itemid'.
' AND i.hostid=h.hostid'.
' AND ('.
'i.status='.ITEM_STATUS_DISABLED.
' OR h.status='.HOST_STATUS_NOT_MONITORED.
' OR t.status='.TRIGGER_STATUS_DISABLED.
')'.
' AND '.dbConditionInt('t.triggerid', $allUpTriggerIds)
);
while ($row = DBfetch($dbResult)) {
$resultTriggerId = $row['triggerid'];
$disabledTriggerIds[$resultTriggerId] = $resultTriggerId;
}
// Now process all mapped dependencies and unset any disabled "up" triggers so they do not participate in
// decisions regarding nesting resolution in next step.
foreach ($downToUpTriggerIds as $downTriggerId => $upTriggerIds) {
$upTriggerIdsToUnset = [];
foreach ($upTriggerIds as $upTriggerId) {
if (isset($disabledTriggerIds[$upTriggerId])) {
unset($downToUpTriggerIds[$downTriggerId][$upTriggerId]);
}
}
}
// Resolve dependencies for all result set triggers.
foreach ($resultTriggerIds as $resultTriggerId) {
// We start with result trigger.
$triggerIds = [$resultTriggerId];
// This also is unrolled recursive function and is repeated until there are no more trigger IDs to
// check, add and resolve.
do {
$nextTriggerIds = [];
foreach ($triggerIds as $triggerId) {
// Loop through all "up" triggers.
foreach ($downToUpTriggerIds[$triggerId] as $upTriggerId) {
if ($downToUpTriggerIds[$upTriggerId]) {
// If there this "up" trigger has "up" triggers of it's own, merge them and proceed with recursion.
$downToUpTriggerIds[$resultTriggerId] += $downToUpTriggerIds[$upTriggerId];
// Add trigger ID to be processed in next loop iteration.
$nextTriggerIds[] = $upTriggerId;
}
}
}
$triggerIds = $nextTriggerIds;
} while ($triggerIds);
}
// Clean result set.
foreach ($resultTriggerIds as $resultTriggerId) {
foreach ($downToUpTriggerIds[$resultTriggerId] as $upTriggerId) {
// If "up" trigger is in problem state, dependent trigger should not be returned and is removed
// from results.
if ($upTriggerValues[$upTriggerId] == TRIGGER_VALUE_TRUE) {
unset($triggers[$resultTriggerId]);
}
}
// Check if result trigger is disabled and if so, remove from results.
if (isset($disabledTriggerIds[$resultTriggerId])) {
unset($triggers[$resultTriggerId]);
}
}
}
// withLastEventUnacknowledged
if ($options['withLastEventUnacknowledged'] !== null) {
$triggerIds = zbx_objectValues($triggers, 'triggerid');
$eventIds = [];
$eventsDb = DBselect(
'SELECT MAX(e.eventid) AS eventid,e.objectid'.
' FROM events e'.
' WHERE e.object='.EVENT_OBJECT_TRIGGER.
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND '.dbConditionInt('e.objectid', $triggerIds).
' AND '.dbConditionInt('e.value', [TRIGGER_VALUE_TRUE]).
' GROUP BY e.objectid'
);
while ($event = DBfetch($eventsDb)) {
$eventIds[] = $event['eventid'];
}
$correctTriggerIds = DBfetchArrayAssoc(DBselect(
'SELECT e.objectid'.
' FROM events e '.
' WHERE '.dbConditionInt('e.eventid', $eventIds).
' AND e.acknowledged=0'
), 'objectid');
foreach ($triggers as $triggerId => $trigger) {
if (!isset($correctTriggerIds[$triggerId])) {
unset($triggers[$triggerId]);
}
}
}
return $triggers;
}
}