['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; } }