['templates' => 'h.hostid'], 'from' => ['hosts' => 'hosts h'], 'where' => ['h.status='.HOST_STATUS_TEMPLATE], 'group' => [], 'order' => [], 'limit' => null ]; $defOptions = [ 'groupids' => null, 'templateids' => null, 'parentTemplateids' => null, 'hostids' => null, 'graphids' => null, 'itemids' => null, 'triggerids' => null, 'with_items' => null, 'with_triggers' => null, 'with_graphs' => null, 'with_httptests' => null, 'editable' => false, 'nopermissions' => null, // filter 'evaltype' => TAG_EVAL_TYPE_AND_OR, 'tags' => null, 'filter' => null, 'search' => '', 'searchByAny' => null, 'startSearch' => false, 'excludeSearch' => false, 'searchWildcardsEnabled' => null, // output 'output' => API_OUTPUT_EXTEND, 'selectGroups' => null, 'selectTemplateGroups' => null, 'selectHosts' => null, 'selectTemplates' => null, 'selectParentTemplates' => null, 'selectItems' => null, 'selectDiscoveries' => null, 'selectTriggers' => null, 'selectGraphs' => null, 'selectMacros' => null, 'selectDashboards' => null, 'selectHttpTests' => null, 'selectTags' => null, 'selectValueMaps' => null, 'countOutput' => false, 'groupCount' => false, 'preservekeys' => false, 'sortfield' => '', 'sortorder' => '', 'limit' => null, 'limitSelects' => null ]; $options = zbx_array_merge($defOptions, $options); $this->validateGet($options); $this->checkDeprecatedParam($options, 'selectGroups'); // editable + PERMISSION CHECK if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; $userGroups = getUserGroupsByUserId(self::$userData['userid']); $sqlParts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM hosts_groups hgg'. ' JOIN rights r'. ' ON r.id=hgg.groupid'. ' AND '.dbConditionInt('r.groupid', $userGroups). ' WHERE h.hostid=hgg.hostid'. ' GROUP BY hgg.hostid'. ' HAVING MIN(r.permission)>'.PERM_DENY. ' AND MAX(r.permission)>='.zbx_dbstr($permission). ')'; } // groupids if (!is_null($options['groupids'])) { zbx_value2array($options['groupids']); $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); $sqlParts['where']['hgh'] = 'hg.hostid=h.hostid'; if ($options['groupCount']) { $sqlParts['group']['hg'] = 'hg.groupid'; } } // templateids if (!is_null($options['templateids'])) { zbx_value2array($options['templateids']); $sqlParts['where']['templateid'] = dbConditionInt('h.hostid', $options['templateids']); } // parentTemplateids if (!is_null($options['parentTemplateids'])) { zbx_value2array($options['parentTemplateids']); $sqlParts['from']['hosts_templates'] = 'hosts_templates ht'; $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['parentTemplateids']); $sqlParts['where']['hht'] = 'h.hostid=ht.hostid'; if ($options['groupCount']) { $sqlParts['group']['templateid'] = 'ht.templateid'; } } // hostids if (!is_null($options['hostids'])) { zbx_value2array($options['hostids']); $sqlParts['from']['hosts_templates'] = 'hosts_templates ht'; $sqlParts['where'][] = dbConditionInt('ht.hostid', $options['hostids']); $sqlParts['where']['hht'] = 'h.hostid=ht.templateid'; if ($options['groupCount']) { $sqlParts['group']['ht'] = 'ht.hostid'; } } // itemids if (!is_null($options['itemids'])) { zbx_value2array($options['itemids']); $sqlParts['from']['items'] = 'items i'; $sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']); $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; } // triggerids if (!is_null($options['triggerids'])) { zbx_value2array($options['triggerids']); $sqlParts['from']['functions'] = 'functions f'; $sqlParts['from']['items'] = 'items i'; $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; } // graphids if (!is_null($options['graphids'])) { zbx_value2array($options['graphids']); $sqlParts['from']['graphs_items'] = 'graphs_items gi'; $sqlParts['from']['items'] = 'items i'; $sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']); $sqlParts['where']['igi'] = 'i.itemid=gi.itemid'; $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; } // with_items if (!is_null($options['with_items'])) { $sqlParts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM items i'. ' WHERE h.hostid=i.hostid'. ' AND i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. ')'; } // with_triggers if (!is_null($options['with_triggers'])) { $sqlParts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM items i,functions f,triggers t'. ' WHERE i.hostid=h.hostid'. ' AND i.itemid=f.itemid'. ' AND f.triggerid=t.triggerid'. ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. ')'; } // with_graphs if (!is_null($options['with_graphs'])) { $sqlParts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM items i,graphs_items gi,graphs g'. ' WHERE i.hostid=h.hostid'. ' AND i.itemid=gi.itemid'. ' AND gi.graphid=g.graphid'. ' AND g.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. ')'; } // with_httptests if (!empty($options['with_httptests'])) { $sqlParts['where'][] = 'EXISTS (SELECT ht.httptestid FROM httptest ht WHERE ht.hostid=h.hostid)'; } // tags if ($options['tags'] !== null && $options['tags']) { $sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'h', 'host_tag', 'hostid' ); } // filter if (is_array($options['filter'])) { $this->dbFilter('hosts h', $options, $sqlParts); } // search if (is_array($options['search'])) { zbx_db_search('hosts h', $options, $sqlParts); } // limit if (zbx_ctype_digit($options['limit']) && $options['limit']) { $sqlParts['limit'] = $options['limit']; } $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); $upcased_index = array_search($this->tableAlias().'.name_upper', $sqlParts['select']); if ($upcased_index !== false) { unset($sqlParts['select'][$upcased_index]); } $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); while ($template = DBfetch($res)) { if ($options['countOutput']) { if ($options['groupCount']) { $result[] = $template; } else { $result = $template['rowscount']; } } else { $template['templateid'] = $template['hostid']; // Templates share table with hosts and host prototypes. Therefore remove template unrelated fields. unset($template['hostid'], $template['discover']); $result[$template['templateid']] = $template; } } if ($options['countOutput']) { return $result; } if ($result) { $result = $this->addRelatedObjects($options, $result); $result = $this->unsetExtraFields($result, ['name_upper']); } // removing keys (hash -> array) if (!$options['preservekeys']) { $result = zbx_cleanHashes($result); } return $result; } /** * Validates the input parameters for the get() method. * * @param array $options * * @throws APIException if the input is invalid */ protected function validateGet(array $options) { // Validate input parameters. $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'selectTags' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', ['tag', 'value'])], 'selectValueMaps' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', ['valuemapid', 'name', 'mappings', 'uuid'])], 'selectParentTemplates' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['templateid', 'host', 'name', 'description', 'uuid'])] ]]; $options_filter = array_intersect_key($options, $api_input_rules['fields']); if (!CApiInputValidator::validate($api_input_rules, $options_filter, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } } /** * Add template. * * @param array $templates * * @return array */ public function create(array $templates) { $this->validateCreate($templates); $ins_templates = []; foreach ($templates as $template) { unset($template['groups'], $template['templates'], $template['tags'], $template['macros']); $ins_templates[] = $template + ['status' => HOST_STATUS_TEMPLATE]; } $templateids = DB::insert('hosts', $ins_templates); foreach ($templates as $index => &$template) { $template['templateid'] = $templateids[$index]; } unset($template); $this->checkTemplatesLinks($templates); $this->updateGroups($templates); $this->updateTags($templates); $this->updateMacros($templates); $this->updateTemplates($templates); self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_TEMPLATE, $templates); return ['templateids' => $templateids]; } /** * @param array $templates * * @throws APIException if the input is invalid. */ protected function validateCreate(array &$templates) { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['host'], ['name']], 'fields' => [ 'uuid' => ['type' => API_UUID], 'host' => ['type' => API_H_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hosts', 'host')], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('hosts', 'name'), 'default_source' => 'host'], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'description')], 'vendor_name' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'vendor_name')], 'vendor_version' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'vendor_version')], 'groups' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['groupid']], 'fields' => [ 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'templates' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'tags' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('host_tag', 'tag')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('host_tag', 'value'), 'default' => DB::getDefault('host_tag', 'value')] ]], 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')], 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'provider' => CSettingsHelper::get(CSettingsHelper::VAULT_PROVIDER), 'length' => DB::getFieldLength('hostmacro', 'value')], ['else' => true, 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')] ]], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $templates, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } self::checkVendorFields($templates); self::addUuid($templates); self::checkUuidDuplicates($templates); $this->checkDuplicates($templates); $this->checkGroups($templates); $this->checkTemplates($templates); } /** * Add the UUID to those of the given templates that don't have the 'uuid' parameter set. * * @param array $templates */ private static function addUuid(array &$templates): void { foreach ($templates as &$template) { if (!array_key_exists('uuid', $template)) { $template['uuid'] = generateUuidV4(); } } unset($template); } /** * Verify template UUIDs are not repeated. * * @param array $templates * @param array|null $db_templates * * @throws APIException */ private static function checkUuidDuplicates(array $templates, array $db_templates = null): void { $template_indexes = []; foreach ($templates as $i => $template) { if (!array_key_exists('uuid', $template)) { continue; } if ($db_templates === null || $template['uuid'] !== $db_templates[$template['templateid']]['uuid']) { $template_indexes[$template['uuid']] = $i; } } if (!$template_indexes) { return; } $duplicates = DB::select('hosts', [ 'output' => ['uuid'], 'filter' => [ 'status' => HOST_STATUS_TEMPLATE, 'uuid' => array_keys($template_indexes) ], 'limit' => 1 ]); if ($duplicates) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($template_indexes[$duplicates[0]['uuid']] + 1), _('template with the same UUID already exists') ) ); } } /** * @param array $templates * * @return array */ public function update(array $templates): array { $this->validateUpdate($templates, $db_templates); $upd_templates =[]; foreach ($templates as $template) { $upd_template = DB::getUpdatedValues('hosts', $template, $db_templates[$template['templateid']]); if ($upd_template) { $upd_templates[] = [ 'values' => $upd_template, 'where' => ['hostid' => $template['templateid']] ]; } } if ($upd_templates) { DB::update('hosts', $upd_templates); } $this->updateGroups($templates, $db_templates); $this->updateTags($templates, $db_templates); $this->updateMacros($templates, $db_templates); $this->updateTemplates($templates, $db_templates); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_TEMPLATE, $templates, $db_templates); return ['templateids' => array_column($templates, 'templateid')]; } /** * @param array $templates * @param array|null $db_templates * * @throws APIException if the input is invalid. */ protected function validateUpdate(array &$templates, array &$db_templates = null) { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['templateid'], ['host'], ['name']], 'fields' => [ 'uuid' => ['type' => API_UUID], 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED], 'host' => ['type' => API_H_NAME, 'length' => DB::getFieldLength('hosts', 'host')], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('hosts', 'name')], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'description')], 'vendor_name' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'vendor_name')], 'vendor_version' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hosts', 'vendor_version')], 'groups' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['groupid']], 'fields' => [ 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'templates' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'templates_clear' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'tags' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('host_tag', 'tag')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('host_tag', 'value'), 'default' => DB::getDefault('host_tag', 'value')] ]], 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['hostmacroid']], 'fields' => [ 'hostmacroid' => ['type' => API_ID], 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('hostmacro', 'macro')], 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT])], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $templates, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_templates = $this->get([ 'output' => ['uuid', 'templateid', 'host', 'name', 'description', 'vendor_name', 'vendor_version'], 'templateids' => array_column($templates, 'templateid'), 'editable' => true, 'preservekeys' => true ]); if (count($db_templates) != count($templates)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } $this->addAffectedObjects($templates, $db_templates); self::checkVendorFields($templates, $db_templates); self::checkUuidDuplicates($templates, $db_templates); $this->checkDuplicates($templates, $db_templates); $this->checkGroups($templates, $db_templates); $this->checkTemplates($templates, $db_templates); $this->checkTemplatesLinks($templates, $db_templates); $templates = $this->validateHostMacros($templates, $db_templates); } /** * Check vendor fields for update or create operation. * * @param array $templates * @param array|null $db_templates * * @throws Exception */ private static function checkVendorFields(array $templates, array $db_templates = null): void { $vendor_fields = array_fill_keys(['vendor_name', 'vendor_version'], ''); foreach ($templates as $i => $template) { if (!array_key_exists('vendor_name', $template) && !array_key_exists('vendor_version', $template)) { continue; } $_template = array_intersect_key($template, $vendor_fields); if ($db_templates === null) { $_template += $vendor_fields; } else { $_template += array_intersect_key($db_templates[$template['templateid']], $vendor_fields); } if (($_template['vendor_name'] === '') !== ($_template['vendor_version'] === '')) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($i + 1), _('both vendor_name and vendor_version should be either present or empty') )); } } } /** * Delete template. * * @param array $templateids * @param array $templateids['templateids'] * * @return array */ public function delete(array $templateids) { $this->validateDelete($templateids, $db_templates); self::unlinkTemplatesObjects($templateids, null, true); // delete the discovery rules first $db_lld_rules = DB::select('items', [ 'output' => ['itemid', 'name'], 'filter' => [ 'hostid' => $templateids, 'flags' => ZBX_FLAG_DISCOVERY_RULE ], 'preservekeys' => true ]); if ($db_lld_rules) { CDiscoveryRule::deleteForce($db_lld_rules); } // delete the items $db_items = DB::select('items', [ 'output' => ['itemid', 'name'], 'filter' => [ 'hostid' => $templateids, 'flags' => ZBX_FLAG_DISCOVERY_NORMAL, 'type' => CItem::SUPPORTED_ITEM_TYPES ], 'preservekeys' => true ]); if ($db_items) { CItem::deleteForce($db_items); } // delete host from maps if (!empty($templateids)) { DB::delete('sysmaps_elements', ['elementtype' => SYSMAP_ELEMENT_TYPE_HOST, 'elementid' => $templateids]); } // disable actions // actions from conditions $actionids = []; $sql = 'SELECT DISTINCT actionid'. ' FROM conditions'. ' WHERE conditiontype='.CONDITION_TYPE_TEMPLATE. ' AND '.dbConditionString('value', $templateids); $dbActions = DBselect($sql); while ($dbAction = DBfetch($dbActions)) { $actionids[$dbAction['actionid']] = $dbAction['actionid']; } // actions from operations $sql = 'SELECT DISTINCT o.actionid'. ' FROM operations o,optemplate ot'. ' WHERE o.operationid=ot.operationid'. ' AND '.dbConditionInt('ot.templateid', $templateids); $dbActions = DBselect($sql); while ($dbAction = DBfetch($dbActions)) { $actionids[$dbAction['actionid']] = $dbAction['actionid']; } if (!empty($actionids)) { DB::update('actions', [ 'values' => ['status' => ACTION_STATUS_DISABLED], 'where' => ['actionid' => $actionids] ]); } // delete action conditions DB::delete('conditions', [ 'conditiontype' => CONDITION_TYPE_TEMPLATE, 'value' => $templateids ]); // delete action operation commands $operationids = []; $sql = 'SELECT DISTINCT ot.operationid'. ' FROM optemplate ot'. ' WHERE '.dbConditionInt('ot.templateid', $templateids); $dbOperations = DBselect($sql); while ($dbOperation = DBfetch($dbOperations)) { $operationids[$dbOperation['operationid']] = $dbOperation['operationid']; } DB::delete('optemplate', [ 'templateid'=>$templateids ]); // delete empty operations $delOperationids = []; $sql = 'SELECT DISTINCT o.operationid'. ' FROM operations o'. ' WHERE '.dbConditionInt('o.operationid', $operationids). ' AND NOT EXISTS(SELECT NULL FROM optemplate ot WHERE ot.operationid=o.operationid)'; $dbOperations = DBselect($sql); while ($dbOperation = DBfetch($dbOperations)) { $delOperationids[$dbOperation['operationid']] = $dbOperation['operationid']; } DB::delete('operations', [ 'operationid'=>$delOperationids ]); // delete web scenarios $db_httptests = DB::select('httptest', [ 'output' => ['httptestid', 'name'], 'filter' => ['hostid' => $templateids], 'preservekeys' => true ]); if ($db_httptests) { CHttpTest::deleteForce($db_httptests); } // Get host prototype operations from LLD overrides where this template is linked. $lld_override_operationids = []; $db_lld_override_operationids = DBselect( 'SELECT loo.lld_override_operationid'. ' FROM lld_override_operation loo'. ' WHERE EXISTS('. 'SELECT NULL'. ' FROM lld_override_optemplate lot'. ' WHERE lot.lld_override_operationid=loo.lld_override_operationid'. ' AND '.dbConditionId('lot.templateid', $templateids). ')' ); while ($db_lld_override_operationid = DBfetch($db_lld_override_operationids)) { $lld_override_operationids[] = $db_lld_override_operationid['lld_override_operationid']; } if ($lld_override_operationids) { DB::delete('lld_override_optemplate', ['templateid' => $templateids]); // Make sure there no other operations left to safely delete the operation. $delete_lld_override_operationids = []; $db_delete_lld_override_operationids = DBselect( 'SELECT loo.lld_override_operationid'. ' FROM lld_override_operation loo'. ' WHERE NOT EXISTS ('. 'SELECT NULL'. ' FROM lld_override_opstatus los'. ' WHERE los.lld_override_operationid=loo.lld_override_operationid'. ')'. ' AND NOT EXISTS ('. 'SELECT NULL'. ' FROM lld_override_opdiscover lod'. ' WHERE lod.lld_override_operationid=loo.lld_override_operationid'. ')'. ' AND NOT EXISTS ('. 'SELECT NULL'. ' FROM lld_override_opinventory loi'. ' WHERE loi.lld_override_operationid=loo.lld_override_operationid'. ')'. ' AND NOT EXISTS ('. 'SELECT NULL'. ' FROM lld_override_optemplate lot'. ' WHERE lot.lld_override_operationid=loo.lld_override_operationid'. ')'. ' AND '.dbConditionId('loo.lld_override_operationid', $lld_override_operationids) ); while ($db_delete_lld_override_operationid = DBfetch($db_delete_lld_override_operationids)) { $delete_lld_override_operationids[] = $db_delete_lld_override_operationid['lld_override_operationid']; } if ($delete_lld_override_operationids) { DB::delete('lld_override_operation', ['lld_override_operationid' => $delete_lld_override_operationids]); } } // Finally delete the template. DB::delete('host_tag', ['hostid' => $templateids]); DB::delete('hosts', ['hostid' => $templateids]); $this->addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_TEMPLATE, $db_templates); return ['templateids' => $templateids]; } /** * @param array $templateids * @param array|null $db_templates * * @throws APIException if the input is invalid. */ private function validateDelete(array &$templateids, array &$db_templates = null): void { $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; if (!CApiInputValidator::validate($api_input_rules, $templateids, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_templates = $this->get([ 'output' => ['templateid', 'host', 'name'], 'templateids' => $templateids, 'editable' => true, 'preservekeys' => true ]); if (count($db_templates) != count($templateids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } $del_templates = []; $result = DBselect( 'SELECT ht.hostid,ht.templateid AS del_templateid,htt.templateid'. ' FROM hosts_templates ht,hosts_templates htt'. ' WHERE ht.hostid=htt.hostid'. ' AND ht.templateid!=htt.templateid'. ' AND '.dbConditionId('ht.templateid', $templateids). ' AND '.dbConditionId('htt.templateid', $templateids, true) ); while ($row = DBfetch($result)) { $del_templates[$row['del_templateid']][$row['hostid']][] = $row['templateid']; } $del_links_clear = []; $options = [ 'output' => ['templateid', 'hostid'], 'filter' => [ 'templateid' => $templateids ] ]; $result = DBselect(DB::makeSql('hosts_templates', $options)); while ($row = DBfetch($result)) { if (!in_array($row['hostid'], $templateids)) { $del_links_clear[$row['templateid']][$row['hostid']] = true; } } if ($del_templates) { $this->checkTriggerExpressionsOfDelTemplates($del_templates); } if ($del_links_clear) { $this->checkTriggerDependenciesOfHostTriggers($del_links_clear); } } /** * Add given template groups, macros and templates to given templates. * * @param array $data * * @return array */ public function massAdd(array $data) { $this->validateMassAdd($data, $db_templates); $templates = $this->getObjectsByData($data, $db_templates); $this->updateGroups($templates, $db_templates); $this->updateMacros($templates, $db_templates); $this->updateTemplates($templates, $db_templates); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_TEMPLATE, $templates, $db_templates); return ['templateids' => array_column($data['templates'], 'templateid')]; } /** * Replace template groups, macros and templates on the given templates. * * @param array $data * * @return array */ public function massUpdate(array $data) { $this->validateMassUpdate($data, $db_templates); $templates = $this->getObjectsByData($data, $db_templates); $this->updateGroups($templates, $db_templates); $this->updateMacros($templates, $db_templates); $this->updateTemplates($templates, $db_templates); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_TEMPLATE, $templates, $db_templates); return ['templateids' => array_column($data['templates'], 'templateid')]; } /** * Remove given template groups, macros and templates from given templates. * * @param array $data * * @return array */ public function massRemove(array $data) { $this->validateMassRemove($data, $db_templates); $templates = $this->getObjectsByData($data, $db_templates); $this->updateGroups($templates, $db_templates); $this->updateMacros($templates, $db_templates); $this->updateTemplates($templates, $db_templates); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_TEMPLATE, $templates, $db_templates); return ['templateids' => $data['templateids']]; } /** * @param array $data * @param array|null $db_templates * * @throws APIException if the input is invalid. */ private function validateMassAdd(array &$data, ?array &$db_templates): void { $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'templates' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'groups' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['groupid']], 'fields' => [ 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')], 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'provider' => CSettingsHelper::get(CSettingsHelper::VAULT_PROVIDER), 'length' => DB::getFieldLength('hostmacro', 'value')], ['else' => true, 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')] ]], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] ]], 'templates_link' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $data, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_templates = $this->get([ 'output' => ['templateid', 'host'], 'templateids' => array_column($data['templates'], 'templateid'), 'editable' => true, 'preservekeys' => true ]); if (count($db_templates) != count($data['templates'])) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } if (array_key_exists('groups', $data) && $data['groups']) { $groupids = array_column($data['groups'], 'groupid'); $count = API::TemplateGroup()->get([ 'countOutput' => true, 'groupids' => $groupids, 'editable' => true ]); if ($count != count($groupids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } $this->massAddAffectedObjects('groups', $groupids, $db_templates); } if (array_key_exists('macros', $data) && $data['macros']) { $macros = []; foreach ($data['macros'] as $macro) { $macros[CApiInputValidator::trimMacro($macro['macro'])] = $macro['macro']; } $options = [ 'output' => ['hostid', 'macro'], 'filter' => ['hostid' => array_keys($db_templates)] ]; $db_macros = DBselect(DB::makeSql('hostmacro', $options)); while ($db_macro = DBfetch($db_macros)) { $trimmed_db_macro = CApiInputValidator::trimMacro($db_macro['macro']); if (array_key_exists($trimmed_db_macro, $macros)) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists on "%2$s".', $macros[$trimmed_db_macro], $db_templates[$db_macro['hostid']]['host'] ) ); } } foreach ($db_templates as &$db_template) { $db_template['macros'] = []; } unset($db_host); } if (array_key_exists('templates_link', $data) && $data['templates_link']) { $templateids = array_column($data['templates_link'], 'templateid'); $count = API::Template()->get([ 'countOutput' => true, 'templateids' => $templateids ]); if ($count != count($templateids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } $this->massAddAffectedObjects('templates', $templateids, $db_templates); $this->massCheckTemplatesLinks('massadd', $templateids, $db_templates); } } /** * @param array $data * @param array|null $db_templates * * @throws APIException if the input is invalid. */ private function validateMassUpdate(array &$data, ?array &$db_templates): void { $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'templates' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'groups' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['groupid']], 'fields' => [ 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'macros' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hostmacro', 'macro')], 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_MACRO_TYPE_TEXT, ZBX_MACRO_TYPE_SECRET, ZBX_MACRO_TYPE_VAULT]), 'default' => ZBX_MACRO_TYPE_TEXT], 'value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'provider' => CSettingsHelper::get(CSettingsHelper::VAULT_PROVIDER), 'length' => DB::getFieldLength('hostmacro', 'value')], ['else' => true, 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')] ]], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] ]], 'templates_link' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'templates_clear' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['templateid']], 'fields' => [ 'templateid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $data, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_templates = $this->get([ 'output' => ['templateid', 'host'], 'templateids' => array_column($data['templates'], 'templateid'), 'editable' => true, 'preservekeys' => true ]); if (count($db_templates) != count($data['templates'])) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } if (array_key_exists('groups', $data)) { $groupids = array_column($data['groups'], 'groupid'); $count = API::TemplateGroup()->get([ 'countOutput' => true, 'groupids' => $groupids ]); if ($count != count($groupids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } $this->massAddAffectedObjects('groups', [], $db_templates); $groupids = array_flip($groupids); $edit_groupids = []; foreach ($db_templates as $db_template) { $_groupids = $groupids; foreach ($db_template['groups'] as $db_group) { if (array_key_exists($db_group['groupid'], $_groupids)) { unset($_groupids[$db_group['groupid']]); } else { $edit_groupids[$db_group['groupid']] = true; } } $edit_groupids += $_groupids; } if ($edit_groupids) { $count = API::TemplateGroup()->get([ 'countOutput' => true, 'groupids' => array_keys($edit_groupids), 'editable' => true ]); if ($count != count($edit_groupids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } } } if (array_key_exists('macros', $data)) { $this->massAddAffectedObjects('macros', [], $db_templates); } if (array_key_exists('templates_link', $data) || (array_key_exists('templates_clear', $data) && $data['templates_clear'])) { if (array_key_exists('templates_link', $data) && array_key_exists('templates_clear', $data)) { $path_clear = '/templates_clear'; $path = '/templates_link'; foreach ($data['templates_clear'] as $i1_clear => $template_clear) { foreach ($data['templates_link'] as $i1 => $template) { if (bccomp($template['templateid'], $template_clear['templateid']) == 0) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', $path_clear.'/'.($i1_clear + 1).'/templateid', _s('cannot be specified the value of parameter "%1$s"', $path.'/'.($i1 + 1).'/templateid' ) )); } } } } $this->massAddAffectedObjects('templates', [], $db_templates); $templateids_link = array_key_exists('templates_link', $data) ? array_column($data['templates_link'], 'templateid') : []; $templateids_clear = array_key_exists('templates_clear', $data) ? array_column($data['templates_clear'], 'templateid') : []; $edit_templateids = array_flip($templateids_clear); if ($templateids_link) { foreach ($db_templates as $db_template) { $edit_templateids += array_flip(array_diff(array_column($db_template['templates'], 'templateid'), $templateids_link )); } } if ($edit_templateids) { $count = $this->get([ 'countOutput' => true, 'templateids' => array_keys($edit_templateids) ]); if ($count != count($edit_templateids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } if (array_key_exists('templates_link', $data)) { $this->massCheckTemplatesLinks('massupdate', $templateids_link, $db_templates, $templateids_clear); } else { $this->massCheckTemplatesLinks('massremove', $templateids_clear, $db_templates, $templateids_clear); } } } } /** * @param array $data * @param array|null $db_templates * * @throws APIException if the input is invalid. */ private function validateMassRemove(array &$data, ?array &$db_templates): void { $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 'templateids' => ['type' => API_IDS, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_NORMALIZE, 'uniq' => true], 'groupids' => ['type' => API_IDS, 'flags' => API_NORMALIZE, 'uniq' => true], 'macros' => ['type' => API_USER_MACROS, 'flags' => API_NORMALIZE, 'uniq' => true, 'length' => DB::getFieldLength('hostmacro', 'macro')], 'templateids_link' => ['type' => API_IDS, 'flags' => API_NORMALIZE, 'uniq' => true], 'templateids_clear' => ['type' => API_IDS, 'flags' => API_NORMALIZE, 'uniq' => true] ]]; if (!CApiInputValidator::validate($api_input_rules, $data, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_templates = $this->get([ 'output' => ['templateid', 'host'], 'templateids' => $data['templateids'], 'editable' => true, 'preservekeys' => true ]); if (count($db_templates) != count($data['templateids'])) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } if (array_key_exists('groupids', $data) && $data['groupids']) { $count = API::TemplateGroup()->get([ 'countOutput' => true, 'groupids' => $data['groupids'], 'editable' => true ]); if ($count != count($data['groupids'])) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } CTemplateGroup::checkTemplatesWithoutGroups($db_templates, $data['groupids']); $this->massAddAffectedObjects('groups', $data['groupids'], $db_templates); } if (array_key_exists('macros', $data) && $data['macros']) { $this->massAddAffectedObjects('macros', $data['macros'], $db_templates); } if ((array_key_exists('templateids_link', $data) && $data['templateids_link']) || (array_key_exists('templateids_clear', $data) && $data['templateids_clear'])) { if (array_key_exists('templateids_link', $data) && $data['templateids_link'] && array_key_exists('templateids_clear', $data) && $data['templateids_clear']) { $templateids = array_unique(array_merge($data['templateids_link'], $data['templateids_clear'])); } elseif (array_key_exists('templateids_link', $data) && $data['templateids_link']) { $templateids = $data['templateids_link']; } else { $templateids = $data['templateids_clear']; } $count = $this->get([ 'countOutput' => true, 'templateids' => $templateids ]); if ($count != count($templateids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } $this->massAddAffectedObjects('templates', $templateids, $db_templates); $this->massCheckTemplatesLinks('massremove', $templateids, $db_templates, array_key_exists('templateids_clear', $data) ? $data['templateids_clear'] : [] ); } } protected function addRelatedObjects(array $options, array $result) { $result = parent::addRelatedObjects($options, $result); // adding template groups $this->addRelatedGroups($options, $result, 'selectGroups'); $this->addRelatedGroups($options, $result, 'selectTemplateGroups'); $templateids = array_keys($result); if ($options['selectTemplates'] !== null) { if ($options['selectTemplates'] != API_OUTPUT_COUNT) { $templates = []; $relationMap = $this->createRelationMap($result, 'templateid', 'hostid', 'hosts_templates'); $related_ids = $relationMap->getRelatedIds(); if ($related_ids) { $templates = API::Template()->get([ 'output' => $options['selectTemplates'], 'templateids' => $related_ids, 'preservekeys' => true ]); if (!is_null($options['limitSelects'])) { order_result($templates, 'host'); } } $result = $relationMap->mapMany($result, $templates, 'templates', $options['limitSelects']); } else { $templates = API::Template()->get([ 'parentTemplateids' => $templateids, 'countOutput' => true, 'groupCount' => true ]); $templates = zbx_toHash($templates, 'templateid'); foreach ($result as $templateid => $template) { $result[$templateid]['templates'] = array_key_exists($templateid, $templates) ? $templates[$templateid]['rowscount'] : '0'; } } } if ($options['selectHosts'] !== null) { if ($options['selectHosts'] != API_OUTPUT_COUNT) { $hosts = []; $relationMap = $this->createRelationMap($result, 'templateid', 'hostid', 'hosts_templates'); $related_ids = $relationMap->getRelatedIds(); if ($related_ids) { $hosts = API::Host()->get([ 'output' => $options['selectHosts'], 'hostids' => $related_ids, 'preservekeys' => true ]); if (!is_null($options['limitSelects'])) { order_result($hosts, 'host'); } } $result = $relationMap->mapMany($result, $hosts, 'hosts', $options['limitSelects']); } else { $hosts = API::Host()->get([ 'templateids' => $templateids, 'countOutput' => true, 'groupCount' => true ]); $hosts = zbx_toHash($hosts, 'templateid'); foreach ($result as $templateid => $template) { $result[$templateid]['hosts'] = array_key_exists($templateid, $hosts) ? $hosts[$templateid]['rowscount'] : '0'; } } } if ($options['selectDashboards'] !== null) { if ($options['selectDashboards'] != API_OUTPUT_COUNT) { $dashboards = API::TemplateDashboard()->get([ 'output' => $this->outputExtend($options['selectDashboards'], ['templateid']), 'templateids' => $templateids ]); if (!is_null($options['limitSelects'])) { order_result($dashboards, 'name'); } // Build relation map. $relationMap = new CRelationMap(); foreach ($dashboards as $key => $dashboard) { $relationMap->addRelation($dashboard['templateid'], $key); } $dashboards = $this->unsetExtraFields($dashboards, ['templateid'], $options['selectDashboards']); $result = $relationMap->mapMany($result, $dashboards, 'dashboards', $options['limitSelects']); } else { $dashboards = API::TemplateDashboard()->get([ 'templateids' => $templateids, 'countOutput' => true, 'groupCount' => true ]); $dashboards = zbx_toHash($dashboards, 'templateid'); foreach ($result as $templateid => $template) { $result[$templateid]['dashboards'] = array_key_exists($templateid, $dashboards) ? $dashboards[$templateid]['rowscount'] : '0'; } } } if ($options['selectTags'] !== null) { foreach ($result as &$row) { $row['tags'] = []; } unset($row); if ($options['selectTags'] === API_OUTPUT_EXTEND) { $output = ['hosttagid', 'hostid', 'tag', 'value']; } else { $output = array_unique(array_merge(['hosttagid', 'hostid'], $options['selectTags'])); } $sql_options = [ 'output' => $output, 'filter' => ['hostid' => $templateids] ]; $db_tags = DBselect(DB::makeSql('host_tag', $sql_options)); while ($db_tag = DBfetch($db_tags)) { $hostid = $db_tag['hostid']; unset($db_tag['hosttagid'], $db_tag['hostid']); $result[$hostid]['tags'][] = $db_tag; } } return $result; } /** * Adds related template groups requested by "select*" options to the resulting object set. * * @param array $options [IN] Original input options. * @param array $result [IN/OUT] Result output. * @param string $option [IN] Possible values: * - "selectGroups" (deprecated); * - "selectHostGroups" (or any other value). */ private function addRelatedGroups(array $options, array &$result, string $option): void { if ($options[$option] === null || $options[$option] === API_OUTPUT_COUNT) { return; } $relationMap = $this->createRelationMap($result, 'hostid', 'groupid', 'hosts_groups'); $groups = API::TemplateGroup()->get([ 'output' => $options[$option], 'groupids' => $relationMap->getRelatedIds(), 'preservekeys' => true ]); $output_tag = $option === 'selectGroups' ? 'groups' : 'templategroups'; $result = $relationMap->mapMany($result, $groups, $output_tag); } }