['min_user_type' => USER_TYPE_ZABBIX_USER], 'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 'createglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 'updateglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 'deleteglobal' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] ]; protected $tableName = 'hostmacro'; protected $tableAlias = 'hm'; protected $sortColumns = ['macro']; /** * Get UserMacros data. * * @param array $options * @param array $options['groupids'] usermacrosgroup ids * @param array $options['hostids'] host ids * @param array $options['hostmacroids'] host macros ids * @param array $options['globalmacroids'] global macros ids * @param array $options['templateids'] template ids * @param boolean $options['globalmacro'] only global macros * @param boolean $options['selectGroups'] select groups * @param boolean $options['selectHosts'] select hosts * @param boolean $options['selectTemplates'] select templates * * @return array|boolean UserMacros data as array or false if error */ public function get($options = []) { $result = []; $userid = self::$userData['userid']; $sqlParts = [ 'select' => ['macros' => 'hm.hostmacroid'], 'from' => ['hostmacro hm'], 'where' => [], 'order' => [], 'limit' => null ]; $sqlPartsGlobal = [ 'select' => ['macros' => 'gm.globalmacroid'], 'from' => ['globalmacro gm'], 'where' => [], 'order' => [], 'limit' => null ]; $defOptions = [ 'groupids' => null, 'hostids' => null, 'hostmacroids' => null, 'globalmacroids' => null, 'templateids' => null, 'globalmacro' => null, 'inherited' => null, 'editable' => false, 'nopermissions' => null, // filter 'filter' => null, 'search' => null, 'searchByAny' => null, 'startSearch' => false, 'excludeSearch' => false, 'searchWildcardsEnabled' => null, // output 'output' => API_OUTPUT_EXTEND, 'selectGroups' => null, 'selectHosts' => null, 'selectTemplates' => null, 'countOutput' => false, 'preservekeys' => false, 'sortfield' => '', 'sortorder' => '', 'limit' => null ]; $options = zbx_array_merge($defOptions, $options); // editable + PERMISSION CHECK if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { if ($options['editable'] && !is_null($options['globalmacro'])) { return []; } else { $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; $userGroups = getUserGroupsByUserId($userid); $sqlParts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM hosts_groups hgg'. ' JOIN rights r'. ' ON r.id=hgg.groupid'. ' AND '.dbConditionInt('r.groupid', $userGroups). ' WHERE hm.hostid=hgg.hostid'. ' GROUP BY hgg.hostid'. ' HAVING MIN(r.permission)>'.PERM_DENY. ' AND MAX(r.permission)>='.zbx_dbstr($permission). ')'; } } // global macro if (!is_null($options['globalmacro'])) { $options['groupids'] = null; $options['hostmacroids'] = null; $options['triggerids'] = null; $options['hostids'] = null; $options['itemids'] = null; $options['selectGroups'] = null; $options['selectTemplates'] = null; $options['selectHosts'] = null; $options['inherited'] = null; } // globalmacroids if (!is_null($options['globalmacroids'])) { zbx_value2array($options['globalmacroids']); $sqlPartsGlobal['where'][] = dbConditionInt('gm.globalmacroid', $options['globalmacroids']); } // hostmacroids if (!is_null($options['hostmacroids'])) { zbx_value2array($options['hostmacroids']); $sqlParts['where'][] = dbConditionInt('hm.hostmacroid', $options['hostmacroids']); } // inherited if (!is_null($options['inherited'])) { $sqlParts['from']['hosts'] = 'hosts h'; $sqlParts['where'][] = $options['inherited'] ? 'h.templateid IS NOT NULL' : 'h.templateid IS NULL'; $sqlParts['where']['hmh'] = 'hm.hostid=h.hostid'; } // 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=hm.hostid'; } // hostids if (!is_null($options['hostids'])) { zbx_value2array($options['hostids']); $sqlParts['where'][] = dbConditionInt('hm.hostid', $options['hostids']); } // templateids if (!is_null($options['templateids'])) { zbx_value2array($options['templateids']); $sqlParts['from']['macros_templates'] = 'hosts_templates ht'; $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']); $sqlParts['where']['hht'] = 'hm.hostid=ht.hostid'; } // sorting $sqlParts = $this->applyQuerySortOptions('hostmacro', 'hm', $options, $sqlParts); $sqlPartsGlobal = $this->applyQuerySortOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); // limit if (zbx_ctype_digit($options['limit']) && $options['limit']) { $sqlParts['limit'] = $options['limit']; $sqlPartsGlobal['limit'] = $options['limit']; } // init GLOBALS if (!is_null($options['globalmacro'])) { $sqlPartsGlobal = $this->applyQueryFilterOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); $sqlPartsGlobal = $this->applyQueryOutputOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); $res = DBselect(self::createSelectQueryFromParts($sqlPartsGlobal), $sqlPartsGlobal['limit']); while ($macro = DBfetch($res)) { if ($options['countOutput']) { $result = $macro['rowscount']; } else { $result[$macro['globalmacroid']] = $macro; } } } // init HOSTS else { $sqlParts = $this->applyQueryFilterOptions('hostmacro', 'hm', $options, $sqlParts); $sqlParts = $this->applyQueryOutputOptions('hostmacro', 'hm', $options, $sqlParts); $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); while ($macro = DBfetch($res)) { if ($options['countOutput']) { $result = $macro['rowscount']; } else { $result[$macro['hostmacroid']] = $macro; } } } if ($options['countOutput']) { return $result; } if ($result) { $result = $this->addRelatedObjects($options, $result); $result = $this->unsetExtraFields($result, ['hostid', 'type'], $options['output']); } // removing keys (hash -> array) if (!$options['preservekeys']) { $result = zbx_cleanHashes($result); } return $result; } /** * @param array $globalmacros * * @return array */ public function createGlobal(array $globalmacros) { $this->validateCreateGlobal($globalmacros); $globalmacroids = DB::insertBatch('globalmacro', $globalmacros); foreach ($globalmacros as $index => &$globalmacro) { $globalmacro['globalmacroid'] = $globalmacroids[$index]; } unset($globalmacro); $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MACRO, $globalmacros); return ['globalmacroids' => $globalmacroids]; } /** * @param array $globalmacros * * @throws APIException if the input is invalid. */ private function validateCreateGlobal(array &$globalmacros) { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); } $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', '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_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'value')], ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('globalmacro', 'value')] ]], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] ]]; if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $this->checkDuplicates($globalmacros); } /** * @param array $globalmacros * * @return array */ public function updateGlobal(array $globalmacros) { $this->validateUpdateGlobal($globalmacros, $db_globalmacros); $upd_globalmacros = []; foreach ($globalmacros as $globalmacro) { $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; $upd_globalmacro = DB::getUpdatedValues('globalmacro', $globalmacro, $db_globalmacro); if ($upd_globalmacro) { $upd_globalmacros[] = [ 'values'=> $upd_globalmacro, 'where'=> ['globalmacroid' => $globalmacro['globalmacroid']] ]; } } if ($upd_globalmacros) { DB::update('globalmacro', $upd_globalmacros); } $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MACRO, $globalmacros, $db_globalmacros); return ['globalmacroids' => array_column($globalmacros, 'globalmacroid')]; } /** * @param array $globalmacros * @param array $db_globalmacros * * @throws APIException if the input is invalid */ private function validateUpdateGlobal(array &$globalmacros, array &$db_globalmacros = null) { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); } $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['globalmacroid'], ['macro']], 'fields' => [ 'globalmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('globalmacro', '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('globalmacro', 'value')], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] ]]; if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_globalmacros = DB::select('globalmacro', [ 'output' => ['globalmacroid', 'macro', 'value', 'description', 'type'], 'globalmacroids' => array_column($globalmacros, 'globalmacroid'), 'preservekeys' => true ]); if (count($globalmacros) != count($db_globalmacros)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } $globalmacros = $this->extendObjectsByKey($globalmacros, $db_globalmacros, 'globalmacroid', ['type']); foreach ($globalmacros as $index => &$globalmacro) { $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; if ($globalmacro['type'] != $db_globalmacro['type']) { if ($db_globalmacro['type'] == ZBX_MACRO_TYPE_SECRET) { $globalmacro += ['value' => '']; } if ($globalmacro['type'] == ZBX_MACRO_TYPE_VAULT) { $globalmacro += ['value' => $db_globalmacro['value']]; } } if (array_key_exists('value', $globalmacro) && $globalmacro['type'] == ZBX_MACRO_TYPE_VAULT) { if (!CApiInputValidator::validate(['type' => API_VAULT_SECRET], $globalmacro['value'], '/'.($index + 1).'/value', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } } } unset($globalmacro); $this->checkDuplicates($globalmacros, $db_globalmacros); } /** * Check for duplicated macros. * * @param array $globalmacros * @param string $globalmacros[]['globalmacroid'] (optional if $db_globalmacros is null) * @param string $globalmacros[]['macro'] (optional if $db_globalmacros is not null) * @param array|null $db_globalmacros * * @throws APIException if macros already exists. */ private function checkDuplicates(array $globalmacros, array $db_globalmacros = null): void { $macros = []; foreach ($globalmacros as $globalmacro) { if ($db_globalmacros === null || (array_key_exists('macro', $globalmacro) && CApiInputValidator::trimMacro($globalmacro['macro']) !== CApiInputValidator::trimMacro($db_globalmacros[$globalmacro['globalmacroid']]['macro']))) { $macros[] = $globalmacro['macro']; } } if (!$macros) { return; } $db_globalmacros = DB::select('globalmacro', [ 'output' => ['macro'] ]); $db_macros = []; foreach ($db_globalmacros as $db_globalmacro) { $db_macros[CApiInputValidator::trimMacro($db_globalmacro['macro'])] = true; } foreach ($macros as $macro) { if (array_key_exists(CApiInputValidator::trimMacro($macro), $db_macros)) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists.', $macro)); } } } /** * @param array $globalmacroids * * @return array */ public function deleteGlobal(array $globalmacroids) { $this->validateDeleteGlobal($globalmacroids, $db_globalmacros); DB::delete('globalmacro', ['globalmacroid' => $globalmacroids]); $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MACRO, $db_globalmacros); return ['globalmacroids' => $globalmacroids]; } /** * @param array $globalmacroids * * @throws APIException if the input is invalid. */ private function validateDeleteGlobal(array &$globalmacroids, array &$db_globalmacros = null) { if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); } $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; if (!CApiInputValidator::validate($api_input_rules, $globalmacroids, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_globalmacros = DB::select('globalmacro', [ 'output' => ['globalmacroid', 'macro'], 'globalmacroids' => $globalmacroids, 'preservekeys' => true ]); if (count($globalmacroids) != count($db_globalmacros)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } } /** * @param array $hostmacros * * @throws APIException if the input is invalid. */ protected function validateCreate(array &$hostmacros) { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostid', 'macro']], 'fields' => [ 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED], '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_TEXT, ZBX_MACRO_TYPE_SECRET])], 'type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'value')], ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_MACRO_TYPE_VAULT])], 'type' => API_VAULT_SECRET, 'length' => DB::getFieldLength('hostmacro', 'value')] ]], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('hostmacro', 'description')] ]]; if (!CApiInputValidator::validate($api_input_rules, $hostmacros, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $this->checkHostPermissions(array_unique(array_column($hostmacros, 'hostid'))); $this->checkHostDuplicates($hostmacros); } /** * @param array $hostmacros * * @return array */ public function create(array $hostmacros) { $this->validateCreate($hostmacros); $this->createReal($hostmacros); if ($tpl_hostmacros = $this->getMacrosToInherit($hostmacros)) { $this->inherit($tpl_hostmacros); } return ['hostmacroids' => array_column($hostmacros, 'hostmacroid')]; } /** * Inserts hostmacros records into the database. * * @param array $hostmacros */ private function createReal(array &$hostmacros): void { $hostmacroids = DB::insert('hostmacro', $hostmacros); foreach ($hostmacros as $index => &$hostmacro) { $hostmacro['hostmacroid'] = $hostmacroids[$index]; } unset($hostmacro); } /** * @param array $hostmacros * * @throws APIException if the input is invalid. */ protected function validateUpdate(array &$hostmacros, array &$db_hostmacros = null) { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['hostmacroid']], 'fields' => [ 'hostmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], '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, $hostmacros, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_hostmacros = $this->get([ 'output' => ['hostmacroid', 'hostid', 'macro', 'type', 'description'], 'hostmacroids' => array_column($hostmacros, 'hostmacroid'), 'editable' => true, 'inherited' => false, 'preservekeys' => true ]); if (count($hostmacros) != count($db_hostmacros)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } // CUserMacro::get does not return secret values. Loading directly from the database. $options = [ 'output' => ['hostmacroid', 'value'], 'hostmacroids' => array_keys($db_hostmacros) ]; $db_hostmacro_values = DBselect(DB::makeSql('hostmacro', $options)); while ($db_hostmacro_value = DBfetch($db_hostmacro_values)) { $db_hostmacros[$db_hostmacro_value['hostmacroid']] += $db_hostmacro_value; } $hostmacros = $this->extendObjectsByKey($hostmacros, $db_hostmacros, 'hostmacroid', ['hostid', 'type']); foreach ($hostmacros as $index => &$hostmacro) { $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; if ($hostmacro['type'] != $db_hostmacro['type']) { if ($db_hostmacro['type'] == ZBX_MACRO_TYPE_SECRET) { $hostmacro += ['value' => '']; } if ($hostmacro['type'] == ZBX_MACRO_TYPE_VAULT) { $hostmacro += ['value' => $db_hostmacro['value']]; } } if (array_key_exists('value', $hostmacro) && $hostmacro['type'] == ZBX_MACRO_TYPE_VAULT) { if (!CApiInputValidator::validate(['type' => API_VAULT_SECRET], $hostmacro['value'], '/'.($index + 1).'/value', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } } } unset($hostmacro); $api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'macro']], 'fields' => [ 'hostid' => ['type' => API_ID], 'macro' => ['type' => API_USER_MACRO] ]]; if (!CApiInputValidator::validateUniqueness($api_input_rules, $hostmacros, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $this->checkHostDuplicates($hostmacros, $db_hostmacros); } /** * Checks if any of the given host macros already exist on the corresponding hosts. If the macros are updated and * the "hostmacroid" field is set, the method will only fail, if a macro with a different hostmacroid exists. * Assumes the "macro", "hostid" and "hostmacroid" fields are valid. * * @param array $hostmacros * @param string $hostmacros[]['hostmacroid'] (optional if $db_hostmacros is null) * @param string $hostmacros[]['hostid'] * @param string $hostmacros[]['macro'] (optional if $db_hostmacros is not null) * @param array|null $db_hostmacros * * @throws APIException if any of the given macros already exist. */ private function checkHostDuplicates(array $hostmacros, array $db_hostmacros = null) { $macro_names = []; $existing_macros = []; // Parse each macro, get unique names and, if context exists, narrow down the search. foreach ($hostmacros as $index => $hostmacro) { if ($db_hostmacros !== null && (!array_key_exists('macro', $hostmacro) || CApiInputValidator::trimMacro($hostmacro['macro']) === CApiInputValidator::trimMacro($db_hostmacros[$hostmacro['hostmacroid']]['macro']))) { unset($hostmacros[$index]); continue; } $trimmed_macro = CApiInputValidator::trimMacro($hostmacro['macro']); [$macro_name] = explode(':', $trimmed_macro, 2); $macro_name = !isset($trimmed_macro[strlen($macro_name)]) ? '{$'.$macro_name : '{$'.$macro_name.':'; $macro_names[$macro_name] = true; $existing_macros[$hostmacro['hostid']] = []; } if (!$existing_macros) { return; } $options = [ 'output' => ['hostmacroid', 'hostid', 'macro'], 'filter' => ['hostid' => array_keys($existing_macros)], 'search' => ['macro' => array_keys($macro_names)], 'searchByAny' => true, 'startSearch' => true ]; $db_hostmacros = DBselect(DB::makeSql('hostmacro', $options)); // Collect existing unique macro names and their contexts for each host. while ($db_hostmacro = DBfetch($db_hostmacros)) { $trimmed_macro = CApiInputValidator::trimMacro($db_hostmacro['macro']); $existing_macros[$db_hostmacro['hostid']][$trimmed_macro] = $db_hostmacro['hostmacroid']; } // Compare each macro name and context to existing one. foreach ($hostmacros as $hostmacro) { $hostid = $hostmacro['hostid']; $trimmed_macro = CApiInputValidator::trimMacro($hostmacro['macro']); if (array_key_exists($trimmed_macro, $existing_macros[$hostid])) { $hosts = DB::select('hosts', [ 'output' => ['name'], 'hostids' => $hostid ]); self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists on "%2$s".', $hostmacro['macro'], $hosts[0]['name']) ); } } } /** * Update host macros. * * @param array $hostmacros an array of host macros * * @return array */ public function update($hostmacros) { $this->validateUpdate($hostmacros, $db_hostmacros); $this->updateReal($hostmacros, $db_hostmacros); if ($tpl_hostmacros = $this->getMacrosToInherit($hostmacros, $db_hostmacros)) { $this->inherit($tpl_hostmacros); } return ['hostmacroids' => array_column($hostmacros, 'hostmacroid')]; } /** * Updates hostmacros records in the database. * * @param array $hostmacros * @param array $db_hostmacros */ private function updateReal(array $hostmacros, array $db_hostmacros) { $upd_hostmacros = []; foreach ($hostmacros as $hostmacro) { $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; $upd_hostmacro = DB::getUpdatedValues('hostmacro', $hostmacro, $db_hostmacro); if ($upd_hostmacro) { $upd_hostmacros[] = [ 'values' => $upd_hostmacro, 'where' => ['hostmacroid' => $hostmacro['hostmacroid']] ]; } } if ($upd_hostmacros) { DB::update('hostmacro', $upd_hostmacros); } } /** * @param array $hostmacroids * @param array $db_hostmacros * * @throws APIException if the input is invalid. */ protected function validateDelete(array &$hostmacroids, array &$db_hostmacros = null) { $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; if (!CApiInputValidator::validate($api_input_rules, $hostmacroids, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_hostmacros = $this->get([ 'output' => ['hostmacroid', 'hostid', 'macro'], 'hostmacroids' => $hostmacroids, 'editable' => true, 'inherited' => false, 'preservekeys' => true ]); if (count($hostmacroids) != count($db_hostmacros)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } } /** * Remove macros from hosts. * * @param array $hostmacroids * * @return array */ public function delete(array $hostmacroids) { $this->validateDelete($hostmacroids, $db_hostmacros); DB::delete('hostmacro', ['hostmacroid' => $hostmacroids]); if ($tpl_hostmacros = $this->getMacrosToInherit($db_hostmacros)) { $this->inherit($tpl_hostmacros, true); } return ['hostmacroids' => $hostmacroids]; } /** * 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) { $count = API::Host()->get([ 'countOutput' => true, 'hostids' => $hostids, 'filter' => [ 'flags' => ZBX_FLAG_DISCOVERY_NORMAL ], 'editable' => true ]); if ($count == count($hostids)) { return; } $count += API::Template()->get([ 'countOutput' => true, 'templateids' => $hostids, 'editable' => true ]); if ($count == count($hostids)) { return; } $count += API::HostPrototype()->get([ 'countOutput' => true, 'hostids' => $hostids, 'editable' => true, 'inherited' => false ]); if ($count != count($hostids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } } /** * Forms the array of hostmacros, which are support the inheritance, from the passed hostmacros array. * * @param array $hostmacros * @param string $hostmacros[]['hostmacroid'] * @param string $hostmacros[]['hostid'] * @param string $hostmacros[]['macro'] (optional) * @param string $hostmacros[]['value'] (optional) * @param string $hostmacros[]['description'] (optional) * @param int $hostmacros[]['type'] (optional) * @param array|null $db_hostmacros Used to set the old macro name in case when it was * updated. * @param string $db_hostmacros[]['macro'] * * @return array */ private function getMacrosToInherit(array $hostmacros, array $db_hostmacros = null): array { $templated_host_prototypeids = DBfetchColumn(DBselect( 'SELECT hd.hostid'. ' FROM host_discovery hd,items i,hosts h'. ' WHERE hd.parent_itemid=i.itemid'. ' AND i.hostid=h.hostid'. ' AND h.status='.HOST_STATUS_TEMPLATE. ' AND '.dbConditionId('hd.hostid', array_unique(array_column($hostmacros, 'hostid'))) ), 'hostid'); if (!$templated_host_prototypeids) { return []; } foreach ($hostmacros as $index => &$hostmacro) { if (!in_array($hostmacro['hostid'], $templated_host_prototypeids)) { unset($hostmacros[$index]); continue; } if ($db_hostmacros) { $db_hostmacro = $db_hostmacros[$hostmacro['hostmacroid']]; $hostmacro += array_intersect_key($db_hostmacro, array_flip(['macro'])); if ($hostmacro['macro'] !== $db_hostmacro['macro']) { $hostmacro['macro_old'] = $db_hostmacro['macro']; } } } unset($hostmacro); return $hostmacros; } /** * Prepares and returns an array of child hostmacros, inherited from hostmacros $tpl_hostmacros on the all hosts. * * @param array $tpl_hostmacros * @param string $tpl_hostmacros[]['hostmacroid'] * @param string $tpl_hostmacros[]['hostid'] * @param string $tpl_hostmacros[]['macro'] * @param string $tpl_hostmacros[]['value'] (optional) * @param string $tpl_hostmacros[]['description'] (optional) * @param int $tpl_hostmacros[]['type'] (optional) * @param string $tpl_hostmacros[]['macro_old'] (optional) * @param array $ins_hostmacros * @param array $upd_hostmacros * @param array $db_hostmacros */ private function prepareInheritedObjects(array $tpl_hostmacros, array &$ins_hostmacros = null, array &$upd_hostmacros = null, array &$db_hostmacros = null): void { $ins_hostmacros = []; $upd_hostmacros = []; $db_hostmacros = []; $templateids_hostids = []; $hostids = []; $options = [ 'output' => ['hostid', 'templateid'], 'filter' => ['templateid' => array_unique(array_column($tpl_hostmacros, 'hostid'))] ]; $chd_hosts = DBselect(DB::makeSql('hosts', $options)); while ($chd_host = DBfetch($chd_hosts)) { $templateids_hostids[$chd_host['templateid']][] = $chd_host['hostid']; $hostids[] = $chd_host['hostid']; } if (!$templateids_hostids) { return; } $macros = []; foreach ($tpl_hostmacros as $tpl_hostmacro) { if (array_key_exists('macro_old', $tpl_hostmacro)) { $macros[$tpl_hostmacro['macro_old']] = true; } else { $macros[$tpl_hostmacro['macro']] = true; } } $chd_hostmacros = DB::select('hostmacro', [ 'output' => ['hostmacroid', 'hostid', 'macro', 'type', 'value', 'description'], 'filter' => ['hostid' => $hostids, 'macro' => array_keys($macros)], 'preservekeys' => true ]); $host_macros = array_fill_keys($hostids, []); foreach ($chd_hostmacros as $hostmacroid => $hostmacro) { $host_macros[$hostmacro['hostid']][$hostmacro['macro']] = $hostmacroid; } foreach ($tpl_hostmacros as $tpl_hostmacro) { $templateid = $tpl_hostmacro['hostid']; if (!array_key_exists($templateid, $templateids_hostids)) { continue; } foreach ($templateids_hostids[$templateid] as $hostid) { if (array_key_exists('macro_old', $tpl_hostmacro)) { $hostmacroid = $host_macros[$hostid][$tpl_hostmacro['macro_old']]; $upd_hostmacros[] = ['hostmacroid' => $hostmacroid, 'hostid' => $hostid] + $tpl_hostmacro; $db_hostmacros[$hostmacroid] = $chd_hostmacros[$hostmacroid]; unset($chd_hostmacros[$hostmacroid], $host_macros[$hostid][$tpl_hostmacro['macro_old']]); } elseif (array_key_exists($tpl_hostmacro['macro'], $host_macros[$hostid])) { $hostmacroid = $host_macros[$hostid][$tpl_hostmacro['macro']]; $upd_hostmacros[] = ['hostmacroid' => $hostmacroid, 'hostid' => $hostid] + $tpl_hostmacro; $db_hostmacros[$hostmacroid] = $chd_hostmacros[$hostmacroid]; unset($chd_hostmacros[$hostmacroid], $host_macros[$hostid][$tpl_hostmacro['macro']]); } else { $ins_hostmacros[] = ['hostid' => $hostid] + $tpl_hostmacro; } } } } /** * Updates the macros for the children of host prototypes and propagates the inheritance to the child host * prototypes. * * @param array $tpl_hostmacros * @param string $tpl_hostmacros[]['hostmacroid'] * @param string $tpl_hostmacros[]['hostid'] * @param string $tpl_hostmacros[]['macro'] * @param string $tpl_hostmacros[]['value'] (optional) * @param string $tpl_hostmacros[]['description'] (optional) * @param int $tpl_hostmacros[]['type'] (optional) * @param string $tpl_hostmacros[]['macro_old'] (optional) * @param bool $is_delete Whether the passed hostmacros are intended to delete. */ private function inherit(array $tpl_hostmacros, bool $is_delete = false): void { $this->prepareInheritedObjects($tpl_hostmacros, $ins_hostmacros, $upd_hostmacros, $db_hostmacros); if ($ins_hostmacros) { $this->createReal($ins_hostmacros); } if ($upd_hostmacros) { if ($is_delete) { DB::delete('hostmacro', ['hostmacroid' => array_column($upd_hostmacros, 'hostmacroid')]); } else { $this->updateReal($upd_hostmacros, $db_hostmacros); } } if ($ins_hostmacros || $upd_hostmacros) { $this->inherit(array_merge($ins_hostmacros, $upd_hostmacros), $is_delete); } } protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { // Added type to query because it required to check macro is secret or not. if (!$this->outputIsRequested('type', $options['output'])) { $options['output'][] = 'type'; } $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); if ($options['output'] != API_OUTPUT_COUNT && $options['globalmacro'] === null) { if ($options['selectGroups'] !== null || $options['selectHosts'] !== null || $options['selectTemplates'] !== null) { $sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts); } } return $sqlParts; } /** * @inheritdoc */ protected function applyQueryFilterOptions($table, $alias, array $options, $sql_parts) { if (is_array($options['search'])) { // Do not allow to search by value for macro of type ZBX_MACRO_TYPE_SECRET. if (array_key_exists('value', $options['search'])) { $sql_parts['where']['search'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; zbx_db_search($table.' '.$alias, [ 'searchByAny' => false, 'search' => ['value' => $options['search']['value']] ] + $options, $sql_parts ); unset($options['search']['value']); } if ($options['search']) { zbx_db_search($table.' '.$alias, $options, $sql_parts); } } if (is_array($options['filter'])) { // Do not allow to filter by value for macro of type ZBX_MACRO_TYPE_SECRET. if (array_key_exists('value', $options['filter'])) { $sql_parts['where']['filter'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; $this->dbFilter($table.' '.$alias, [ 'searchByAny' => false, 'filter' => ['value' => $options['filter']['value']] ] + $options, $sql_parts ); unset($options['filter']['value']); } if ($options['filter']) { $this->dbFilter($table.' '.$alias, $options, $sql_parts); } } return $sql_parts; } protected function addRelatedObjects(array $options, array $result) { $result = parent::addRelatedObjects($options, $result); if ($options['globalmacro'] === null) { $hostMacroIds = array_keys($result); /* * Adding objects */ // adding groups if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { $res = DBselect( 'SELECT hm.hostmacroid,hg.groupid'. ' FROM hostmacro hm,hosts_groups hg'. ' WHERE '.dbConditionInt('hm.hostmacroid', $hostMacroIds). ' AND hm.hostid=hg.hostid' ); $relationMap = new CRelationMap(); while ($relation = DBfetch($res)) { $relationMap->addRelation($relation['hostmacroid'], $relation['groupid']); } $groups = API::HostGroup()->get([ 'output' => $options['selectGroups'], 'groupids' => $relationMap->getRelatedIds(), 'preservekeys' => true ]); $result = $relationMap->mapMany($result, $groups, 'groups'); } // adding templates if ($options['selectTemplates'] !== null && $options['selectTemplates'] != API_OUTPUT_COUNT) { $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); $templates = API::Template()->get([ 'output' => $options['selectTemplates'], 'templateids' => $relationMap->getRelatedIds(), 'preservekeys' => true ]); $result = $relationMap->mapMany($result, $templates, 'templates'); } // adding templates if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); $templates = API::Host()->get([ 'output' => $options['selectHosts'], 'hostids' => $relationMap->getRelatedIds(), 'preservekeys' => true ]); $result = $relationMap->mapMany($result, $templates, 'hosts'); } } return $result; } protected function unsetExtraFields(array $objects, array $fields, $output) { foreach ($objects as &$object) { if ($object['type'] == ZBX_MACRO_TYPE_SECRET) { unset($object['value']); } } unset($object); return parent::unsetExtraFields($objects, $fields, $output); } }