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.

4152 lines
131 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 discovery rules.
*/
class CDiscoveryRule extends CItemGeneral {
public const ACCESS_RULES = parent::ACCESS_RULES + [
'copy' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
];
protected $tableName = 'items';
protected $tableAlias = 'i';
protected $sortColumns = ['itemid', 'name', 'key_', 'delay', 'type', 'status'];
public const OUTPUT_FIELDS = ['itemid', 'type', 'snmp_oid', 'hostid', 'name', 'key_', 'delay', 'status',
'trapper_hosts', 'templateid', 'params', 'ipmi_sensor', 'authtype', 'username', 'password', 'publickey',
'privatekey', 'interfaceid', 'description', 'lifetime', 'jmx_endpoint', 'master_itemid', 'timeout', 'url',
'query_fields', 'posts', 'status_codes', 'follow_redirects', 'post_type', 'http_proxy', 'headers',
'retrieve_mode', 'request_method', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'verify_peer',
'verify_host', 'allow_traps', 'state', 'error', 'parameters'
];
/**
* @inheritDoc
*/
const SUPPORTED_PREPROCESSING_TYPES = [ZBX_PREPROC_REGSUB, ZBX_PREPROC_XPATH, ZBX_PREPROC_JSONPATH,
ZBX_PREPROC_VALIDATE_NOT_REGEX, ZBX_PREPROC_ERROR_FIELD_JSON, ZBX_PREPROC_ERROR_FIELD_XML,
ZBX_PREPROC_THROTTLE_TIMED_VALUE, ZBX_PREPROC_SCRIPT, ZBX_PREPROC_PROMETHEUS_TO_JSON,
ZBX_PREPROC_CSV_TO_JSON, ZBX_PREPROC_STR_REPLACE, ZBX_PREPROC_XML_TO_JSON, ZBX_PREPROC_SNMP_WALK_VALUE,
ZBX_PREPROC_SNMP_WALK_TO_JSON
];
/**
* Define a set of supported item types.
*
* @var array
*/
const SUPPORTED_ITEM_TYPES = [ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL,
ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI, ITEM_TYPE_SSH,
ITEM_TYPE_TELNET, ITEM_TYPE_JMX, ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT
];
/**
* A list of supported operation fields indexed by table name.
*/
const OPERATION_FIELDS = [
'lld_override_opdiscover' => 'opdiscover',
'lld_override_opstatus' => 'opstatus',
'lld_override_opperiod' => 'opperiod',
'lld_override_ophistory' => 'ophistory',
'lld_override_optrends' => 'optrends',
'lld_override_opseverity' => 'opseverity',
'lld_override_optag' => 'optag',
'lld_override_optemplate' => 'optemplate',
'lld_override_opinventory' => 'opinventory'
];
/**
* Get DiscoveryRule data
*/
public function get($options = []) {
$result = [];
$sqlParts = [
'select' => ['items' => 'i.itemid'],
'from' => ['items' => 'items i'],
'where' => ['i.flags='.ZBX_FLAG_DISCOVERY_RULE],
'group' => [],
'order' => [],
'limit' => null
];
$defOptions = [
'groupids' => null,
'templateids' => null,
'hostids' => null,
'itemids' => null,
'interfaceids' => null,
'inherited' => null,
'templated' => null,
'monitored' => null,
'editable' => false,
'nopermissions' => null,
// filter
'filter' => null,
'search' => null,
'searchByAny' => null,
'startSearch' => false,
'excludeSearch' => false,
'searchWildcardsEnabled' => null,
// output
'output' => API_OUTPUT_EXTEND,
'selectHosts' => null,
'selectItems' => null,
'selectTriggers' => null,
'selectGraphs' => null,
'selectHostPrototypes' => null,
'selectFilter' => null,
'selectLLDMacroPaths' => null,
'selectPreprocessing' => null,
'selectOverrides' => null,
'countOutput' => false,
'groupCount' => false,
'preservekeys' => false,
'sortfield' => '',
'sortorder' => '',
'limit' => null,
'limitSelects' => null
];
$options = zbx_array_merge($defOptions, $options);
// 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'][] = 'EXISTS ('.
'SELECT NULL'.
' FROM hosts_groups hgg'.
' JOIN rights r'.
' ON r.id=hgg.groupid'.
' AND '.dbConditionInt('r.groupid', $userGroups).
' WHERE i.hostid=hgg.hostid'.
' GROUP BY hgg.hostid'.
' HAVING MIN(r.permission)>'.PERM_DENY.
' AND MAX(r.permission)>='.zbx_dbstr($permission).
')';
}
// templateids
if (!is_null($options['templateids'])) {
zbx_value2array($options['templateids']);
if (!is_null($options['hostids'])) {
zbx_value2array($options['hostids']);
$options['hostids'] = array_merge($options['hostids'], $options['templateids']);
}
else {
$options['hostids'] = $options['templateids'];
}
}
// hostids
if (!is_null($options['hostids'])) {
zbx_value2array($options['hostids']);
$sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['hostids']);
if ($options['groupCount']) {
$sqlParts['group']['i'] = 'i.hostid';
}
}
// itemids
if (!is_null($options['itemids'])) {
zbx_value2array($options['itemids']);
$sqlParts['where']['itemid'] = dbConditionInt('i.itemid', $options['itemids']);
}
// interfaceids
if (!is_null($options['interfaceids'])) {
zbx_value2array($options['interfaceids']);
$sqlParts['where']['interfaceid'] = dbConditionId('i.interfaceid', $options['interfaceids']);
if ($options['groupCount']) {
$sqlParts['group']['i'] = 'i.interfaceid';
}
}
// groupids
if ($options['groupids'] !== null) {
zbx_value2array($options['groupids']);
$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
$sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']);
$sqlParts['where'][] = 'hg.hostid=i.hostid';
if ($options['groupCount']) {
$sqlParts['group']['hg'] = 'hg.groupid';
}
}
// inherited
if (!is_null($options['inherited'])) {
if ($options['inherited']) {
$sqlParts['where'][] = 'i.templateid IS NOT NULL';
}
else {
$sqlParts['where'][] = 'i.templateid IS NULL';
}
}
// templated
if (!is_null($options['templated'])) {
$sqlParts['from']['hosts'] = 'hosts h';
$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;
}
}
// monitored
if (!is_null($options['monitored'])) {
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
if ($options['monitored']) {
$sqlParts['where'][] = 'h.status='.HOST_STATUS_MONITORED;
$sqlParts['where'][] = 'i.status='.ITEM_STATUS_ACTIVE;
}
else {
$sqlParts['where'][] = '(h.status<>'.HOST_STATUS_MONITORED.' OR i.status<>'.ITEM_STATUS_ACTIVE.')';
}
}
// search
if (is_array($options['search'])) {
if (array_key_exists('error', $options['search']) && $options['search']['error'] !== null) {
zbx_db_search('item_rtdata ir', ['search' => ['error' => $options['search']['error']]] + $options,
$sqlParts
);
}
zbx_db_search('items i', $options, $sqlParts);
}
// filter
if (is_array($options['filter'])) {
if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) {
$sqlParts['where'][] = makeUpdateIntervalFilter('i.delay', $options['filter']['delay']);
unset($options['filter']['delay']);
}
if (array_key_exists('lifetime', $options['filter']) && $options['filter']['lifetime'] !== null) {
$options['filter']['lifetime'] = getTimeUnitFilters($options['filter']['lifetime']);
}
if (array_key_exists('state', $options['filter']) && $options['filter']['state'] !== null) {
$this->dbFilter('item_rtdata ir', ['filter' => ['state' => $options['filter']['state']]] + $options,
$sqlParts
);
}
$this->dbFilter('items i', $options, $sqlParts);
if (isset($options['filter']['host'])) {
zbx_value2array($options['filter']['host']);
$sqlParts['from']['hosts'] = 'hosts h';
$sqlParts['where']['hi'] = 'h.hostid=i.hostid';
$sqlParts['where']['h'] = dbConditionString('h.host', $options['filter']['host']);
}
}
// limit
if (zbx_ctype_digit($options['limit']) && $options['limit']) {
$sqlParts['limit'] = $options['limit'];
}
$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
$res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
while ($item = DBfetch($res)) {
if (!$options['countOutput']) {
$result[$item['itemid']] = $item;
continue;
}
if ($options['groupCount']) {
$result[] = $item;
}
else {
$result = $item['rowscount'];
}
}
if ($options['countOutput']) {
return $result;
}
if ($result) {
if (self::dbDistinct($sqlParts)) {
$result = $this->addNclobFieldValues($options, $result);
}
$result = $this->addRelatedObjects($options, $result);
$result = $this->unsetExtraFields($result, ['hostid'], $options['output']);
$result = $this->unsetExtraFields($result, ['name_upper']);
foreach ($result as &$rule) {
// unset the fields that are returned in the filter
unset($rule['formula'], $rule['evaltype']);
if ($options['selectFilter'] !== null) {
$filter = $this->unsetExtraFields([$rule['filter']],
['conditions', 'formula', 'evaltype'],
$options['selectFilter']
);
$filter = reset($filter);
if (isset($filter['conditions'])) {
foreach ($filter['conditions'] as &$condition) {
unset($condition['item_conditionid'], $condition['itemid']);
}
unset($condition);
}
$rule['filter'] = $filter;
}
}
unset($rule);
}
// Decode ITEM_TYPE_HTTPAGENT encoded fields.
foreach ($result as &$item) {
if (array_key_exists('query_fields', $item)) {
$query_fields = ($item['query_fields'] !== '') ? json_decode($item['query_fields'], true) : [];
$item['query_fields'] = json_last_error() ? [] : $query_fields;
}
if (array_key_exists('headers', $item)) {
$item['headers'] = $this->headersStringToArray($item['headers']);
}
// Option 'Convert to JSON' is not supported for discovery rule.
unset($item['output_format']);
}
unset($item);
if (!$options['preservekeys']) {
$result = zbx_cleanHashes($result);
}
return $result;
}
protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
$upcased_index = array_search($tableAlias.'.name_upper', $sqlParts['select']);
if ($upcased_index !== false) {
unset($sqlParts['select'][$upcased_index]);
}
if ((!$options['countOutput'] && ($this->outputIsRequested('state', $options['output'])
|| $this->outputIsRequested('error', $options['output'])))
|| (is_array($options['search']) && array_key_exists('error', $options['search']))
|| (is_array($options['filter']) && array_key_exists('state', $options['filter']))) {
$sqlParts['left_join'][] = ['alias' => 'ir', 'table' => 'item_rtdata', 'using' => 'itemid'];
$sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName];
}
if (!$options['countOutput']) {
if ($this->outputIsRequested('state', $options['output'])) {
$sqlParts = $this->addQuerySelect('ir.state', $sqlParts);
}
if ($this->outputIsRequested('error', $options['output'])) {
/*
* SQL func COALESCE use for template items because they don't have record
* in item_rtdata table and DBFetch convert null to '0'
*/
$sqlParts = $this->addQuerySelect(dbConditionCoalesce('ir.error', '', 'error'), $sqlParts);
}
// add filter fields
if ($this->outputIsRequested('formula', $options['selectFilter'])
|| $this->outputIsRequested('eval_formula', $options['selectFilter'])
|| $this->outputIsRequested('conditions', $options['selectFilter'])) {
$sqlParts = $this->addQuerySelect('i.formula', $sqlParts);
$sqlParts = $this->addQuerySelect('i.evaltype', $sqlParts);
}
if ($this->outputIsRequested('evaltype', $options['selectFilter'])) {
$sqlParts = $this->addQuerySelect('i.evaltype', $sqlParts);
}
if ($options['selectHosts'] !== null) {
$sqlParts = $this->addQuerySelect('i.hostid', $sqlParts);
}
}
return $sqlParts;
}
protected function addRelatedObjects(array $options, array $result) {
$result = parent::addRelatedObjects($options, $result);
$itemIds = array_keys($result);
// adding items
if (!is_null($options['selectItems'])) {
if ($options['selectItems'] != API_OUTPUT_COUNT) {
$items = [];
$relationMap = $this->createRelationMap($result, 'parent_itemid', 'itemid', 'item_discovery');
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$items = API::ItemPrototype()->get([
'output' => $options['selectItems'],
'itemids' => $related_ids,
'nopermissions' => true,
'preservekeys' => true
]);
}
$result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']);
}
else {
$items = API::ItemPrototype()->get([
'discoveryids' => $itemIds,
'nopermissions' => true,
'countOutput' => true,
'groupCount' => true
]);
$items = zbx_toHash($items, 'parent_itemid');
foreach ($result as $itemid => $item) {
$result[$itemid]['items'] = array_key_exists($itemid, $items) ? $items[$itemid]['rowscount'] : '0';
}
}
}
// adding triggers
if (!is_null($options['selectTriggers'])) {
if ($options['selectTriggers'] != API_OUTPUT_COUNT) {
$triggers = [];
$relationMap = new CRelationMap();
$res = DBselect(
'SELECT id.parent_itemid,f.triggerid'.
' FROM item_discovery id,items i,functions f'.
' WHERE '.dbConditionInt('id.parent_itemid', $itemIds).
' AND id.itemid=i.itemid'.
' AND i.itemid=f.itemid'
);
while ($relation = DBfetch($res)) {
$relationMap->addRelation($relation['parent_itemid'], $relation['triggerid']);
}
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$triggers = API::TriggerPrototype()->get([
'output' => $options['selectTriggers'],
'triggerids' => $related_ids,
'preservekeys' => true
]);
}
$result = $relationMap->mapMany($result, $triggers, 'triggers', $options['limitSelects']);
}
else {
$triggers = API::TriggerPrototype()->get([
'discoveryids' => $itemIds,
'countOutput' => true,
'groupCount' => true
]);
$triggers = zbx_toHash($triggers, 'parent_itemid');
foreach ($result as $itemid => $item) {
$result[$itemid]['triggers'] = array_key_exists($itemid, $triggers)
? $triggers[$itemid]['rowscount']
: '0';
}
}
}
// adding graphs
if (!is_null($options['selectGraphs'])) {
if ($options['selectGraphs'] != API_OUTPUT_COUNT) {
$graphs = [];
$relationMap = new CRelationMap();
$res = DBselect(
'SELECT id.parent_itemid,gi.graphid'.
' FROM item_discovery id,items i,graphs_items gi'.
' WHERE '.dbConditionInt('id.parent_itemid', $itemIds).
' AND id.itemid=i.itemid'.
' AND i.itemid=gi.itemid'
);
while ($relation = DBfetch($res)) {
$relationMap->addRelation($relation['parent_itemid'], $relation['graphid']);
}
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$graphs = API::GraphPrototype()->get([
'output' => $options['selectGraphs'],
'graphids' => $related_ids,
'preservekeys' => true
]);
}
$result = $relationMap->mapMany($result, $graphs, 'graphs', $options['limitSelects']);
}
else {
$graphs = API::GraphPrototype()->get([
'discoveryids' => $itemIds,
'countOutput' => true,
'groupCount' => true
]);
$graphs = zbx_toHash($graphs, 'parent_itemid');
foreach ($result as $itemid => $item) {
$result[$itemid]['graphs'] = array_key_exists($itemid, $graphs)
? $graphs[$itemid]['rowscount']
: '0';
}
}
}
// adding hosts
if ($options['selectHostPrototypes'] !== null) {
if ($options['selectHostPrototypes'] != API_OUTPUT_COUNT) {
$hostPrototypes = [];
$relationMap = $this->createRelationMap($result, 'parent_itemid', 'hostid', 'host_discovery');
$related_ids = $relationMap->getRelatedIds();
if ($related_ids) {
$hostPrototypes = API::HostPrototype()->get([
'output' => $options['selectHostPrototypes'],
'hostids' => $related_ids,
'nopermissions' => true,
'preservekeys' => true
]);
}
$result = $relationMap->mapMany($result, $hostPrototypes, 'hostPrototypes', $options['limitSelects']);
}
else {
$hostPrototypes = API::HostPrototype()->get([
'discoveryids' => $itemIds,
'nopermissions' => true,
'countOutput' => true,
'groupCount' => true
]);
$hostPrototypes = zbx_toHash($hostPrototypes, 'parent_itemid');
foreach ($result as $itemid => $item) {
$result[$itemid]['hostPrototypes'] = array_key_exists($itemid, $hostPrototypes)
? $hostPrototypes[$itemid]['rowscount']
: '0';
}
}
}
if ($options['selectFilter'] !== null) {
$formulaRequested = $this->outputIsRequested('formula', $options['selectFilter']);
$evalFormulaRequested = $this->outputIsRequested('eval_formula', $options['selectFilter']);
$conditionsRequested = $this->outputIsRequested('conditions', $options['selectFilter']);
$filters = [];
foreach ($result as $rule) {
$filters[$rule['itemid']] = [
'evaltype' => $rule['evaltype'],
'formula' => isset($rule['formula']) ? $rule['formula'] : ''
];
}
// adding conditions
if ($formulaRequested || $evalFormulaRequested || $conditionsRequested) {
$conditions = DB::select('item_condition', [
'output' => ['item_conditionid', 'macro', 'value', 'itemid', 'operator'],
'filter' => ['itemid' => $itemIds],
'preservekeys' => true,
'sortfield' => ['item_conditionid']
]);
$relationMap = $this->createRelationMap($conditions, 'itemid', 'item_conditionid');
$filters = $relationMap->mapMany($filters, $conditions, 'conditions');
foreach ($filters as &$filter) {
// in case of a custom expression - use the given formula
if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$formula = $filter['formula'];
}
// in other cases - generate the formula automatically
else {
// sort the conditions by macro before generating the formula
$conditions = zbx_toHash($filter['conditions'], 'item_conditionid');
$conditions = order_macros($conditions, 'macro');
$formulaConditions = [];
foreach ($conditions as $condition) {
$formulaConditions[$condition['item_conditionid']] = $condition['macro'];
}
$formula = CConditionHelper::getFormula($formulaConditions, $filter['evaltype']);
}
// generate formulaids from the effective formula
$formulaIds = CConditionHelper::getFormulaIds($formula);
foreach ($filter['conditions'] as &$condition) {
$condition['formulaid'] = $formulaIds[$condition['item_conditionid']];
}
unset($condition);
// generated a letter based formula only for rules with custom expressions
if ($formulaRequested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
}
if ($evalFormulaRequested) {
$filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaIds);
}
}
unset($filter);
}
// add filters to the result
foreach ($result as &$rule) {
$rule['filter'] = $filters[$rule['itemid']];
}
unset($rule);
}
// Add LLD macro paths.
if ($options['selectLLDMacroPaths'] !== null && $options['selectLLDMacroPaths'] != API_OUTPUT_COUNT) {
$lld_macro_paths = API::getApiService()->select('lld_macro_path', [
'output' => $this->outputExtend($options['selectLLDMacroPaths'], ['itemid']),
'filter' => ['itemid' => $itemIds]
]);
foreach ($result as &$lld_rule) {
$lld_rule['lld_macro_paths'] = [];
}
unset($lld_rule);
foreach ($lld_macro_paths as $lld_macro_path) {
$itemid = $lld_macro_path['itemid'];
unset($lld_macro_path['lld_macro_pathid'], $lld_macro_path['itemid']);
$result[$itemid]['lld_macro_paths'][] = $lld_macro_path;
}
}
// add overrides
if ($options['selectOverrides'] !== null && $options['selectOverrides'] != API_OUTPUT_COUNT) {
$ovrd_fields = ['itemid', 'lld_overrideid'];
$filter_requested = $this->outputIsRequested('filter', $options['selectOverrides']);
$operations_requested = $this->outputIsRequested('operations', $options['selectOverrides']);
if ($filter_requested) {
$ovrd_fields = array_merge($ovrd_fields, ['formula', 'evaltype']);
}
$overrides = API::getApiService()->select('lld_override', [
'output' => $this->outputExtend($options['selectOverrides'], $ovrd_fields),
'filter' => ['itemid' => $itemIds],
'preservekeys' => true
]);
if ($filter_requested && $overrides) {
$conditions = DB::select('lld_override_condition', [
'output' => ['lld_override_conditionid', 'macro', 'value', 'lld_overrideid', 'operator'],
'filter' => ['lld_overrideid' => array_keys($overrides)],
'sortfield' => ['lld_override_conditionid'],
'preservekeys' => true
]);
$relation_map = $this->createRelationMap($conditions, 'lld_overrideid', 'lld_override_conditionid');
foreach ($overrides as &$override) {
$override['filter'] = [
'evaltype' => $override['evaltype'],
'formula' => $override['formula']
];
unset($override['evaltype'], $override['formula']);
}
unset($override);
$overrides = $relation_map->mapMany($overrides, $conditions, 'conditions');
foreach ($overrides as &$override) {
$override['filter'] += ['conditions' => $override['conditions']];
unset($override['conditions']);
if ($override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$formula = $override['filter']['formula'];
}
else {
$conditions = zbx_toHash($override['filter']['conditions'], 'lld_override_conditionid');
$conditions = order_macros($conditions, 'macro');
$formula_conditions = [];
foreach ($conditions as $condition) {
$formula_conditions[$condition['lld_override_conditionid']] = $condition['macro'];
}
$formula = CConditionHelper::getFormula($formula_conditions, $override['filter']['evaltype']);
}
$formulaids = CConditionHelper::getFormulaIds($formula);
foreach ($override['filter']['conditions'] as &$condition) {
$condition['formulaid'] = $formulaids[$condition['lld_override_conditionid']];
unset($condition['lld_override_conditionid'], $condition['lld_overrideid']);
}
unset($condition);
if ($override['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$override['filter']['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids);
$override['filter']['eval_formula'] = $override['filter']['formula'];
}
else {
$override['filter']['eval_formula'] = CConditionHelper::replaceNumericIds($formula,
$formulaids
);
}
}
unset($override);
}
if ($operations_requested && $overrides) {
$operations = DB::select('lld_override_operation', [
'output' => ['lld_override_operationid', 'lld_overrideid', 'operationobject', 'operator', 'value'],
'filter' => ['lld_overrideid' => array_keys($overrides)],
'sortfield' => ['lld_override_operationid'],
'preservekeys' => true
]);
if ($operations) {
$opdiscover = DB::select('lld_override_opdiscover', [
'output' => ['lld_override_operationid', 'discover'],
'filter' => ['lld_override_operationid' => array_keys($operations)]
]);
$item_prototype_objectids = [];
$trigger_prototype_objectids = [];
$host_prototype_objectids = [];
foreach ($operations as $operation) {
switch ($operation['operationobject']) {
case OPERATION_OBJECT_ITEM_PROTOTYPE:
$item_prototype_objectids[$operation['lld_override_operationid']] = true;
break;
case OPERATION_OBJECT_TRIGGER_PROTOTYPE:
$trigger_prototype_objectids[$operation['lld_override_operationid']] = true;
break;
case OPERATION_OBJECT_HOST_PROTOTYPE:
$host_prototype_objectids[$operation['lld_override_operationid']] = true;
break;
}
}
if ($item_prototype_objectids || $trigger_prototype_objectids || $host_prototype_objectids) {
$opstatus = DB::select('lld_override_opstatus', [
'output' => ['lld_override_operationid', 'status'],
'filter' => ['lld_override_operationid' => array_keys(
$item_prototype_objectids + $trigger_prototype_objectids + $host_prototype_objectids
)]
]);
}
if ($item_prototype_objectids) {
$ophistory = DB::select('lld_override_ophistory', [
'output' => ['lld_override_operationid', 'history'],
'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)]
]);
$optrends = DB::select('lld_override_optrends', [
'output' => ['lld_override_operationid', 'trends'],
'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)]
]);
$opperiod = DB::select('lld_override_opperiod', [
'output' => ['lld_override_operationid', 'delay'],
'filter' => ['lld_override_operationid' => array_keys($item_prototype_objectids)]
]);
}
if ($trigger_prototype_objectids) {
$opseverity = DB::select('lld_override_opseverity', [
'output' => ['lld_override_operationid', 'severity'],
'filter' => ['lld_override_operationid' => array_keys($trigger_prototype_objectids)]
]);
}
if ($trigger_prototype_objectids || $host_prototype_objectids || $item_prototype_objectids) {
$optag = DB::select('lld_override_optag', [
'output' => ['lld_override_operationid', 'tag', 'value'],
'filter' => ['lld_override_operationid' => array_keys(
$trigger_prototype_objectids + $host_prototype_objectids + $item_prototype_objectids
)]
]);
}
if ($host_prototype_objectids) {
$optemplate = DB::select('lld_override_optemplate', [
'output' => ['lld_override_operationid', 'templateid'],
'filter' => ['lld_override_operationid' => array_keys($host_prototype_objectids)]
]);
$opinventory = DB::select('lld_override_opinventory', [
'output' => ['lld_override_operationid', 'inventory_mode'],
'filter' => ['lld_override_operationid' => array_keys($host_prototype_objectids)]
]);
}
foreach ($operations as &$operation) {
$lld_override_operationid = $operation['lld_override_operationid'];
if ($item_prototype_objectids || $trigger_prototype_objectids || $host_prototype_objectids) {
foreach ($opstatus as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['opstatus']['status'] = $row['status'];
}
}
}
foreach ($opdiscover as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['opdiscover']['discover'] = $row['discover'];
}
}
if ($item_prototype_objectids) {
foreach ($ophistory as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['ophistory']['history'] = $row['history'];
}
}
foreach ($optrends as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['optrends']['trends'] = $row['trends'];
}
}
foreach ($opperiod as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['opperiod']['delay'] = $row['delay'];
}
}
}
if ($trigger_prototype_objectids) {
foreach ($opseverity as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['opseverity']['severity'] = $row['severity'];
}
}
}
if ($trigger_prototype_objectids || $host_prototype_objectids || $item_prototype_objectids) {
foreach ($optag as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['optag'][] = ['tag' => $row['tag'], 'value' => $row['value']];
}
}
}
if ($host_prototype_objectids) {
foreach ($optemplate as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['optemplate'][] = ['templateid' => $row['templateid']];
}
}
foreach ($opinventory as $row) {
if (bccomp($lld_override_operationid, $row['lld_override_operationid']) == 0) {
$operation['opinventory']['inventory_mode'] = $row['inventory_mode'];
}
}
}
}
unset($operation);
}
$relation_map = $this->createRelationMap($operations, 'lld_overrideid', 'lld_override_operationid');
$overrides = $relation_map->mapMany($overrides, $operations, 'operations');
}
foreach ($result as &$row) {
$row['overrides'] = [];
foreach ($overrides as $override) {
if (bccomp($override['itemid'], $row['itemid']) == 0) {
unset($override['itemid'], $override['lld_overrideid']);
if ($operations_requested) {
foreach ($override['operations'] as &$operation) {
unset($operation['lld_override_operationid'], $operation['lld_overrideid']);
}
unset($operation);
}
$row['overrides'][] = $override;
}
}
}
unset($row);
}
return $result;
}
/**
* @param array $items
*
* @return array
*/
public function create(array $items): array {
self::validateCreate($items);
self::createForce($items);
self::inherit($items);
return ['itemids' => array_column($items, 'itemid')];
}
/**
* @param array $items
*
* @throws APIException
*/
private static function validateCreate(array &$items): void {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE | API_ALLOW_UNEXPECTED, 'fields' => [
'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED]
]];
if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
self::checkHostsAndTemplates($items, $db_hosts, $db_templates);
self::addHostStatus($items, $db_hosts, $db_templates);
self::addFlags($items, ZBX_FLAG_DISCOVERY_RULE);
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_ALLOW_UNEXPECTED, 'uniq' => [['uuid'], ['hostid', 'key_']], 'fields' => [
'host_status' => ['type' => API_ANY],
'flags' => ['type' => API_ANY],
'uuid' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'host_status', 'in' => implode(',', [HOST_STATUS_TEMPLATE])], 'type' => API_UUID],
['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'uuid'), 'unset' => true]
]],
'hostid' => ['type' => API_ANY],
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('items', 'name')],
'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', self::SUPPORTED_ITEM_TYPES)],
'key_' => ['type' => API_ITEM_KEY, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('items', 'key_')],
'lifetime' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'lifetime')],
'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')],
'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])],
'preprocessing' => self::getPreprocessingValidationRules(),
'lld_macro_paths' => self::getLldMacroPathsValidationRules(),
'filter' => self::getFilterValidationRules('items', 'item_condition'),
'overrides' => self::getOverridesValidationRules()
]];
if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
self::validateByType(array_keys($api_input_rules['fields']), $items);
self::addUuid($items);
self::checkUuidDuplicates($items);
self::checkDuplicates($items);
self::checkHostInterfaces($items);
self::checkDependentItems($items);
self::checkFilterFormula($items);
self::checkOverridesFilterFormula($items);
self::checkOverridesOperationTemplates($items);
}
/**
* @param array $items
*/
private static function createForce(array &$items): void {
self::addValueType($items);
$itemids = DB::insert('items', $items);
$ins_items_rtdata = [];
$host_statuses = [];
foreach ($items as &$item) {
$item['itemid'] = array_shift($itemids);
if (in_array($item['host_status'], [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED])) {
$ins_items_rtdata[] = ['itemid' => $item['itemid']];
}
$host_statuses[] = $item['host_status'];
unset($item['host_status'], $item['flags'], $item['value_type']);
}
unset($item);
if ($ins_items_rtdata) {
DB::insertBatch('item_rtdata', $ins_items_rtdata, false);
}
self::updateParameters($items);
self::updatePreprocessing($items);
self::updateLldMacroPaths($items);
self::updateItemFilters($items);
self::updateOverrides($items);
self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_LLD_RULE, $items);
foreach ($items as &$item) {
$item['host_status'] = array_shift($host_statuses);
$item['flags'] = ZBX_FLAG_DISCOVERY_RULE;
}
unset($item);
}
/**
* Add value_type property to given items.
*
* @param array $items
*/
private static function addValueType(array &$items): void {
foreach ($items as &$item) {
$item['value_type'] = ITEM_VALUE_TYPE_TEXT;
}
unset($item);
}
/**
* @param array $items
*
* @return array
*/
public function update(array $items): array {
$this->validateUpdate($items, $db_items);
$itemids = array_column($items, 'itemid');
self::updateForce($items, $db_items);
self::inherit($items, $db_items);
return ['itemids' => $itemids];
}
/**
* @param array $items
* @param array|null $db_items
*
* @throws APIException
*/
protected function validateUpdate(array &$items, ?array &$db_items): void {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE | API_ALLOW_UNEXPECTED, 'uniq' => [['itemid']], 'fields' => [
'itemid' => ['type' => API_ID, 'flags' => API_REQUIRED]
]];
if (!CApiInputValidator::validate($api_input_rules, $items, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$count = $this->get([
'countOutput' => true,
'itemids' => array_column($items, 'itemid'),
'editable' => true
]);
if ($count != count($items)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
/*
* The fields "headers" and "query_fields" in API are arrays, but it is necessary to get the values of these
* fields as stored in database.
*/
$db_items = DB::select('items', [
'output' => array_merge(['uuid', 'itemid', 'name', 'type', 'key_', 'lifetime', 'description', 'status'],
array_diff(CItemType::FIELD_NAMES, ['parameters'])
),
'itemids' => array_column($items, 'itemid'),
'preservekeys' => true
]);
self::addInternalFields($db_items);
foreach ($items as $i => &$item) {
$db_item = $db_items[$item['itemid']];
$item['host_status'] = $db_item['host_status'];
$api_input_rules = $db_item['templateid'] == 0
? self::getValidationRules()
: self::getInheritedValidationRules();
if (!CApiInputValidator::validate($api_input_rules, $item, '/'.($i + 1), $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
}
unset($item);
$items = $this->extendObjectsByKey($items, $db_items, 'itemid', ['type', 'key_']);
self::validateByType(array_keys($api_input_rules['fields']), $items, $db_items);
$items = $this->extendObjectsByKey($items, $db_items, 'itemid', ['hostid', 'flags']);
self::validateUniqueness($items);
self::addAffectedObjects($items, $db_items);
self::checkUuidDuplicates($items, $db_items);
self::checkDuplicates($items, $db_items);
self::checkHostInterfaces($items, $db_items);
self::checkDependentItems($items, $db_items);
self::checkFilterFormula($items);
self::checkOverridesFilterFormula($items);
self::checkOverridesOperationTemplates($items);
}
/**
* @return array
*/
private static function getValidationRules(): array {
return ['type' => API_OBJECT, 'flags' => API_ALLOW_UNEXPECTED, 'fields' => [
'host_status' => ['type' => API_ANY],
'uuid' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'host_status', 'in' => HOST_STATUS_TEMPLATE], 'type' => API_UUID],
['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault('items', 'uuid'), 'unset' => true]
]],
'itemid' => ['type' => API_ANY],
'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('items', 'name')],
'type' => ['type' => API_INT32, 'in' => implode(',', self::SUPPORTED_ITEM_TYPES)],
'key_' => ['type' => API_ITEM_KEY, 'length' => DB::getFieldLength('items', 'key_')],
'lifetime' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'lifetime')],
'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')],
'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])],
'preprocessing' => self::getPreprocessingValidationRules(),
'lld_macro_paths' => self::getLldMacroPathsValidationRules(),
'filter' => self::getFilterValidationRules('items', 'item_condition'),
'overrides' => self::getOverridesValidationRules()
]];
}
/**
* @return array
*/
private static function getInheritedValidationRules(): array {
return ['type' => API_OBJECT, 'flags' => API_ALLOW_UNEXPECTED, 'fields' => [
'host_status' => ['type' => API_ANY],
'uuid' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'itemid' => ['type' => API_ANY],
'name' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'type' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'key_' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'lifetime' => ['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('items', 'lifetime')],
'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('items', 'description')],
'status' => ['type' => API_INT32, 'in' => implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED])],
'preprocessing' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'lld_macro_paths' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED],
'filter' => self::getFilterValidationRules('items', 'item_condition'),
'overrides' => ['type' => API_UNEXPECTED, 'error_type' => API_ERR_INHERITED]
]];
}
/**
* @return array
*/
private static function getLldMacroPathsValidationRules(): array {
return ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['lld_macro']], 'fields' => [
'lld_macro' => ['type' => API_LLD_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('lld_macro_path', 'lld_macro')],
'path' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_macro_path', 'path')]
]];
}
/**
* @param string $base_table
* @param string $condition_table
*
* @return array
*/
private static function getFilterValidationRules(string $base_table, string $condition_table): array {
$condition_fields = [
'macro' => ['type' => API_LLD_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength($condition_table, 'macro')],
'operator' => ['type' => API_INT32, 'in' => implode(',', [CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP, CONDITION_OPERATOR_EXISTS, CONDITION_OPERATOR_NOT_EXISTS]), 'default' => CONDITION_OPERATOR_REGEXP],
'value' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operator', 'in' => implode(',', [CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP])], 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength($condition_table, 'value')],
['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault($condition_table, 'value')]
]]
];
return ['type' => API_OBJECT, 'fields' => [
'evaltype' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [CONDITION_EVAL_TYPE_AND_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_EXPRESSION])],
'formula' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'evaltype', 'in' => CONDITION_EVAL_TYPE_EXPRESSION], 'type' => API_COND_FORMULA, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength($base_table, 'formula')],
['else' => true, 'type' => API_STRING_UTF8, 'in' => DB::getDefault($base_table, 'formula')]
]],
'conditions' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED | API_NORMALIZE, 'rules' => [
['if' => ['field' => 'evaltype', 'in' => CONDITION_EVAL_TYPE_EXPRESSION], 'type' => API_OBJECTS, 'uniq' => [['formulaid']], 'fields' => [
'formulaid' => ['type' => API_COND_FORMULAID, 'flags' => API_REQUIRED]
] + $condition_fields],
['else' => true, 'type' => API_OBJECTS, 'fields' => [
'formulaid' => ['type' => API_STRING_UTF8, 'in' => '', 'unset' => true]
] + $condition_fields]
]]
]];
}
/**
* @return array
*/
private static function getOverridesValidationRules(): array {
return ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['name'], ['step']], 'fields' => [
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override', 'name')],
'step' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(':', [1, ZBX_MAX_INT32])],
'stop' => ['type' => API_INT32, 'in' => implode(',', [ZBX_LLD_OVERRIDE_STOP_NO, ZBX_LLD_OVERRIDE_STOP_YES])],
'filter' => self::getFilterValidationRules('lld_override', 'lld_override_condition'),
'operations' => self::getOperationsValidationRules()
]];
}
/**
* @return array
*/
private static function getOperationsValidationRules(): array {
return ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [
'operationobject' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE, OPERATION_OBJECT_TRIGGER_PROTOTYPE, OPERATION_OBJECT_GRAPH_PROTOTYPE, OPERATION_OBJECT_HOST_PROTOTYPE])],
'operator' => ['type' => API_INT32, 'in' => implode(',', [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, CONDITION_OPERATOR_NOT_LIKE, CONDITION_OPERATOR_REGEXP, CONDITION_OPERATOR_NOT_REGEXP])],
'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('lld_override_operation', 'value')],
'opdiscover' => ['type' => API_OBJECT, 'fields' => [
'discover' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PROTOTYPE_DISCOVER, ZBX_PROTOTYPE_NO_DISCOVER])]
]],
'opstatus' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE, OPERATION_OBJECT_TRIGGER_PROTOTYPE, OPERATION_OBJECT_HOST_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PROTOTYPE_STATUS_ENABLED, ZBX_PROTOTYPE_STATUS_DISABLED])]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]],
'opperiod' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'delay' => ['type' => API_ITEM_DELAY, 'flags' => API_REQUIRED | API_ALLOW_USER_MACRO, 'length' => DB::getFieldLength('lld_override_opperiod', 'delay')]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]],
'ophistory' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'history' => ['type' => API_TIME_UNIT, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '0,'.implode(':', [SEC_PER_HOUR, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('lld_override_ophistory', 'history')]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]],
'optrends' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'trends' => ['type' => API_TIME_UNIT, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '0,'.implode(':', [SEC_PER_DAY, 25 * SEC_PER_YEAR]), 'length' => DB::getFieldLength('lld_override_optrends', 'trends')]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]],
'opseverity' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_TRIGGER_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'severity' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_INFORMATION, TRIGGER_SEVERITY_WARNING, TRIGGER_SEVERITY_AVERAGE, TRIGGER_SEVERITY_HIGH, TRIGGER_SEVERITY_DISASTER])]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]],
'optag' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_ITEM_PROTOTYPE, OPERATION_OBJECT_TRIGGER_PROTOTYPE, OPERATION_OBJECT_HOST_PROTOTYPE])], 'type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['tag', 'value']], 'fields' => [
'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('lld_override_optag', 'tag')],
'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('lld_override_optag', 'value'), 'default' => DB::getDefault('lld_override_optag', 'value')]
]],
['else' => true, 'type' => API_OBJECTS, 'length' => 0]
]],
'optemplate' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_HOST_PROTOTYPE])], 'type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [
'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED]
]],
['else' => true, 'type' => API_OBJECTS, 'length' => 0]
]],
'opinventory' => ['type' => API_MULTIPLE, 'rules' => [
['if' => ['field' => 'operationobject', 'in' => implode(',', [OPERATION_OBJECT_HOST_PROTOTYPE])], 'type' => API_OBJECT, 'fields' => [
'inventory_mode' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC])]
]],
['else' => true, 'type' => API_OBJECT, 'fields' => []]
]]
]];
}
/**
* @inheritDoc
*/
protected static function addAffectedObjects(array $items, array &$db_items): void {
self::addAffectedPreprocessing($items, $db_items);
self::addAffectedLldMacroPaths($items, $db_items);
self::addAffectedItemFilters($items, $db_items);
self::addAffectedOverrides($items, $db_items);
self::addAffectedParameters($items, $db_items);
}
/**
* @param array $items
* @param array $db_items
*/
private static function addAffectedLldMacroPaths(array $items, array &$db_items): void {
$itemids = [];
foreach ($items as $item) {
if (array_key_exists('lld_macro_paths', $item)) {
$itemids[] = $item['itemid'];
$db_items[$item['itemid']]['lld_macro_paths'] = [];
}
}
if (!$itemids) {
return;
}
$options = [
'output' => ['lld_macro_pathid', 'itemid', 'lld_macro', 'path'],
'filter' => ['itemid' => $itemids]
];
$db_lld_macro_paths = DBselect(DB::makeSql('lld_macro_path', $options));
while ($db_lld_macro_path = DBfetch($db_lld_macro_paths)) {
$db_items[$db_lld_macro_path['itemid']]['lld_macro_paths'][$db_lld_macro_path['lld_macro_pathid']] =
array_diff_key($db_lld_macro_path, array_flip(['itemid']));
}
}
/**
* @param array $items
* @param array $db_items
*/
private static function addAffectedItemFilters(array $items, array &$db_items): void {
$_db_items = [];
foreach ($items as $item) {
if (array_key_exists('filter', $item)) {
$_db_items[$item['itemid']] = &$db_items[$item['itemid']];
}
}
if (!$_db_items) {
return;
}
self::addAffectedFilters($_db_items, 'items', 'item_condition');
}
/**
* @param array $db_objects
* @param string $base_table
* @param string $condition_table
*/
private static function addAffectedFilters(array &$db_objects, string $base_table, string $condition_table): void {
$base_pk = DB::getPk($base_table);
$condition_pk = DB::getPk($condition_table);
foreach ($db_objects as &$db_object) {
$db_object['filter'] = [];
}
unset($db_object);
$options = [
'output' => [$base_pk, 'evaltype', 'formula'],
'filter' => [$base_pk => array_keys($db_objects)]
];
$db_filters = DBselect(DB::makeSql($base_table, $options));
$object_formulaids = [];
while ($db_filter = DBfetch($db_filters)) {
if ($db_filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$object_formulaids[$db_filter[$base_pk]] = CConditionHelper::getFormulaIds($db_filter['formula']);
$db_filter['formula'] = CConditionHelper::replaceNumericIds($db_filter['formula'],
$object_formulaids[$db_filter[$base_pk]]
);
}
$db_objects[$db_filter[$base_pk]]['filter'] =
array_diff_key($db_filter, array_flip([$base_pk])) + ['conditions' => []];
}
$options = [
'output' => [$condition_pk, $base_pk, 'operator', 'macro', 'value'],
'filter' => [$base_pk => array_keys($db_objects)]
];
$db_conditions = DBselect(DB::makeSql($condition_table, $options));
while ($db_condition = DBfetch($db_conditions)) {
if ($db_objects[$db_condition[$base_pk]]['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$db_condition['formulaid'] = $object_formulaids[$db_condition[$base_pk]][$db_condition[$condition_pk]];
}
else {
$db_condition['formulaid'] = '';
}
$db_objects[$db_condition[$base_pk]]['filter']['conditions'][$db_condition[$condition_pk]] =
array_diff_key($db_condition, array_flip([$base_pk]));
}
}
/**
* @param array $items
* @param array $db_items
*/
private static function addAffectedOverrides(array $items, array &$db_items): void {
$itemids = [];
foreach ($items as $item) {
if (array_key_exists('overrides', $item)) {
$itemids[] = $item['itemid'];
$db_items[$item['itemid']]['overrides'] = [];
}
}
if (!$itemids) {
return;
}
$options = [
'output' => ['lld_overrideid', 'itemid', 'name', 'step', 'stop'],
'filter' => ['itemid' => $itemids]
];
$result = DBselect(DB::makeSql('lld_override', $options));
$db_overrides = [];
while ($db_override = DBfetch($result)) {
$db_items[$db_override['itemid']]['overrides'][$db_override['lld_overrideid']] =
array_diff_key($db_override, array_flip(['itemid']));
$db_overrides[$db_override['lld_overrideid']] = &$db_items[$db_override['itemid']]['overrides'][$db_override['lld_overrideid']];
}
if (!$db_overrides) {
return;
}
self::addAffectedOverrideFilters($db_overrides);
self::addAffectedOverrideOperations($db_overrides);
}
/**
* @param array $db_overrides
*/
private static function addAffectedOverrideFilters(array &$db_overrides): void {
self::addAffectedFilters($db_overrides, 'lld_override', 'lld_override_condition');
}
/**
* @param array $db_overrides
*/
private static function addAffectedOverrideOperations(array &$db_overrides): void {
foreach ($db_overrides as &$db_override) {
$db_override['operations'] = [];
}
unset($db_override);
$options = [
'output' => ['lld_override_operationid', 'lld_overrideid', 'operationobject', 'operator', 'value'],
'filter' => ['lld_overrideid' => array_keys($db_overrides)]
];
$result = DBselect(DB::makeSql('lld_override_operation', $options));
$db_operations = [];
while ($db_operation = DBfetch($result)) {
$db_overrides[$db_operation['lld_overrideid']]['operations'][$db_operation['lld_override_operationid']] =
array_diff_key($db_operation, array_flip(['lld_overrideid']));
$db_operations[$db_operation['lld_override_operationid']] =
&$db_overrides[$db_operation['lld_overrideid']]['operations'][$db_operation['lld_override_operationid']];
}
if (!$db_operations) {
return;
}
self::addAffectedOverrideOperationSingleObjectFields($db_operations);
self::addAffectedOverrideOperationTags($db_operations);
self::addAffectedOverrideOperationTemplates($db_operations);
}
/**
* @param array $db_operations
*/
private static function addAffectedOverrideOperationSingleObjectFields(array &$db_operations): void {
$db_op_fields = DBselect(
'SELECT op.lld_override_operationid,d.discover AS d_discover,s.status AS s_status,p.delay AS p_delay,'.
'h.history AS h_history,t.trends AS t_trends,ss.severity AS ss_severity,'.
'i.inventory_mode AS i_inventory_mode'.
' FROM lld_override_operation op'.
' LEFT JOIN lld_override_opdiscover d ON op.lld_override_operationid=d.lld_override_operationid'.
' LEFT JOIN lld_override_opstatus s ON op.lld_override_operationid=s.lld_override_operationid'.
' LEFT JOIN lld_override_opperiod p ON op.lld_override_operationid=p.lld_override_operationid'.
' LEFT JOIN lld_override_ophistory h ON op.lld_override_operationid=h.lld_override_operationid'.
' LEFT JOIN lld_override_optrends t ON op.lld_override_operationid=t.lld_override_operationid'.
' LEFT JOIN lld_override_opseverity ss ON op.lld_override_operationid=ss.lld_override_operationid'.
' LEFT JOIN lld_override_opinventory i ON op.lld_override_operationid=i.lld_override_operationid'.
' WHERE '.dbConditionId('op.lld_override_operationid', array_keys($db_operations))
);
$single_op_fields = [
'd' => 'opdiscover',
's' => 'opstatus',
'p' => 'opperiod',
'h' => 'ophistory',
't' => 'optrends',
'ss' => 'opseverity',
'i' => 'opinventory'
];
while ($db_op_field = DBfetch($db_op_fields, false)) {
foreach ($single_op_fields as $alias => $opfield) {
$fields = [];
$has_filled_fields = false;
foreach ($db_op_field as $aliased_name => $value) {
if (strncmp($aliased_name, $alias.'_', strlen($alias) + 1) != 0) {
continue;
}
$fields[substr($aliased_name, strlen($alias) + 1)] = $value;
if ($value !== null) {
$has_filled_fields = true;
}
}
$db_operations[$db_op_field['lld_override_operationid']][$opfield] = $has_filled_fields ? $fields : [];
}
}
}
/**
* @param array $db_operations
*/
private static function addAffectedOverrideOperationTags(array &$db_operations): void {
$tag_operationobjects = [
OPERATION_OBJECT_ITEM_PROTOTYPE, OPERATION_OBJECT_TRIGGER_PROTOTYPE, OPERATION_OBJECT_HOST_PROTOTYPE
];
$operationids = [];
foreach ($db_operations as &$db_operation) {
$db_operation['optag'] = [];
if (in_array($db_operation['operationobject'], $tag_operationobjects)) {
$operationids[] = $db_operation['lld_override_operationid'];
}
}
unset($db_operation);
if (!$operationids) {
return;
}
$options = [
'output' => ['lld_override_optagid', 'lld_override_operationid', 'tag', 'value'],
'filter' => ['lld_override_operationid' => $operationids]
];
$db_tags = DBselect(DB::makeSql('lld_override_optag', $options));
while ($db_tag = DBfetch($db_tags)) {
$db_operations[$db_tag['lld_override_operationid']]['optag'][$db_tag['lld_override_optagid']] =
array_diff_key($db_tag, array_flip(['lld_override_operationid']));
}
}
/**
* @param array $db_operations
*/
private static function addAffectedOverrideOperationTemplates(array &$db_operations): void {
$operationids = [];
foreach ($db_operations as &$db_operation) {
$db_operation['optemplate'] = [];
if ($db_operation['operationobject'] == OPERATION_OBJECT_HOST_PROTOTYPE) {
$operationids[] = $db_operation['lld_override_operationid'];
}
}
unset($db_operation);
if (!$operationids) {
return;
}
$options = [
'output' => ['lld_override_optemplateid', 'lld_override_operationid', 'templateid'],
'filter' => ['lld_override_operationid' => $operationids]
];
$db_templates = DBselect(DB::makeSql('lld_override_optemplate', $options));
while ($db_template = DBfetch($db_templates)) {
$db_operations[$db_template['lld_override_operationid']]['optemplate'][$db_template['lld_override_optemplateid']] =
array_diff_key($db_template, array_flip(['lld_override_operationid']));
}
}
/**
* Check that all constants of formula are specified in the filter conditions of the given LLD rules or overrides.
*
* @param array $objects
* @param string $path
*
* @throws APIException
*/
private static function checkFilterFormula(array $objects, string $path = '/'): void {
$condition_formula_parser = new CConditionFormula();
foreach ($objects as $i => $object) {
if (!array_key_exists('filter', $object)
|| $object['filter']['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
continue;
}
$condition_formula_parser->parse($object['filter']['formula']);
$constants = array_unique(array_column($condition_formula_parser->constants, 'value'));
$subpath = ($path === '/' ? $path : $path.'/').($i + 1).'/filter';
$condition_formulaids = array_column($object['filter']['conditions'], 'formulaid');
foreach ($constants as $constant) {
if (!in_array($constant, $condition_formulaids)) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', $subpath.'/formula',
_s('missing filter condition "%1$s"', $constant)
));
}
}
foreach ($object['filter']['conditions'] as $j => $condition) {
if (!in_array($condition['formulaid'], $constants)) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
$subpath.'/conditions/'.($j + 1).'/formulaid', _('an identifier is not defined in the formula')
));
}
}
}
}
/**
* Check that specific checks for overrides of given LLD rules are valid.
*
* @param array $items
*/
private static function checkOverridesFilterFormula(array $items): void {
foreach ($items as $i => $item) {
if (!array_key_exists('overrides', $item)) {
continue;
}
$path = '/'.($i + 1).'/overrides';
self::checkFilterFormula($item['overrides'], $path);
}
}
/**
* Check that templates specified in override operations of the given LLD rules are valid.
*
* @param array $items
*
* @throws APIException
*/
private static function checkOverridesOperationTemplates(array $items): void {
$template_indexes = [];
foreach ($items as $i1 => $item) {
if (!array_key_exists('overrides', $item)) {
continue;
}
foreach ($item['overrides'] as $i2 => $override) {
if (!array_key_exists('operations', $override)) {
continue;
}
foreach ($override['operations'] as $i3 => $operation) {
if (!array_key_exists('optemplate', $operation)) {
continue;
}
foreach ($operation['optemplate'] as $i4 => $template) {
$template_indexes[$template['templateid']][$i1][$i2][$i3] = $i4;
}
}
}
}
if (!$template_indexes) {
return;
}
$db_templates = API::Template()->get([
'output' => ['templateid'],
'templateids' => array_keys($template_indexes),
'preservekeys' => true
]);
$template_indexes = array_diff_key($template_indexes, $db_templates);
$index = reset($template_indexes);
if ($index === false) {
return;
}
$i1 = key($index);
$i2 = key($index[$i1]);
$i3 = key($index[$i1][$i2]);
$i4 = $index[$i1][$i2][$i3];
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
'/'.($i1 + 1).'/overrides/'.($i2 + 1).'/operations/'.($i3 + 1).'/optemplate/'.($i4 + 1).'/templateid',
_('a template ID is expected')
));
}
/**
* @param array $items
* @param array $db_items
*/
private static function updateForce(array &$items, array &$db_items): void {
// Helps to avoid deadlocks.
CArrayHelper::sort($items, ['itemid']);
self::addFieldDefaultsByType($items, $db_items);
$upd_items = [];
$upd_itemids = [];
$internal_fields = array_flip(['itemid', 'type', 'key_', 'hostid', 'flags', 'host_status']);
$nested_object_fields = array_flip(['preprocessing', 'lld_macro_paths', 'filter', 'overrides', 'parameters']);
foreach ($items as $i => &$item) {
$upd_item = DB::getUpdatedValues('items', $item, $db_items[$item['itemid']]);
if ($upd_item) {
$upd_items[] = [
'values' => $upd_item,
'where' => ['itemid' => $item['itemid']]
];
if (array_key_exists('type', $item) && $item['type'] == ITEM_TYPE_HTTPAGENT) {
$item = array_intersect_key($item,
array_flip(['authtype']) + $internal_fields + $upd_item + $nested_object_fields
);
}
else {
$item = array_intersect_key($item, $internal_fields + $upd_item + $nested_object_fields);
}
$upd_itemids[$i] = $item['itemid'];
}
else {
$item = array_intersect_key($item, $internal_fields + $nested_object_fields);
}
}
unset($item);
if ($upd_items) {
DB::update('items', $upd_items);
}
self::updateParameters($items, $db_items, $upd_itemids);
self::updatePreprocessing($items, $db_items, $upd_itemids);
self::updateLldMacroPaths($items, $db_items, $upd_itemids);
self::updateItemFilters($items, $db_items, $upd_itemids);
self::updateOverrides($items, $db_items, $upd_itemids);
$items = array_intersect_key($items, $upd_itemids);
$db_items = array_intersect_key($db_items, array_flip($upd_itemids));
self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_LLD_RULE, $items, $db_items);
}
/**
* @param array $items
* @param array|null $db_items
* @param array|null $upd_itemids
*/
private static function updateLldMacroPaths(array &$items, array &$db_items = null,
array &$upd_itemids = null): void {
$ins_lld_macro_paths = [];
$upd_lld_macro_paths = [];
$del_lld_macro_pathids = [];
foreach ($items as $i => &$item) {
if (!array_key_exists('lld_macro_paths', $item)) {
continue;
}
$changed = false;
$db_lld_macro_paths = $db_items !== null
? array_column($db_items[$item['itemid']]['lld_macro_paths'], null, 'lld_macro')
: [];
foreach ($item['lld_macro_paths'] as &$lld_macro_path) {
if (array_key_exists($lld_macro_path['lld_macro'], $db_lld_macro_paths)) {
$db_lld_macro_path = $db_lld_macro_paths[$lld_macro_path['lld_macro']];
$lld_macro_path['lld_macro_pathid'] = $db_lld_macro_path['lld_macro_pathid'];
unset($db_lld_macro_paths[$lld_macro_path['lld_macro']]);
$upd_lld_macro_path = DB::getUpdatedValues('lld_macro_path', $lld_macro_path, $db_lld_macro_path);
if ($upd_lld_macro_path) {
$upd_lld_macro_paths[] = [
'values' => $upd_lld_macro_path,
'where' => ['lld_macro_pathid' => $db_lld_macro_path['lld_macro_pathid']]
];
$changed = true;
}
}
else {
$ins_lld_macro_paths[] = ['itemid' => $item['itemid']] + $lld_macro_path;
$changed = true;
}
}
unset($lld_macro_path);
if ($db_lld_macro_paths) {
$del_lld_macro_pathids =
array_merge($del_lld_macro_pathids, array_column($db_lld_macro_paths, 'lld_macro_pathid'));
$changed = true;
}
if ($db_items !== null) {
if ($changed) {
$upd_itemids[$i] = $item['itemid'];
}
else {
unset($item['lld_macro_paths'], $db_items[$item['itemid']]['lld_macro_paths']);
}
}
}
unset($item);
if ($del_lld_macro_pathids) {
DB::delete('lld_macro_path', ['lld_macro_pathid' => $del_lld_macro_pathids]);
}
if ($upd_lld_macro_paths) {
DB::update('lld_macro_path', $upd_lld_macro_paths);
}
if ($ins_lld_macro_paths) {
$lld_macro_pathids = DB::insert('lld_macro_path', $ins_lld_macro_paths);
}
foreach ($items as &$item) {
if (!array_key_exists('lld_macro_paths', $item)) {
continue;
}
foreach ($item['lld_macro_paths'] as &$lld_macro_path) {
if (!array_key_exists('lld_macro_pathid', $lld_macro_path)) {
$lld_macro_path['lld_macro_pathid'] = array_shift($lld_macro_pathids);
}
}
unset($lld_macro_path);
}
unset($item);
}
/**
* @param array $items
* @param array|null $db_items
* @param array|null $upd_itemids
*/
private static function updateItemFilters(array &$items, array &$db_items = null, array &$upd_itemids = null): void {
self::updateFilters($items, $db_items, $upd_itemids, 'items', 'item_condition');
}
/**
* @param array $objects
* @param array|null $db_objects
* @param array|null $upd_objectids
* @param string $base_table
* @param string $condition_table
*/
private static function updateFilters(array &$objects, ?array &$db_objects, ?array &$upd_objectids,
string $base_table, string $condition_table): void {
$base_pk = DB::getPk($base_table);
$condition_pk = DB::getPk($condition_table);
$_upd_objectids = $db_objects !== null ? [] : null;
self::updateFilterConditions($objects, $db_objects, $_upd_objectids, $base_table, $condition_table);
$upd_objects = [];
foreach ($objects as $i => &$object) {
if (!array_key_exists('filter', $object)) {
continue;
}
$upd_object = [];
$changed = false;
if ($db_objects === null
|| $object['filter']['evaltype'] != $db_objects[$object[$base_pk]]['filter']['evaltype']) {
$upd_object['evaltype'] = $object['filter']['evaltype'];
}
if ($object['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
if ($db_objects === null
|| $object['filter']['formula'] != $db_objects[$object[$base_pk]]['filter']['formula']
|| array_key_exists($i, $_upd_objectids)) {
$upd_object['formula'] = CConditionHelper::replaceLetterIds($object['filter']['formula'],
array_column($object['filter']['conditions'], $condition_pk, 'formulaid')
);
}
}
elseif ($db_objects !== null
&& $db_objects[$object[$base_pk]]['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$upd_object['formula'] = DB::getDefault($base_table, 'formula');
$object['filter']['formula'] = DB::getDefault($base_table, 'formula');
}
if ($upd_object) {
$upd_objects[] = [
'values' => $upd_object,
'where' => [$base_pk => $object[$base_pk]]
];
$changed = true;
}
if ($db_objects !== null) {
if ($changed || array_key_exists($i, $_upd_objectids)) {
$upd_objectids[$i] = $object[$base_pk];
}
else {
unset($object['filter'], $db_objects[$object[$base_pk]]['filter']);
}
}
}
unset($object);
if ($upd_objects) {
DB::update($base_table, $upd_objects);
}
}
/**
* @param array $objects
* @param array|null $db_objects
* @param array|null $upd_objectids
* @param string $base_table
* @param string $condition_table
*/
private static function updateFilterConditions(array &$objects, array &$db_objects = null,
array &$upd_objectids = null, string $base_table, string $condition_table): void {
$base_pk = DB::getPk($base_table);
$condition_pk = DB::getPk($condition_table);
$ins_conditions = [];
$del_conditionids = [];
foreach ($objects as $i => &$object) {
if (!array_key_exists('filter', $object) || !array_key_exists('conditions', $object['filter'])) {
continue;
}
if ($object['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$condition_indexes = [];
foreach ($object['filter']['conditions'] as $j => $condition) {
$condition_indexes[$condition['formulaid']] = $j;
}
$formula = CConditionHelper::replaceLetterIds($object['filter']['formula'], $condition_indexes);
$formulaids = CConditionHelper::getFormulaIds($formula);
$object['filter']['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids);
}
$changed = false;
$db_conditions = $db_objects !== null ? $db_objects[$object[$base_pk]]['filter']['conditions'] : [];
foreach ($object['filter']['conditions'] as $j => &$condition) {
if ($object['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$condition['formulaid'] = $formulaids[$j];
}
elseif ($db_objects !== null
&& $db_objects[$object[$base_pk]]['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) {
$condition['formulaid'] = '';
}
$db_conditionid = self::getConditionId($condition_table, $condition, $db_conditions);
if ($db_conditionid !== null) {
$condition[$condition_pk] = $db_conditionid;
if (array_key_exists('formulaid', $condition)
&& $condition['formulaid'] != $db_conditions[$db_conditionid]['formulaid']) {
$changed = true;
}
unset($db_conditions[$db_conditionid]);
} else {
$ins_conditions[] = [$base_pk => $object[$base_pk]] + $condition;
$changed = true;
}
}
unset($condition);
if ($db_conditions) {
$del_conditionids =
array_merge($del_conditionids, array_column($db_conditions, $condition_pk));
$changed = true;
}
if ($db_objects !== null) {
if ($changed) {
$upd_objectids[$i] = $object[$base_pk];
}
elseif ($object['filter']['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
unset($object['filter']['conditions'], $db_objects[$object[$base_pk]]['filter']['conditions']);
}
}
}
unset($object);
if ($del_conditionids) {
DB::delete($condition_table, [$condition_pk => $del_conditionids]);
}
if ($ins_conditions) {
$conditionids = DB::insert($condition_table, $ins_conditions);
}
foreach ($objects as &$object) {
if (!array_key_exists('filter', $object) || !array_key_exists('conditions', $object['filter'])) {
continue;
}
foreach ($object['filter']['conditions'] as &$condition) {
if (!array_key_exists($condition_pk, $condition)) {
$condition[$condition_pk] = array_shift($conditionids);
}
}
unset($condition);
}
unset($object);
}
/**
* @param string $condition_table
* @param array $condition
* @param array $db_conditions
*
* @return array|null
*/
private static function getConditionId(string $condition_table, array $condition, array $db_conditions): ?string {
$condition += [
'operator' => DB::getDefault($condition_table, 'operator'),
'value' => DB::getDefault($condition_table, 'value')
];
$condition_pk = DB::getPk($condition_table);
foreach ($db_conditions as $db_condition) {
if (!DB::getUpdatedValues($condition_table, $condition, $db_condition)) {
return $db_condition[$condition_pk];
}
}
return null;
}
/**
* @param array $items
* @param array|null $db_items
* @param array|null $upd_itemids
*/
private static function updateOverrides(array &$items, array &$db_items = null, array &$upd_itemids = null): void {
$ins_overrides = [];
$upd_overrides = [];
$del_overrideids = [];
$_upd_itemids = $db_items !== null ? [] : null;
$_upd_overrides = [];
foreach ($items as $i => &$item) {
if (!array_key_exists('overrides', $item)) {
continue;
}
$changed = false;
$db_overrides = $db_items !== null
? array_column($db_items[$item['itemid']]['overrides'], null, 'step')
: [];
$db_override_steps = $db_items !== null
? array_column($db_items[$item['itemid']]['overrides'], 'step', 'name')
: [];
foreach ($item['overrides'] as &$override) {
if (array_key_exists($override['step'], $db_overrides)) {
$db_override = $db_overrides[$override['step']];
$override['lld_overrideid'] = $db_override['lld_overrideid'];
unset($db_overrides[$override['step']]);
$upd_override = DB::getUpdatedValues('lld_override', $override, $db_override);
if (array_key_exists($override['name'], $db_override_steps)
&& $override['step'] != $db_override_steps[$override['name']]) {
$_upd_overrides[] = [
'values' => $upd_override,
'where' => ['lld_overrideid' => $db_override['lld_overrideid']]
];
$upd_override = ['name' => '#'.$db_override['lld_overrideid']];
}
if ($upd_override) {
$upd_overrides[] = [
'values' => $upd_override,
'where' => ['lld_overrideid' => $db_override['lld_overrideid']]
];
$changed = true;
}
}
else {
$ins_overrides[] = ['itemid' => $item['itemid']] + $override;
$changed = true;
}
}
unset($override);
if ($db_overrides) {
$del_overrideids = array_merge($del_overrideids, array_column($db_overrides, 'lld_overrideid'));
$changed = true;
}
if ($db_items !== null && $changed) {
$_upd_itemids[$i] = $item['itemid'];
}
}
unset($item);
if ($del_overrideids) {
self::deleteOverrides($del_overrideids);
}
if ($upd_overrides) {
DB::update('lld_override', array_merge($upd_overrides, $_upd_overrides));
}
if ($ins_overrides) {
$overrideids = DB::insert('lld_override', $ins_overrides);
}
$overrides = [];
$db_overrides = null;
$upd_overrideids = null;
if ($db_items !== null) {
$db_overrides = [];
$upd_overrideids = [];
$item_indexes = [];
}
foreach ($items as $i => &$item) {
if (!array_key_exists('overrides', $item)) {
continue;
}
foreach ($item['overrides'] as &$override) {
if (!array_key_exists('lld_overrideid', $override)) {
$override['lld_overrideid'] = array_shift($overrideids);
if ($db_items !== null) {
$db_overrides[$override['lld_overrideid']] = [
'lld_overrideid' => $override['lld_overrideid']
];
if (array_key_exists('filter', $override)) {
$db_overrides[$override['lld_overrideid']]['filter'] = [
'evaltype' => DB::getDefault('lld_override', 'evaltype'),
'formula' => DB::getDefault('lld_override', 'formula'),
'conditions' => []
];
}
if (array_key_exists('operations', $override)) {
$db_overrides[$override['lld_overrideid']]['operations'] = [];
}
}
}
else {
$db_overrides[$override['lld_overrideid']] =
$db_items[$item['itemid']]['overrides'][$override['lld_overrideid']];
}
$overrides[] = &$override;
if ($db_items !== null) {
$item_indexes[] = $i;
}
}
unset($override);
}
unset($item);
if ($overrides) {
self::updateOverrideFilters($overrides, $db_overrides, $upd_overrideids);
self::updateOverrideOperations($overrides, $db_overrides, $upd_overrideids);
}
if ($db_items !== null) {
foreach (array_unique(array_intersect_key($item_indexes, $upd_overrideids)) as $i) {
$_upd_itemids[$i] = $items[$i]['itemid'];
}
foreach ($items as $i => &$item) {
if (!array_key_exists($i, $_upd_itemids)) {
unset($item['overrides'], $db_items[$item['itemid']]['overrides']);
}
}
unset($item);
$upd_itemids += $_upd_itemids;
}
}
/**
* @param array $del_overrideids
*/
private static function deleteOverrides(array $del_overrideids): void {
DB::delete('lld_override_condition', ['lld_overrideid' => $del_overrideids]);
$options = [
'output' => ['lld_override_operationid'],
'filter' => ['lld_overrideid' => $del_overrideids]
];
$del_operationids =
DBfetchColumn(DBselect(DB::makeSql('lld_override_operation', $options)), 'lld_override_operationid');
self::deleteOverrideOperations($del_operationids);
DB::delete('lld_override', ['lld_overrideid' => $del_overrideids]);
}
/**
* @param array $overrides
* @param array|null $db_overrides
* @param array|null $upd_overrideids
*/
private static function updateOverrideFilters(array &$overrides, ?array &$db_overrides,
?array &$upd_overrideids): void {
self::updateFilters($overrides, $db_overrides, $upd_overrideids, 'lld_override', 'lld_override_condition');
}
/**
* @param array $overrides
* @param array|null $db_overrides
* @param array|null $upd_overrideids
*/
private static function updateOverrideOperations(array &$overrides, ?array &$db_overrides,
?array &$upd_overrideids): void {
$ins_operations = [];
$del_operationids = [];
$_upd_overrideids = $db_overrides !== null ? [] : null;
foreach ($overrides as $i => &$override) {
if (!array_key_exists('operations', $override)) {
continue;
}
$changed = false;
$db_operations = $db_overrides !== null ? $db_overrides[$override['lld_overrideid']]['operations'] : [];
foreach ($override['operations'] as &$operation) {
self::setOperationId($operation, $db_operations);
if (array_key_exists('lld_override_operationid', $operation)) {
unset($db_operations[$operation['lld_override_operationid']]);
}
else {
$ins_operations[] = ['lld_overrideid' => $override['lld_overrideid']] + $operation;
$changed = true;
}
}
unset($operation);
if ($db_operations) {
$del_operationids = array_merge($del_operationids, array_keys($db_operations));
$changed = true;
}
if ($db_overrides !== null && $changed) {
$_upd_overrideids[$i] = $override['lld_overrideid'];
}
}
unset($override);
if ($del_operationids) {
self::deleteOverrideOperations($del_operationids);
}
if ($ins_operations) {
$operationids = DB::insert('lld_override_operation', $ins_operations);
}
$operations = [];
$upd_operationids = null;
if ($db_overrides !== null) {
$upd_operationids = [];
$override_indexes = [];
}
foreach ($overrides as $i => &$override) {
if (!array_key_exists('operations', $override)) {
continue;
}
foreach ($override['operations'] as &$operation) {
if (!array_key_exists('lld_override_operationid', $operation)) {
$operation['lld_override_operationid'] = array_shift($operationids);
$operations[] = &$operation;
if ($db_overrides !== null) {
$override_indexes[] = $i;
}
}
}
unset($operation);
}
unset($override);
if ($operations) {
self::createOverrideOperationFields($operations, $upd_operationids);
}
if ($db_overrides !== null) {
foreach (array_unique(array_intersect_key($override_indexes, $upd_operationids)) as $i) {
$_upd_overrideids[$i] = $overrides[$i]['lld_overrideid'];
}
foreach ($overrides as $i => &$override) {
if (!array_key_exists($i, $_upd_overrideids)) {
unset($override['operations'], $db_overrides[$override['lld_overrideid']]['operations']);
}
}
unset($override);
$upd_overrideids += $_upd_overrideids;
}
}
/**
* Set the ID of override operation if all fields of the given operation are equal to all fields of one of existing
* override operations.
*
* @param array $operation
* @param array $db_operations
*/
private static function setOperationId(array &$operation, array $db_operations): void {
$_operation = $operation
+ array_intersect_key(DB::getDefaults('lld_override_operation'), array_flip(['operator', 'value']))
+ array_fill_keys(self::OPERATION_FIELDS, []);
foreach ($db_operations as $db_operation) {
if (self::operationMatches($_operation, $db_operation)) {
$operation = $_operation;
return;
}
}
}
/**
* Check whether the existing override operation in database matches the given override operation.
*
* @param array $operation
* @param array $db_operation
*
* @return bool
*/
private static function operationMatches(array &$operation, array $db_operation): bool {
if (DB::getUpdatedValues('lld_override_operation', $operation, $db_operation)) {
return false;
}
foreach (self::OPERATION_FIELDS as $optable => $opfield) {
$pk = DB::getPk($optable);
if ($operation[$opfield]) {
if (in_array($opfield, ['optag', 'optemplate'])) {
if (!$db_operation[$opfield]) {
return false;
}
foreach ($operation[$opfield] as &$op) {
foreach ($db_operation[$opfield] as $i => $db_op) {
if (DB::getUpdatedValues($optable, $op, $db_op)) {
continue;
}
unset($db_operation[$opfield][$i]);
$op[$pk] = $db_op[$pk];
continue 2;
}
return false;
}
unset($op);
if ($db_operation[$opfield]) {
return false;
}
}
elseif (DB::getUpdatedValues($optable, $operation[$opfield], $db_operation[$opfield])) {
return false;
}
}
elseif ($db_operation[$opfield]) {
return false;
}
}
$operation['lld_override_operationid'] = $db_operation['lld_override_operationid'];
return true;
}
/**
* @param array $del_operationids
*/
private static function deleteOverrideOperations(array $del_operationids): void {
foreach (self::OPERATION_FIELDS as $optable => $foo) {
DB::delete($optable, ['lld_override_operationid' => $del_operationids]);
}
DB::delete('lld_override_operation', ['lld_override_operationid' => $del_operationids]);
}
/**
* @param array $operations
* @param array|null $upd_operationids
*/
private static function createOverrideOperationFields(array &$operations, ?array &$upd_operationids): void {
foreach (self::OPERATION_FIELDS as $optable => $opfield) {
$pk = DB::getPk($optable);
if (in_array($opfield, ['optag', 'optemplate'])) {
$ins_opfields = [];
foreach ($operations as $i => $operation) {
if (!array_key_exists($opfield, $operation) || !$operation[$opfield]) {
continue;
}
foreach ($operation[$opfield] as $_opfield) {
$ins_opfields[] =
['lld_override_operationid' => $operation['lld_override_operationid']] + $_opfield;
}
if ($upd_operationids !== null) {
$upd_operationids[$i] = $operation['lld_override_operationid'];
}
}
if ($ins_opfields) {
$opfieldids = DB::insert($optable, $ins_opfields);
}
foreach ($operations as &$operation) {
if (!array_key_exists($opfield, $operation)) {
continue;
}
foreach ($operation[$opfield] as &$_opfield) {
$_opfield[$pk] = array_shift($opfieldids);
}
unset($_opfield);
}
unset($operation);
}
else {
$ins_opfields = [];
foreach ($operations as $i => &$operation) {
if (!array_key_exists($opfield, $operation) || !$operation[$opfield]) {
continue;
}
$ins_opfields[] = [$pk => $operation['lld_override_operationid']] + $operation[$opfield];
if ($upd_operationids !== null) {
$upd_operationids[$i] = $operation['lld_override_operationid'];
}
}
unset($operation);
if ($ins_opfields) {
DB::insert($optable, $ins_opfields, false);
}
}
}
}
/**
* @param array $templateids
* @param array $hostids
*/
public static function linkTemplateObjects(array $templateids, array $hostids): void {
$db_items = DB::select('items', [
'output' => array_merge(['itemid', 'name', 'type', 'key_', 'lifetime', 'description', 'status'],
array_diff(CItemType::FIELD_NAMES, ['interfaceid', 'parameters'])
),
'filter' => [
'hostid' => $templateids,
'flags' => ZBX_FLAG_DISCOVERY_RULE
],
'preservekeys' => true
]);
if (!$db_items) {
return;
}
self::addInternalFields($db_items);
$items = [];
foreach ($db_items as $db_item) {
$item = array_intersect_key($db_item, array_flip(['itemid', 'type']));
if ($db_item['type'] == ITEM_TYPE_SCRIPT) {
$item += ['parameters' => []];
}
$items[] = $item + [
'preprocessing' => [],
'lld_macro_paths' => [],
'filter' => [],
'overrides' => []
];
}
self::addAffectedObjects($items, $db_items);
$ruleids = array_keys($db_items);
$items = array_values($db_items);
foreach ($items as &$item) {
if (array_key_exists('parameters', $item)) {
$item['parameters'] = array_values($item['parameters']);
}
$item['preprocessing'] = array_values($item['preprocessing']);
$item['lld_macro_paths'] = array_values($item['lld_macro_paths']);
$item['filter']['conditions'] = array_values($item['filter']['conditions']);
foreach ($item['filter']['conditions'] as &$condition) {
if ($item['filter']['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
unset($condition['formulaid']);
}
}
unset($condition);
foreach ($item['overrides'] as &$override) {
foreach ($override['filter']['conditions'] as &$condition) {
if ($override['filter']['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) {
unset($condition['formulaid']);
}
}
unset($condition);
$override['filter']['conditions'] = array_values($override['filter']['conditions']);
foreach ($override['operations'] as &$operation) {
$operation['optag'] = array_values($operation['optag']);
$operation['optemplate'] = array_values($operation['optemplate']);
}
unset($operation);
$override['operations'] = array_values($override['operations']);
}
unset($override);
$item['overrides'] = $item['overrides'];
}
unset($item);
self::inherit($items, [], $hostids);
CItemPrototype::linkTemplateObjects($templateids, $hostids);
API::TriggerPrototype()->syncTemplates(['templateids' => $templateids, 'hostids' => $hostids]);
API::GraphPrototype()->syncTemplates(['templateids' => $templateids, 'hostids' => $hostids]);
API::HostPrototype()->linkTemplateObjects($ruleids, $hostids);
}
/**
* @inheritDoc
*/
protected static function inherit(array $items, array $db_items = [], ?array $hostids = null,
bool $is_dep_items = false): void {
$tpl_links = self::getTemplateLinks($items, $hostids);
if ($hostids === null) {
self::filterObjectsToInherit($items, $db_items, $tpl_links);
if (!$items) {
return;
}
}
self::checkDoubleInheritedNames($items, $db_items, $tpl_links);
$chunks = self::getInheritChunks($items, $tpl_links);
foreach ($chunks as $chunk) {
$_items = array_intersect_key($items, array_flip($chunk['item_indexes']));
$_db_items = array_intersect_key($db_items, array_flip(array_column($_items, 'itemid')));
$_hostids = array_keys($chunk['hosts']);
self::inheritChunk($_items, $_db_items, $tpl_links, $_hostids);
}
}
/**
* @param array $items
* @param array $db_items
* @param array $tpl_links
* @param array $hostids
*/
private static function inheritChunk(array $items, array $db_items, array $tpl_links, array $hostids): void {
$items_to_link = [];
$items_to_update = [];
foreach ($items as $i => $item) {
if (!array_key_exists($item['itemid'], $db_items)) {
$items_to_link[] = $item;
}
else {
$items_to_update[] = $item;
}
unset($items[$i]);
}
$ins_items = [];
$upd_items = [];
$upd_db_items = [];
if ($items_to_link) {
$upd_db_items = self::getChildObjectsUsingName($items_to_link, $hostids);
if ($upd_db_items) {
$upd_items = self::getUpdChildObjectsUsingName($items_to_link, $upd_db_items);
}
$ins_items = self::getInsChildObjects($items_to_link, $upd_db_items, $tpl_links, $hostids);
}
if ($items_to_update) {
$_upd_db_items = self::getChildObjectsUsingTemplateid($items_to_update, $db_items, $hostids);
$_upd_items = self::getUpdChildObjectsUsingTemplateid($items_to_update, $_upd_db_items);
self::checkDuplicates($_upd_items, $_upd_db_items);
$upd_items = array_merge($upd_items, $_upd_items);
$upd_db_items += $_upd_db_items;
}
self::setChildMasterItemIds($upd_items, $ins_items, $hostids);
self::checkDependentItems(array_merge($upd_items, $ins_items), $upd_db_items, true);
self::addInterfaceIds($upd_items, $upd_db_items, $ins_items);
if ($upd_items) {
self::updateForce($upd_items, $upd_db_items);
}
if ($ins_items) {
self::createForce($ins_items);
}
self::inherit(array_merge($upd_items, $ins_items), $upd_db_items);
}
/**
* @param array $items
* @param array $hostids
*
* @return array
*/
private static function getChildObjectsUsingName(array $items, array $hostids): array {
$result = DBselect(
'SELECT i.itemid,ht.hostid,i.key_,i.templateid,i.flags,h.status AS host_status,'.
'ht.templateid AS parent_hostid'.
' FROM hosts_templates ht,items i,hosts h'.
' WHERE ht.hostid=i.hostid'.
' AND ht.hostid=h.hostid'.
' AND '.dbConditionId('ht.templateid', array_unique(array_column($items, 'hostid'))).
' AND '.dbConditionString('i.key_', array_unique(array_column($items, 'key_'))).
' AND '.dbConditionId('ht.hostid', $hostids)
);
$upd_db_items = [];
$parent_indexes = [];
while ($row = DBfetch($result)) {
foreach ($items as $i => $item) {
if (bccomp($row['parent_hostid'], $item['hostid']) == 0 && $row['key_'] === $item['key_']) {
if ($row['flags'] == $item['flags'] && $row['templateid'] == 0) {
$upd_db_items[$row['itemid']] = $row;
$parent_indexes[$row['itemid']] = $i;
}
else {
self::showObjectMismatchError($item, $row);
}
}
}
}
if (!$upd_db_items) {
return [];
}
$options = [
'output' => array_merge(['uuid', 'itemid', 'name', 'type', 'key_', 'lifetime', 'description', 'status'],
array_diff(CItemType::FIELD_NAMES, ['parameters'])
),
'itemids' => array_keys($upd_db_items)
];
$result = DBselect(DB::makeSql('items', $options));
while ($row = DBfetch($result)) {
$upd_db_items[$row['itemid']] = $row + $upd_db_items[$row['itemid']];
}
$upd_items = [];
foreach ($upd_db_items as $upd_db_item) {
$item = $items[$parent_indexes[$upd_db_item['itemid']]];
$upd_items[] = [
'itemid' => $upd_db_item['itemid'],
'type' => $item['type'],
'preprocessing' => [],
'lld_macro_paths' => [],
'filter' => [],
'overrides' => [],
'parameters' => []
];
}
self::addAffectedObjects($upd_items, $upd_db_items);
return $upd_db_items;
}
/**
* @param array $items
* @param array $upd_db_items
*
* @return array
*/
private static function getUpdChildObjectsUsingName(array $items, array $upd_db_items): array {
$parent_indexes = [];
foreach ($items as $i => &$item) {
$item['uuid'] = '';
$item = self::unsetNestedObjectIds($item);
$parent_indexes[$item['hostid']][$item['key_']] = $i;
}
unset($item);
$upd_items = [];
foreach ($upd_db_items as $upd_db_item) {
$item = $items[$parent_indexes[$upd_db_item['parent_hostid']][$upd_db_item['key_']]];
$upd_item = [
'itemid' => $upd_db_item['itemid'],
'hostid' => $upd_db_item['hostid'],
'templateid' => $item['itemid'],
'host_status' => $upd_db_item['host_status']
] + $item;
$upd_item += [
'preprocessing' => [],
'lld_macro_paths' => [],
'filter' => [],
'overrides' => [],
'parameters' => []
];
$upd_items[] = $upd_item;
}
return $upd_items;
}
/**
* @param array $items
* @param array $upd_db_items
* @param array $tpl_links
* @param array $hostids
*
* @return array
*/
private static function getInsChildObjects(array $items, array $upd_db_items, array $tpl_links,
array $hostids): array {
$ins_items = [];
$upd_item_keys = [];
foreach ($upd_db_items as $upd_db_item) {
$upd_item_keys[$upd_db_item['hostid']][] = $upd_db_item['key_'];
}
foreach ($items as $item) {
$item['uuid'] = '';
$item = self::unsetNestedObjectIds($item);
foreach ($tpl_links[$item['hostid']] as $host) {
if (!in_array($host['hostid'], $hostids)
|| (array_key_exists($host['hostid'], $upd_item_keys)
&& in_array($item['key_'], $upd_item_keys[$host['hostid']]))) {
continue;
}
$ins_items[] = [
'hostid' => $host['hostid'],
'templateid' => $item['itemid'],
'host_status' => $host['status']
] + array_diff_key($item, array_flip(['itemid']));
}
}
return $ins_items;
}
/**
* @param array $items
* @param array $db_items
* @param array $hostids
*
* @return array
*/
private static function getChildObjectsUsingTemplateid(array $items, array $db_items, array $hostids): array {
$upd_db_items = DB::select('items', [
'output' => array_merge(['itemid', 'name', 'type', 'key_', 'lifetime', 'description', 'status'],
array_diff(CItemType::FIELD_NAMES, ['parameters'])
),
'filter' => [
'templateid' => array_keys($db_items),
'hostid' => $hostids
],
'preservekeys' => true
]);
self::addInternalFields($upd_db_items);
if ($upd_db_items) {
$parent_indexes = array_flip(array_column($items, 'itemid'));
$upd_items = [];
foreach ($upd_db_items as $upd_db_item) {
$item = $items[$parent_indexes[$upd_db_item['templateid']]];
$db_item = $db_items[$upd_db_item['templateid']];
$upd_item = [
'itemid' => $upd_db_item['itemid'],
'type' => $item['type']
];
$upd_item += array_intersect_key([
'preprocessing' => [],
'lld_macro_paths' => [],
'filter' => [],
'overrides' => [],
'parameters' => []
], $db_item);
$upd_items[] = $upd_item;
}
self::addAffectedObjects($upd_items, $upd_db_items);
}
return $upd_db_items;
}
/**
* @param array $items
* @param array $upd_db_items
*
* @return array
*/
private static function getUpdChildObjectsUsingTemplateid(array $items, array $upd_db_items): array {
$parent_indexes = array_flip(array_column($items, 'itemid'));
foreach ($items as &$item) {
unset($item['uuid']);
$item = self::unsetNestedObjectIds($item);
}
unset($item);
$upd_items = [];
foreach ($upd_db_items as $upd_db_item) {
$item = $items[$parent_indexes[$upd_db_item['templateid']]];
$upd_items[] = array_intersect_key($upd_db_item,
array_flip(['itemid', 'hostid', 'templateid', 'host_status'])
) + $item;
}
return $upd_items;
}
/**
* @param array $item
*
* @return array
*/
protected static function unsetNestedObjectIds(array $item): array {
$item = parent::unsetNestedObjectIds($item);
if (array_key_exists('lld_macro_paths', $item)) {
foreach ($item['lld_macro_paths'] as &$lld_macro_path) {
unset($lld_macro_path['lld_macro_pathid']);
}
unset($lld_macro_path);
}
if (array_key_exists('filter', $item)) {
foreach ($item['filter']['conditions'] as &$condition) {
unset($condition['item_conditionid']);
}
unset($condition);
}
if (array_key_exists('overrides', $item)) {
foreach ($item['overrides'] as &$override) {
unset($override['lld_overrideid']);
if (array_key_exists('filter', $override)) {
foreach ($override['filter']['conditions'] as &$condition) {
unset($condition['lld_override_conditionid']);
}
unset($condition);
}
if (array_key_exists('operations', $override)) {
foreach ($override['operations'] as &$operation) {
unset($operation['lld_override_operationid']);
if (array_key_exists('optag', $operation)) {
foreach ($operation['optag'] as &$optag) {
unset($optag['lld_override_optagid']);
}
unset($optag);
}
if (array_key_exists('optemplate', $operation)) {
foreach ($operation['optemplate'] as &$optemplate) {
unset($optemplate['lld_override_optemplateid']);
}
unset($optemplate);
}
}
unset($operation);
}
}
unset($override);
}
return $item;
}
/**
* @param array $itemids
*
* @return array
*/
public function delete(array $itemids): array {
$this->validateDelete($itemids, $db_items);
self::deleteForce($db_items);
return ['ruleids' => $itemids];
}
/**
* @param array $itemids
* @param array|null $db_items
*
* @throws APIException
*/
private function validateDelete(array $itemids, array &$db_items = null): void {
$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
if (!CApiInputValidator::validate($api_input_rules, $itemids, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_items = $this->get([
'output' => ['itemid', 'name', 'templateid'],
'itemids' => $itemids,
'editable' => true,
'preservekeys' => true
]);
if (count($db_items) != count($itemids)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
foreach ($itemids as $i => $itemid) {
if ($db_items[$itemid]['templateid'] != 0) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($i + 1),
_('cannot delete inherited LLD rule')
));
}
}
}
/**
* @param array $db_items
*/
public static function deleteForce(array $db_items): void {
self::addInheritedItems($db_items);
$del_itemids = array_keys($db_items);
self::deleteAffectedItemPrototypes($del_itemids);
self::deleteAffectedHostPrototypes($del_itemids);
self::deleteAffectedOverrides($del_itemids);
DB::delete('item_parameter', ['itemid' => $del_itemids]);
DB::delete('item_preproc', ['itemid' => $del_itemids]);
DB::delete('lld_macro_path', ['itemid' => $del_itemids]);
DB::delete('item_condition', ['itemid' => $del_itemids]);
DB::update('items', [
'values' => ['templateid' => 0],
'where' => ['itemid' => $del_itemids]
]);
DB::delete('items', ['itemid' => $del_itemids]);
$ins_housekeeper = [];
foreach ($del_itemids as $itemid) {
$ins_housekeeper[] = [
'tablename' => 'events',
'field' => 'lldruleid',
'value' => $itemid
];
}
DB::insertBatch('housekeeper', $ins_housekeeper);
self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_LLD_RULE, $db_items);
}
/**
* Delete item prototypes which belong to the given LLD rules.
*
* @param array $del_itemids
*/
private static function deleteAffectedItemPrototypes(array $del_itemids): void {
$db_items = DBfetchArrayAssoc(DBselect(
'SELECT id.itemid,i.name'.
' FROM item_discovery id,items i'.
' WHERE id.itemid=i.itemid'.
' AND '.dbConditionId('parent_itemid', $del_itemids)
), 'itemid');
if ($db_items) {
CItemPrototype::deleteForce($db_items);
}
}
/**
* Delete host prototypes which belong to the given LLD rules.
*
* @param array $del_itemids
*/
private static function deleteAffectedHostPrototypes(array $del_itemids): void {
$db_host_prototypes = DBfetchArrayAssoc(DBselect(
'SELECT hd.hostid,h.host'.
' FROM host_discovery hd,hosts h'.
' WHERE hd.hostid=h.hostid'.
' AND '.dbConditionId('hd.parent_itemid', $del_itemids)
), 'hostid');
if ($db_host_prototypes) {
CHostPrototype::deleteForce($db_host_prototypes);
}
}
/**
* Delete overrides which belong to the given LLD rules.
*
* @param array $del_itemids
*/
private static function deleteAffectedOverrides(array $del_itemids): void {
$del_overrideids = array_keys(DB::select('lld_override', [
'filter' => ['itemid' => $del_itemids],
'preservekeys' => true
]));
if ($del_overrideids) {
self::deleteOverrides($del_overrideids);
}
}
/**
* @param array $templateids
* @param array|null $hostids
*/
public static function unlinkTemplateObjects(array $templateids, array $hostids = null): void {
$hostids_condition = $hostids ? ' AND '.dbConditionId('ii.hostid', $hostids) : '';
$result = DBselect(
'SELECT ii.itemid,ii.name,ii.templateid,ii.uuid,h.status AS host_status'.
' FROM items i,items ii,hosts h'.
' WHERE i.itemid=ii.templateid'.
' AND ii.hostid=h.hostid'.
' AND '.dbConditionId('i.hostid', $templateids).
' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_RULE]).
$hostids_condition
);
$items = [];
$db_items = [];
while ($row = DBfetch($result)) {
$item = [
'itemid' => $row['itemid'],
'templateid' => 0
];
if ($row['host_status'] == HOST_STATUS_TEMPLATE) {
$item += ['uuid' => generateUuidV4()];
}
$items[] = $item;
$db_items[$row['itemid']] = $row;
}
if ($items) {
self::updateForce($items, $db_items);
$itemids = array_keys($db_items);
CItemPrototype::unlinkTemplateObjects($itemids);
API::HostPrototype()->unlinkTemplateObjects($itemids);
}
}
/**
* @param array $templateids
* @param array|null $hostids
*/
public static function clearTemplateObjects(array $templateids, array $hostids = null): void {
$hostids_condition = $hostids ? ' AND '.dbConditionId('ii.hostid', $hostids) : '';
$db_items = DBfetchArrayAssoc(DBselect(
'SELECT ii.itemid,ii.name'.
' FROM items i,items ii'.
' WHERE i.itemid=ii.templateid'.
' AND '.dbConditionId('i.hostid', $templateids).
' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_RULE]).
$hostids_condition
), 'itemid');
if ($db_items) {
self::deleteForce($db_items);
}
}
/**
* @deprecated
*
* Copies the given discovery rules to the specified hosts.
*
* @param array $data
* @param array $data['discoveryids'] An array of item ids to be cloned.
* @param array $data['hostids'] An array of host ids were the items should be cloned to.
*
* @return bool
*
* @throws APIException if no discovery rule IDs or host IDs are given or
* the user doesn't have the necessary permissions.
*/
public function copy(array $data) {
// validate data
if (!isset($data['discoveryids']) || !$data['discoveryids']) {
self::exception(ZBX_API_ERROR_PARAMETERS, _('No discovery rule IDs given.'));
}
if (!isset($data['hostids']) || !$data['hostids']) {
self::exception(ZBX_API_ERROR_PARAMETERS, _('No host IDs given.'));
}
$this->checkHostPermissions($data['hostids']);
// check if the given discovery rules exist
$count = $this->get([
'countOutput' => true,
'itemids' => $data['discoveryids']
]);
if ($count != count($data['discoveryids'])) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
// copy
foreach ($data['discoveryids'] as $discoveryid) {
foreach ($data['hostids'] as $hostid) {
$this->copyDiscoveryRule($discoveryid, $hostid);
}
}
return true;
}
/**
* Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid.
*
* @param array $hostids an array of host or template IDs
*
* @throws APIException if the user doesn't have write permissions for the given hosts.
*/
protected function checkHostPermissions(array $hostids) {
if ($hostids) {
$hostids = array_unique($hostids);
$count = API::Host()->get([
'countOutput' => true,
'hostids' => $hostids,
'editable' => true
]);
if ($count == count($hostids)) {
return;
}
$count += API::Template()->get([
'countOutput' => true,
'templateids' => $hostids,
'editable' => true
]);
if ($count != count($hostids)) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_('No permissions to referred object or it does not exist!')
);
}
}
}
/**
* Copies the given discovery rule to the specified host.
*
* @throws APIException if the discovery rule interfaces could not be mapped
* to the new host interfaces.
*
* @param string $discoveryid The ID of the discovery rule to be copied
* @param string $hostid Destination host id
*
* @return bool
*/
protected function copyDiscoveryRule($discoveryid, $hostid) {
// fetch discovery to clone
$srcDiscovery = $this->get([
'output' => array_merge(['itemid', 'hostid', 'name', 'type', 'key_', 'lifetime', 'description', 'status'],
CItemType::FIELD_NAMES
),
'selectFilter' => ['evaltype', 'formula', 'conditions'],
'selectLLDMacroPaths' => ['lld_macro', 'path'],
'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'],
'selectOverrides' => ['name', 'step', 'stop', 'filter', 'operations'],
'itemids' => $discoveryid,
'preservekeys' => true
]);
$srcDiscovery = reset($srcDiscovery);
// fetch source and destination hosts
$hosts = API::Host()->get([
'output' => ['hostid', 'host', 'name', 'status'],
'selectInterfaces' => ['interfaceid', 'main', 'type', 'useip', 'ip', 'dns', 'port', 'details'],
'hostids' => [$srcDiscovery['hostid'], $hostid],
'templated_hosts' => true,
'preservekeys' => true
]);
$src_host = $hosts[$srcDiscovery['hostid']];
$dst_host = $hosts[$hostid];
$dstDiscovery = $srcDiscovery;
$dstDiscovery['hostid'] = $hostid;
unset($dstDiscovery['itemid']);
if ($dstDiscovery['filter']) {
foreach ($dstDiscovery['filter']['conditions'] as &$condition) {
unset($condition['itemid'], $condition['item_conditionid']);
}
unset($condition);
}
if (!$dstDiscovery['lld_macro_paths']) {
unset($dstDiscovery['lld_macro_paths']);
}
if ($dstDiscovery['overrides']) {
foreach ($dstDiscovery['overrides'] as &$override) {
if (array_key_exists('filter', $override)) {
if (!$override['filter']['conditions']) {
unset($override['filter']);
}
unset($override['filter']['eval_formula']);
}
}
unset($override);
}
else {
unset($dstDiscovery['overrides']);
}
// if this is a plain host, map discovery interfaces
if ($src_host['status'] != HOST_STATUS_TEMPLATE) {
// find a matching interface
$interface = self::findInterfaceForItem($dstDiscovery['type'], $dst_host['interfaces']);
if ($interface) {
$dstDiscovery['interfaceid'] = $interface['interfaceid'];
}
// no matching interface found, throw an error
elseif ($interface !== false) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
'Cannot find host interface on "%1$s" for item key "%2$s".',
$dst_host['name'],
$dstDiscovery['key_']
));
}
}
// Master item should exists for LLD rule with type dependent item.
if ($srcDiscovery['type'] == ITEM_TYPE_DEPENDENT) {
$master_items = DBfetchArray(DBselect(
'SELECT i1.itemid'.
' FROM items i1,items i2'.
' WHERE i1.key_=i2.key_'.
' AND i1.hostid='.zbx_dbstr($dstDiscovery['hostid']).
' AND i2.itemid='.zbx_dbstr($srcDiscovery['master_itemid'])
));
if (!$master_items) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_s('Discovery rule "%1$s" cannot be copied without its master item.', $srcDiscovery['name'])
);
}
$dstDiscovery['master_itemid'] = $master_items[0]['itemid'];
}
// save new discovery
$newDiscovery = $this->create([$dstDiscovery]);
$dstDiscovery['itemid'] = $newDiscovery['itemids'][0];
// copy prototypes
$this->copyItemPrototypes($srcDiscovery['itemid'], $src_host, $dstDiscovery['itemid'], $dst_host);
// fetch new prototypes
$dstDiscovery['items'] = API::ItemPrototype()->get([
'output' => ['itemid', 'key_'],
'discoveryids' => $dstDiscovery['itemid'],
'preservekeys' => true
]);
if ($dstDiscovery['items']) {
// copy graphs
$this->copyGraphPrototypes($srcDiscovery, $dstDiscovery);
// copy triggers
$this->copyTriggerPrototypes($srcDiscovery, $src_host, $dst_host);
}
// copy host prototypes
$this->copyHostPrototypes($discoveryid, $dstDiscovery['itemid']);
return true;
}
/**
* Returns the interface that best matches the given item.
*
* @param array $item_type An item type
* @param array $interfaces An array of interfaces to choose from
*
* @return array|boolean The best matching interface;
* an empty array of no matching interface was found;
* false, if the item does not need an interface
*/
private static function findInterfaceForItem($item_type, array $interfaces) {
$type = itemTypeInterface($item_type);
if ($type == INTERFACE_TYPE_OPT) {
return false;
}
elseif ($type == INTERFACE_TYPE_ANY) {
return self::findInterfaceByPriority($interfaces);
}
// the item uses a specific type of interface
elseif ($type !== false) {
$interface_by_type = [];
foreach ($interfaces as $interface) {
if ($interface['main'] == INTERFACE_PRIMARY) {
$interface_by_type[$interface['type']] = $interface;
}
}
return array_key_exists($type, $interface_by_type) ? $interface_by_type[$type] : [];
}
// the item does not need an interface
else {
return false;
}
}
/**
* Return first main interface matched from list of preferred types, or NULL.
*
* @param array $interfaces An array of interfaces to choose from.
*
* @return ?array
*/
private static function findInterfaceByPriority(array $interfaces): ?array {
$interface_by_type = [];
foreach ($interfaces as $interface) {
if ($interface['main'] == INTERFACE_PRIMARY) {
$interface_by_type[$interface['type']] = $interface;
}
}
foreach (self::INTERFACE_TYPES_BY_PRIORITY as $interface_type) {
if (array_key_exists($interface_type, $interface_by_type)) {
return $interface_by_type[$interface_type];
}
}
return null;
}
/**
* Create copies of items prototypes from the given source LLD rule to the given destination host or template.
*
* @param string $src_ruleid
* @param array $src_host
* @param array $src_host['interfaces']
* @param string $dst_ruleid
* @param array $dst_host
* @param string $dst_host['hostid']
* @param string $dst_host['host']
* @param array $dst_host['interfaces']
*
* @throws APIException
*/
private static function copyItemPrototypes(string $src_ruleid, array $src_host, string $dst_ruleid,
array $dst_host): void {
$src_items = API::ItemPrototype()->get([
'output' => ['itemid', 'name', 'type', 'key_', 'value_type', 'units', 'history', 'trends',
'valuemapid', 'logtimefmt', 'description', 'status', 'discover',
// Type fields.
// The fields used for multiple item types.
'interfaceid', 'authtype', 'username', 'password', 'params', 'timeout', 'delay', 'trapper_hosts',
// Dependent item type specific fields.
'master_itemid',
// HTTP Agent item type specific fields.
'url', 'query_fields', 'request_method', 'post_type', 'posts',
'headers', 'status_codes', 'follow_redirects', 'retrieve_mode', 'output_format', 'http_proxy',
'verify_peer', 'verify_host', 'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'allow_traps',
// IPMI item type specific fields.
'ipmi_sensor',
// JMX item type specific fields.
'jmx_endpoint',
// Script item type specific fields.
'parameters',
// SNMP item type specific fields.
'snmp_oid',
// SSH item type specific fields.
'publickey', 'privatekey'
],
'selectPreprocessing' => ['type', 'params', 'error_handler', 'error_handler_params'],
'selectTags' => ['tag', 'value'],
'discoveryids' => $src_ruleid,
'preservekeys' => true
]);
if (!$src_items) {
return;
}
$src_itemids = array_fill_keys(array_keys($src_items), true);
$src_valuemapids = [];
$src_interfaceids = [];
$src_dep_items = [];
$dep_itemids = [];
foreach ($src_items as $itemid => $item) {
if ($item['valuemapid'] != 0) {
$src_valuemapids[$item['valuemapid']] = true;
}
if ($item['interfaceid'] != 0) {
$src_interfaceids[$item['interfaceid']] = true;
}
if ($item['type'] == ITEM_TYPE_DEPENDENT) {
if (array_key_exists($item['master_itemid'], $src_itemids)) {
$src_dep_items[$item['master_itemid']][] = $item;
unset($src_items[$itemid]);
}
else {
$dep_itemids[$item['master_itemid']][] = $item['itemid'];
}
}
}
$valuemap_links = [];
if ($src_valuemapids) {
$src_valuemaps = API::ValueMap()->get([
'output' => ['valuemapid', 'name'],
'valuemapids' => array_keys($src_valuemapids)
]);
$dst_valuemaps = API::ValueMap()->get([
'output' => ['valuemapid', 'hostid', 'name'],
'hostids' => $dst_host['hostid'],
'filter' => ['name' => array_unique(array_column($src_valuemaps, 'name'))]
]);
$dst_valuemapids = [];
foreach ($dst_valuemaps as $dst_valuemap) {
$dst_valuemapids[$dst_valuemap['name']][$dst_valuemap['hostid']] = $dst_valuemap['valuemapid'];
}
foreach ($src_valuemaps as $src_valuemap) {
if (array_key_exists($src_valuemap['name'], $dst_valuemapids)) {
foreach ($dst_valuemapids[$src_valuemap['name']] as $dst_hostid => $dst_valuemapid) {
$valuemap_links[$src_valuemap['valuemapid']][$dst_hostid] = $dst_valuemapid;
}
}
}
}
$interface_links = [];
$dst_interfaceids = [];
if ($src_interfaceids) {
$src_interfaces = [];
foreach ($src_host['interfaces'] as $src_interface) {
if (array_key_exists($src_interface['interfaceid'], $src_interfaceids)) {
$src_interfaces[$src_interface['interfaceid']] =
array_diff_key($src_interface, array_flip(['interfaceid']));
}
}
foreach ($dst_host['interfaces'] as $dst_interface) {
$dst_interfaceid = $dst_interface['interfaceid'];
unset($dst_interface['interfaceid']);
foreach ($src_interfaces as $src_interfaceid => $src_interface) {
if ($src_interface == $dst_interface) {
$interface_links[$src_interfaceid][$dst_host['hostid']] = $dst_interfaceid;
}
}
if ($dst_interface['main'] == INTERFACE_PRIMARY) {
$dst_interfaceids[$dst_host['hostid']][$dst_interface['type']] = $dst_interfaceid;
}
}
}
$master_item_links = [];
if ($dep_itemids) {
$master_items = API::Item()->get([
'output' => ['itemid', 'key_'],
'itemids' => array_keys($dep_itemids),
'webitems' => true
]);
$options = $dst_host['status'] == HOST_STATUS_TEMPLATE
? ['templateids' => $dst_host['hostid']]
: ['hostids' => $dst_host['hostid']];
$dst_master_items = API::Item()->get([
'output' => ['itemid', 'hostid', 'key_'],
'filter' => ['key_' => array_unique(array_column($master_items, 'key_'))],
'webitems' => true
] + $options);
$dst_master_itemids = [];
foreach ($dst_master_items as $item) {
$dst_master_itemids[$item['hostid']][$item['key_']] = $item['itemid'];
}
foreach ($master_items as $item) {
if (array_key_exists($dst_host['hostid'], $dst_master_itemids)
&& array_key_exists($item['key_'], $dst_master_itemids[$dst_host['hostid']])) {
$master_item_links[$item['itemid']][$dst_host['hostid']] =
$dst_master_itemids[$dst_host['hostid']][$item['key_']];
}
else {
$src_itemid = reset($dep_itemids[$item['itemid']]);
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
'Cannot copy item prototype with key "%1$s" without its master item with key "%2$s".',
$src_items[$src_itemid]['key_'], $item['key_']
));
}
}
}
do {
$dst_items = [];
foreach ($src_items as $src_item) {
$dst_item = array_diff_key($src_item, array_flip(['itemid']));
if ($src_item['valuemapid'] != 0) {
if (array_key_exists($src_item['valuemapid'], $valuemap_links)
&& array_key_exists($dst_host['hostid'], $valuemap_links[$src_item['valuemapid']])) {
$dst_item['valuemapid'] = $valuemap_links[$src_item['valuemapid']][$dst_host['hostid']];
}
else {
$dst_item['valuemapid'] = 0;
}
}
$dst_item['interfaceid'] = 0;
if ($src_item['interfaceid'] != 0) {
if (array_key_exists($src_item['interfaceid'], $interface_links)
&& array_key_exists($dst_host['hostid'], $interface_links[$src_item['interfaceid']])) {
$dst_item['interfaceid'] = $interface_links[$src_item['interfaceid']][$dst_host['hostid']];
}
else {
$type = itemTypeInterface($src_item['type']);
if (in_array($type,
[INTERFACE_TYPE_AGENT, INTERFACE_TYPE_SNMP, INTERFACE_TYPE_JMX, INTERFACE_TYPE_IPMI]
)) {
if (array_key_exists($dst_host['hostid'], $dst_interfaceids)
&& array_key_exists($type, $dst_interfaceids[$dst_host['hostid']])) {
$dst_item['interfaceid'] = $dst_interfaceids[$dst_host['hostid']][$type];
}
else {
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
'Cannot find host interface on "%1$s" for item prototype with key "%2$s".',
$dst_host['host'], $src_item['key_']
));
}
}
}
}
if ($src_item['type'] == ITEM_TYPE_DEPENDENT) {
$dst_item['master_itemid'] = $master_item_links[$src_item['master_itemid']][$dst_host['hostid']];
}
$dst_items[] = ['hostid' => $dst_host['hostid'], 'ruleid' => $dst_ruleid] + $dst_item;
}
$response = API::ItemPrototype()->create($dst_items);
$_src_items = [];
if ($src_dep_items) {
foreach ($src_items as $src_item) {
$dst_itemid = array_shift($response['itemids']);
if (array_key_exists($src_item['itemid'], $src_dep_items)) {
$master_item_links[$src_item['itemid']][$dst_host['hostid']] = $dst_itemid;
$_src_items = array_merge($_src_items, $src_dep_items[$src_item['itemid']]);
unset($src_dep_items[$src_item['itemid']]);
}
}
}
$src_items = $_src_items;
} while ($src_items);
}
/**
* Copies all of the graphs from the source discovery to the target discovery rule.
*
* @throws APIException if graph saving fails
*
* @param array $srcDiscovery The source discovery rule to copy from
* @param array $dstDiscovery The target discovery rule to copy to
*
* @return array
*/
protected function copyGraphPrototypes(array $srcDiscovery, array $dstDiscovery) {
// fetch source graphs
$srcGraphs = API::GraphPrototype()->get([
'output' => ['graphid', 'name', 'width', 'height', 'yaxismin', 'yaxismax', 'show_work_period',
'show_triggers', 'graphtype', 'show_legend', 'show_3d', 'percent_left', 'percent_right',
'ymin_type', 'ymax_type', 'ymin_itemid', 'ymax_itemid', 'discover'
],
'selectGraphItems' => ['itemid', 'drawtype', 'sortorder', 'color', 'yaxisside', 'calc_fnc', 'type'],
'selectHosts' => ['hostid'],
'discoveryids' => $srcDiscovery['itemid'],
'preservekeys' => true
]);
if (!$srcGraphs) {
return [];
}
$srcItemIds = [];
foreach ($srcGraphs as $key => $graph) {
// skip graphs with items from multiple hosts
if (count($graph['hosts']) > 1) {
unset($srcGraphs[$key]);
continue;
}
// skip graphs with http items
if (httpItemExists($graph['gitems'])) {
unset($srcGraphs[$key]);
continue;
}
// save all used item ids to map them to the new items
foreach ($graph['gitems'] as $item) {
$srcItemIds[$item['itemid']] = $item['itemid'];
}
if ($graph['ymin_itemid']) {
$srcItemIds[$graph['ymin_itemid']] = $graph['ymin_itemid'];
}
if ($graph['ymax_itemid']) {
$srcItemIds[$graph['ymax_itemid']] = $graph['ymax_itemid'];
}
}
// fetch source items
$items = API::Item()->get([
'output' => ['itemid', 'key_'],
'webitems' => true,
'itemids' => $srcItemIds,
'filter' => ['flags' => null],
'preservekeys' => true
]);
$srcItems = [];
$itemKeys = [];
foreach ($items as $item) {
$srcItems[$item['itemid']] = $item;
$itemKeys[$item['key_']] = $item['key_'];
}
// fetch newly cloned items
$newItems = API::Item()->get([
'output' => ['itemid', 'key_'],
'webitems' => true,
'hostids' => $dstDiscovery['hostid'],
'filter' => [
'key_' => $itemKeys,
'flags' => null
],
'preservekeys' => true
]);
$items = array_merge($dstDiscovery['items'], $newItems);
$dstItems = [];
foreach ($items as $item) {
$dstItems[$item['key_']] = $item;
}
$dstGraphs = $srcGraphs;
foreach ($dstGraphs as &$graph) {
unset($graph['graphid']);
foreach ($graph['gitems'] as &$gitem) {
// replace the old item with the new one with the same key
$item = $srcItems[$gitem['itemid']];
$gitem['itemid'] = $dstItems[$item['key_']]['itemid'];
}
unset($gitem);
// replace the old axis items with the new one with the same key
if ($graph['ymin_itemid']) {
$yMinSrcItem = $srcItems[$graph['ymin_itemid']];
$graph['ymin_itemid'] = $dstItems[$yMinSrcItem['key_']]['itemid'];
}
if ($graph['ymax_itemid']) {
$yMaxSrcItem = $srcItems[$graph['ymax_itemid']];
$graph['ymax_itemid'] = $dstItems[$yMaxSrcItem['key_']]['itemid'];
}
}
unset($graph);
// save graphs
$rs = API::GraphPrototype()->create($dstGraphs);
if (!$rs) {
self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone graph prototypes.'));
}
return $rs;
}
/**
* Copies all of the triggers from the source discovery to the target discovery rule.
*
* @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 $src_discovery, array $src_host, array $dst_host): array {
$src_triggers = API::TriggerPrototype()->get([
'output' => ['triggerid', 'expression', 'description', 'url_name', 'url', 'status', 'priority', 'comments',
'type', 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag', 'opdata',
'discover', 'event_name'
],
'selectItems' => ['itemid', 'type'],
'selectTags' => ['tag', 'value'],
'selectDependencies' => ['triggerid'],
'discoveryids' => $src_discovery['itemid']
]);
$dst_triggers = [];
foreach ($src_triggers as $i => $src_trigger) {
// Skip trigger prototypes with web items and remove them from source.
if (httpItemExists($src_trigger['items'])) {
unset($src_triggers[$i]);
}
else {
$dst_triggers[] = array_intersect_key($src_trigger, array_flip(['expression', 'description', 'url_name',
'url', 'status', 'priority', 'comments','type', 'recovery_mode', 'recovery_expression',
'correlation_mode', 'correlation_tag', 'opdata', 'discover', 'event_name', 'tags'
]));
}
}
if (!$dst_triggers) {
return [];
}
$src_triggers = array_values($src_triggers);
$dst_triggers = CMacrosResolverHelper::resolveTriggerExpressions($dst_triggers,
['sources' => ['expression', 'recovery_expression']]
);
foreach ($dst_triggers as &$trigger) {
$trigger['expression'] = CTriggerGeneralHelper::getExpressionWithReplacedHost(
$trigger['expression'], $src_host['host'], $dst_host['host']
);
if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) {
$trigger['recovery_expression'] = CTriggerGeneralHelper::getExpressionWithReplacedHost(
$trigger['recovery_expression'], $src_host['host'], $dst_host['host']
);
}
}
unset($trigger);
$result = API::TriggerPrototype()->create($dst_triggers);
if (!$result) {
self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot clone trigger prototypes.'));
}
$dst_triggerids = $result['triggerids'];
$src_trigger_indexes = array_flip(array_column($src_triggers, 'triggerid'));
$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;
}
$dst_triggers[$dst_triggerid] = ['triggerid' => $dst_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']]]];
unset($src_triggers[$i]['dependencies'][$i2]);
}
else {
$src_triggerids_up[$src_trigger_up['triggerid']] = true;
}
}
if (!$src_triggers[$i]['dependencies']) {
unset($dst_triggerids[$i]);
}
}
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');
$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 = CTriggerGeneralHelper::getExpressionWithReplacedHost(
$trigger['expression'], $dst_host['host'], $src_host['host']
);
$recovery_expression = $trigger['recovery_expression'];
if ($recovery_expression !== '') {
$recovery_expression = CTriggerGeneralHelper::getExpressionWithReplacedHost(
$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];
}
}
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']
]);
$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']
));
}
}
}
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;
}
/**
* Copy all of the host prototypes from the source discovery rule to the target discovery rule.
*
* @param string $src_discoveryid
* @param string $dst_discoveryid
*
* @throws APIException
*/
protected function copyHostPrototypes(string $src_discoveryid, string $dst_discoveryid): void {
$src_host_prototypes = API::HostPrototype()->get([
'output' => ['host', 'name', 'custom_interfaces', 'status', 'discover', 'inventory_mode'],
'selectInterfaces' => ['type', 'useip', 'ip', 'dns', 'port', 'main', 'details'],
'selectGroupLinks' => ['groupid'],
'selectGroupPrototypes' => ['name'],
'selectTemplates' => ['templateid'],
'selectTags' => ['tag', 'value'],
'selectMacros' => ['macro', 'type', 'value', 'description'],
'discoveryids' => $src_discoveryid
]);
if (!$src_host_prototypes) {
return;
}
$dst_host_prototypes = [];
foreach ($src_host_prototypes as $i => $src_host_prototype) {
unset($src_host_prototypes[$i]);
$dst_host_prototype = ['ruleid' => $dst_discoveryid] + array_intersect_key($src_host_prototype, array_flip([
'host', 'name', 'custom_interfaces', 'status', 'discover', 'inventory_mode', 'groupLinks',
'groupPrototypes', 'templates', 'tags'
]));
if ($src_host_prototype['custom_interfaces'] == HOST_PROT_INTERFACES_CUSTOM) {
foreach ($src_host_prototype['interfaces'] as $src_interface) {
$dst_interface =
array_intersect_key($src_interface, array_flip(['type', 'useip', 'ip', 'dns', 'port', 'main']));
if ($src_interface['type'] == INTERFACE_TYPE_SNMP) {
switch ($src_interface['details']['version']) {
case SNMP_V1:
case SNMP_V2C:
$dst_interface['details'] = array_intersect_key($src_interface['details'],
array_flip(['version', 'bulk', 'community'])
);
break;
case SNMP_V3:
$field_names = array_flip(['version', 'bulk', 'contextname', 'securityname',
'securitylevel'
]);
if ($src_interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV) {
$field_names += array_flip(['authprotocol', 'authpassphrase']);
}
elseif ($src_interface['details']['securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) {
$field_names +=
array_flip(['authprotocol', 'authpassphrase', 'privprotocol', 'privpassphrase']);
}
$dst_interface['details'] = array_intersect_key($src_interface['details'], $field_names);
break;
}
}
$dst_host_prototype['interfaces'][] = $dst_interface;
}
}
foreach ($src_host_prototype['macros'] as $src_macro) {
if ($src_macro['type'] == ZBX_MACRO_TYPE_SECRET) {
$dst_host_prototype['macros'][] = ['type' => ZBX_MACRO_TYPE_TEXT, 'value' => ''] + $src_macro;
}
else {
$dst_host_prototype['macros'][] = $src_macro;
}
}
$dst_host_prototypes[] = $dst_host_prototype;
}
API::HostPrototype()->create($dst_host_prototypes);
}
}