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.
2264 lines
70 KiB
2264 lines
70 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.
|
|
**/
|
|
|
|
|
|
abstract class CHostBase extends CApiService {
|
|
|
|
public const ACCESS_RULES = [
|
|
'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
|
|
'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
|
|
'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
|
|
'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
|
|
];
|
|
|
|
protected $tableName = 'hosts';
|
|
protected $tableAlias = 'h';
|
|
|
|
/**
|
|
* Check for valid templates.
|
|
*
|
|
* @param array $hosts
|
|
* @param array|null $db_hosts
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
protected function checkTemplates(array $hosts, array $db_hosts = null): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$edit_templates = [];
|
|
|
|
foreach ($hosts as $i1 => $host) {
|
|
if (array_key_exists('templates', $host) && array_key_exists('templates_clear', $host)) {
|
|
$path_clear = '/'.($i1 + 1).'/templates_clear';
|
|
$path = '/'.($i1 + 1).'/templates';
|
|
|
|
foreach ($host['templates_clear'] as $i2_clear => $template_clear) {
|
|
foreach ($host['templates'] as $i2 => $template) {
|
|
if (bccomp($template['templateid'], $template_clear['templateid']) == 0) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
|
|
$path_clear.'/'.($i2_clear + 1).'/templateid',
|
|
_s('cannot be specified the value of parameter "%1$s"',
|
|
$path.'/'.($i2 + 1).'/templateid'
|
|
)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('templates', $host)) {
|
|
$templates = array_column($host['templates'], null, 'templateid');
|
|
|
|
if ($db_hosts === null) {
|
|
$edit_templates += $templates;
|
|
}
|
|
else {
|
|
$db_templates = array_column($db_hosts[$host[$id_field_name]]['templates'], null, 'templateid');
|
|
|
|
$ins_templates = array_diff_key($templates, $db_templates);
|
|
$del_templates = array_diff_key($db_templates, $templates);
|
|
|
|
$edit_templates += $ins_templates + $del_templates;
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('templates_clear', $host)) {
|
|
$edit_templates += array_column($host['templates_clear'], null, 'templateid');
|
|
}
|
|
}
|
|
|
|
if (!$edit_templates) {
|
|
return;
|
|
}
|
|
|
|
$count = API::Template()->get([
|
|
'countOutput' => true,
|
|
'templateids' => array_keys($edit_templates)
|
|
]);
|
|
|
|
if ($count != count($edit_templates)) {
|
|
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
|
|
}
|
|
|
|
foreach ($hosts as $i1 => $host) {
|
|
if (!array_key_exists('templates_clear', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$db_templates = array_column($db_hosts[$host[$id_field_name]]['templates'], null, 'templateid');
|
|
$path = '/'.($i1 + 1).'/templates_clear';
|
|
|
|
foreach ($host['templates_clear'] as $i2 => $template) {
|
|
if (!array_key_exists($template['templateid'], $db_templates)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
|
|
$path.'/'.($i2 + 1).'/templateid', _('cannot be unlinked')
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check templates links.
|
|
*
|
|
* @param array $hosts
|
|
* @param array|null $db_hosts
|
|
*/
|
|
protected function checkTemplatesLinks(array $hosts, array $db_hosts = null): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_templates = [];
|
|
$del_links = [];
|
|
$is_template_update = $this instanceof CTemplate && $db_hosts !== null;
|
|
$double_linkage_scope = $is_template_update ? null : [];
|
|
$del_templates = [];
|
|
$del_links_clear = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (array_key_exists('templates', $host)) {
|
|
$db_templates = ($db_hosts !== null)
|
|
? array_column($db_hosts[$host[$id_field_name]]['templates'], null, 'templateid')
|
|
: [];
|
|
$templateids = array_column($host['templates'], 'templateid');
|
|
$templates_count = count($host['templates']);
|
|
$upd_templateids = [];
|
|
|
|
if ($db_hosts !== null
|
|
&& array_key_exists('nopermissions_templates', $db_hosts[$host[$id_field_name]])) {
|
|
foreach ($db_hosts[$host[$id_field_name]]['nopermissions_templates'] as $db_template) {
|
|
$templateids[] = $db_template['templateid'];
|
|
$templates_count++;
|
|
$upd_templateids[] = $db_template['templateid'];
|
|
}
|
|
}
|
|
|
|
foreach ($host['templates'] as $template) {
|
|
if (array_key_exists($template['templateid'], $db_templates)) {
|
|
$upd_templateids[] = $template['templateid'];
|
|
unset($db_templates[$template['templateid']]);
|
|
}
|
|
else {
|
|
$ins_templates[$template['templateid']][$host[$id_field_name]] = $templateids;
|
|
|
|
if (!$is_template_update && $templates_count > 1) {
|
|
$double_linkage_scope[$template['templateid']][$host[$id_field_name]] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($db_templates as $db_template) {
|
|
$del_links[$db_template['templateid']][$host[$id_field_name]] = true;
|
|
|
|
if (($this instanceof CHost || $this instanceof CTemplate) && $upd_templateids) {
|
|
$del_templates[$db_template['templateid']][$host[$id_field_name]] = $upd_templateids;
|
|
}
|
|
}
|
|
}
|
|
elseif (array_key_exists('templates_clear', $host)) {
|
|
$templateids = array_column($host['templates_clear'], 'templateid');
|
|
$upd_templateids = [];
|
|
|
|
foreach ($db_hosts[$host[$id_field_name]]['templates'] as $db_template) {
|
|
if (!in_array($db_template['templateid'], $templateids)) {
|
|
$upd_templateids[] = $db_template['templateid'];
|
|
}
|
|
}
|
|
|
|
foreach ($host['templates_clear'] as $template) {
|
|
$del_links[$template['templateid']][$host[$id_field_name]] = true;
|
|
|
|
if (($this instanceof CHost || $this instanceof CTemplate) && $upd_templateids) {
|
|
$del_templates[$template['templateid']][$host[$id_field_name]] = $upd_templateids;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (($this instanceof CHost || $this instanceof CTemplate) && array_key_exists('templates_clear', $host)) {
|
|
foreach ($host['templates_clear'] as $template) {
|
|
$del_links_clear[$template['templateid']][$host[$id_field_name]] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($del_templates) {
|
|
$this->checkTriggerExpressionsOfDelTemplates($del_templates);
|
|
}
|
|
|
|
if ($del_links_clear) {
|
|
$this->checkTriggerDependenciesOfHostTriggers($del_links_clear);
|
|
}
|
|
|
|
if ($ins_templates) {
|
|
if ($this instanceof CTemplate && $db_hosts !== null) {
|
|
self::checkCircularLinkageNew($ins_templates, $del_links);
|
|
}
|
|
|
|
if ($is_template_update || $double_linkage_scope) {
|
|
$this->checkDoubleLinkageNew($ins_templates, $del_links, $double_linkage_scope);
|
|
}
|
|
|
|
$this->checkTriggerDependenciesOfInsTemplates($ins_templates);
|
|
$this->checkTriggerExpressionsOfInsTemplates($ins_templates);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether all templates of triggers of unlinking templates are unlinked from target hosts or templates.
|
|
*
|
|
* @param array $del_templates
|
|
* @param array $del_templates[<templateid>][<hostid>] Array of IDs of existing templates.
|
|
*
|
|
* @throws APIException if not linked template is found.
|
|
*/
|
|
protected function checkTriggerExpressionsOfDelTemplates(array $del_templates): void {
|
|
$result = DBselect(
|
|
'SELECT DISTINCT i.hostid AS del_templateid,f.triggerid,ii.hostid'.
|
|
' FROM items i,functions f,functions ff,items ii'.
|
|
' WHERE i.itemid=f.itemid'.
|
|
' AND f.triggerid=ff.triggerid'.
|
|
' AND ff.itemid=ii.itemid'.
|
|
' AND '.dbConditionId('i.hostid', array_keys($del_templates))
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($del_templates[$row['del_templateid']] as $hostid => $upd_templateids) {
|
|
if (in_array($row['hostid'], $upd_templateids)) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status'],
|
|
'hostids' => [$row['del_templateid'], $row['hostid'], $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid']
|
|
]);
|
|
|
|
$error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
|
|
? _('Cannot unlink template "%1$s" without template "%2$s" from template "%3$s" due to expression of trigger "%4$s".')
|
|
: _('Cannot unlink template "%1$s" without template "%2$s" from host "%3$s" due to expression of trigger "%4$s".');
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$row['del_templateid']]['host'],
|
|
$objects[$row['hostid']]['host'], $objects[$hostid]['host'], $triggers[0]['description']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether the triggers of the target hosts or templates don't have a dependencies on the triggers of the
|
|
* unlinking (with cleaning) templates.
|
|
*
|
|
* @param array $del_links_clear[<templateid>][<hostid>]
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
protected function checkTriggerDependenciesOfHostTriggers(array $del_links_clear): void {
|
|
$del_host_templates = [];
|
|
|
|
foreach ($del_links_clear as $templateid => $hosts) {
|
|
foreach ($hosts as $hostid => $foo) {
|
|
$del_host_templates[$hostid][] = $templateid;
|
|
}
|
|
}
|
|
|
|
$result = DBselect(
|
|
'SELECT DISTINCT i.hostid AS templateid,t.triggerid,ii.hostid'.
|
|
' FROM items i,functions f,triggers t,functions ff,items ii'.
|
|
' WHERE i.itemid=f.itemid'.
|
|
' AND f.triggerid=t.templateid'.
|
|
' AND t.triggerid=ff.triggerid'.
|
|
' AND ff.itemid=ii.itemid'.
|
|
' AND '.dbConditionId('i.hostid', array_keys($del_links_clear)).
|
|
' AND '.dbConditionId('ii.hostid', array_keys($del_host_templates))
|
|
);
|
|
|
|
$trigger_links = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (in_array($row['templateid'], $del_host_templates[$row['hostid']])) {
|
|
$trigger_links[$row['triggerid']][$row['hostid']] = $row['templateid'];
|
|
}
|
|
}
|
|
|
|
if (!$trigger_links) {
|
|
return;
|
|
}
|
|
|
|
$result = DBselect(
|
|
'SELECT DISTINCT td.triggerid_up,td.triggerid_down,i.hostid'.
|
|
' FROM trigger_depends td,functions f,items i'.
|
|
' WHERE td.triggerid_down=f.triggerid'.
|
|
' AND f.itemid=i.itemid'.
|
|
' AND '.dbConditionId('td.triggerid_up', array_keys($trigger_links)).
|
|
' AND '.dbConditionId('td.triggerid_down', array_keys($trigger_links), true).
|
|
' AND '.dbConditionId('i.hostid', array_keys($del_host_templates))
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($trigger_links[$row['triggerid_up']] as $hostid => $templateid) {
|
|
if (bccomp($row['hostid'], $hostid) == 0) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status'],
|
|
'hostids' => [$templateid, $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid_down']
|
|
]);
|
|
|
|
$error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
|
|
? _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s".')
|
|
: _('Cannot unlink template "%1$s" from host "%2$s" due to dependency of trigger "%3$s".');
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$templateid]['host'],
|
|
$objects[$hostid]['host'], $triggers[0]['description']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this instanceof CTemplate) {
|
|
$trigger_hosts = [];
|
|
|
|
foreach ($trigger_links as $triggerid => $hostids) {
|
|
$trigger_hosts[$triggerid] = array_keys($hostids);
|
|
}
|
|
|
|
$trigger_map = [];
|
|
|
|
while (true) {
|
|
$result = DBselect(
|
|
'SELECT DISTINCT t.templateid,t.triggerid,i.hostid'.
|
|
' FROM triggers t,functions f,items i'.
|
|
' WHERE t.triggerid=f.triggerid'.
|
|
' AND f.itemid=i.itemid'.
|
|
' AND '.dbConditionId('t.templateid', array_keys($trigger_hosts))
|
|
);
|
|
|
|
$_trigger_hosts = [];
|
|
$hostids = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($trigger_hosts[$row['templateid']] as $hostid) {
|
|
if (array_key_exists($row['hostid'], $del_host_templates)
|
|
&& in_array($hostid, $del_host_templates[$row['hostid']])) {
|
|
continue;
|
|
}
|
|
|
|
$trigger_map[$row['triggerid']] = $row['templateid'];
|
|
$_trigger_hosts[$row['triggerid']][] = $row['hostid'];
|
|
$hostids[$row['hostid']] = true;
|
|
}
|
|
}
|
|
|
|
if (!$_trigger_hosts) {
|
|
break;
|
|
}
|
|
|
|
$trigger_hosts = $_trigger_hosts;
|
|
|
|
$result = DBselect(
|
|
'SELECT DISTINCT td.triggerid_up,td.triggerid_down,i.hostid'.
|
|
' FROM trigger_depends td,functions f,items i'.
|
|
' WHERE td.triggerid_down=f.triggerid'.
|
|
' AND f.itemid=i.itemid'.
|
|
' AND '.dbConditionId('td.triggerid_up', array_keys($trigger_hosts)).
|
|
' AND '.dbConditionId('td.triggerid_down', array_keys($trigger_hosts), true).
|
|
' AND '.dbConditionId('i.hostid', array_keys($hostids))
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($trigger_hosts[$row['triggerid_up']] as $hostid) {
|
|
if (bccomp($row['hostid'], $hostid) == 0) {
|
|
$triggerid = $row['triggerid_up'];
|
|
|
|
do {
|
|
$triggerid = $trigger_map[$triggerid];
|
|
} while (array_key_exists($triggerid, $trigger_map));
|
|
|
|
$from_hostid = key($trigger_links[$triggerid]);
|
|
$templateid = $trigger_links[$triggerid][$from_hostid];
|
|
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status'],
|
|
'hostids' => [$templateid, $from_hostid, $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid_down']
|
|
]);
|
|
|
|
$error = ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE)
|
|
? _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s" on template "%4$s".')
|
|
: _('Cannot unlink template "%1$s" from template "%2$s" due to dependency of trigger "%3$s" on host "%4$s".');
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$templateid]['host'],
|
|
$objects[$from_hostid]['host'], $triggers[0]['description'], $objects[$hostid]['host']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether circular linkage occurs as a result of the given changes in templates links.
|
|
*
|
|
* @param array $ins_links[<templateid>][<hostid>]
|
|
* @param array $del_links[<templateid>][<hostid>]
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
protected static function checkCircularLinkageNew(array $ins_links, array $del_links): void {
|
|
$links = [];
|
|
$_hostids = $ins_links;
|
|
|
|
do {
|
|
$result = DBselect(
|
|
'SELECT ht.templateid,ht.hostid'.
|
|
' FROM hosts_templates ht'.
|
|
' WHERE '.dbConditionId('ht.hostid', array_keys($_hostids))
|
|
);
|
|
|
|
$_hostids = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (array_key_exists($row['templateid'], $del_links)
|
|
&& array_key_exists($row['hostid'], $del_links[$row['templateid']])) {
|
|
continue;
|
|
}
|
|
|
|
if (!array_key_exists($row['templateid'], $links)) {
|
|
$_hostids[$row['templateid']] = true;
|
|
}
|
|
|
|
$links[$row['templateid']][$row['hostid']] = true;
|
|
}
|
|
}
|
|
while ($_hostids);
|
|
|
|
foreach ($ins_links as $templateid => $hostids) {
|
|
if (array_key_exists($templateid, $links)) {
|
|
$links[$templateid] += $hostids;
|
|
}
|
|
else {
|
|
$links[$templateid] = $ins_links[$templateid];
|
|
}
|
|
}
|
|
|
|
foreach ($ins_links as $templateid => $hostids) {
|
|
foreach ($hostids as $hostid => $foo) {
|
|
if (array_key_exists($hostid, $links)){
|
|
$links_path = [$hostid => true];
|
|
|
|
if (self::circularLinkageExists($links, $templateid, $links[$hostid], $links_path)) {
|
|
$template_name = '';
|
|
|
|
$templates = DB::select('hosts', [
|
|
'output' => ['hostid', 'host'],
|
|
'hostids' => array_keys($links_path + [$templateid => true]),
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
foreach ($templates as $template) {
|
|
$description = '"'.$template['host'].'"';
|
|
|
|
if (bccomp($template['hostid'], $templateid) == 0) {
|
|
$template_name = $description;
|
|
}
|
|
else {
|
|
$links_path[$template['hostid']] = $description;
|
|
}
|
|
}
|
|
|
|
$circular_linkage = (bccomp($templateid, $hostid) == 0)
|
|
? $template_name.' -> '.$template_name
|
|
: $template_name.' -> '.implode(' -> ', $links_path).' -> '.$template_name;
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
|
|
'Cannot link template "%1$s" to template "%2$s", because a circular linkage (%3$s) would occur.',
|
|
$templates[$templateid]['host'], $templates[$hostid]['host'], $circular_linkage
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively check whether given template to link forms a circular linkage.
|
|
*
|
|
* @param array $links[<templateid>][<hostid>]
|
|
* @param string $templateid
|
|
* @param array $hostids[<hostid>]
|
|
* @param array $links_path Circular linkage path, collected performing the check.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private static function circularLinkageExists(array $links, string $templateid, array $hostids,
|
|
array &$links_path): bool {
|
|
if (array_key_exists($templateid, $hostids)) {
|
|
return true;
|
|
}
|
|
|
|
$_links_path = $links_path;
|
|
|
|
foreach ($hostids as $hostid => $foo) {
|
|
if (array_key_exists($hostid, $links)) {
|
|
$links_path = $_links_path;
|
|
$hostid_links = array_diff_key($links[$hostid], $links_path);
|
|
|
|
if ($hostid_links) {
|
|
$links_path[$hostid] = true;
|
|
|
|
if (self::circularLinkageExists($links, $templateid, $hostid_links, $links_path)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether double linkage occurs as a result of the given changes in template links.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Array of template IDs to replace on target object.
|
|
* @param array $del_links[<templateid>][<hostid>]
|
|
* @param array|null $scope[<templateid>][<hostid>] The scope of template links to perform the double
|
|
* linkage check for. If null, all of $ins_templates
|
|
* links will be checked.
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
protected static function checkDoubleLinkageNew(array $ins_templates, array $del_links, ?array $scope): void {
|
|
$ins_hosts = self::getInsHosts($ins_templates, $scope);
|
|
$scoped_ins_templates = self::getScopedInsTemplates($ins_templates, $scope, $db_templates);
|
|
|
|
$targetids = $scoped_ins_templates + $db_templates;
|
|
|
|
if ($scope === null) {
|
|
$children = self::getChildren($ins_hosts + $ins_templates, $del_links, $ins_templates);
|
|
|
|
$targetids += $ins_hosts + self::getTemplateOrTargetRelatedIds($children, $ins_hosts);
|
|
}
|
|
|
|
$parents = self::getParents($targetids, $del_links, $ins_hosts);
|
|
|
|
self::checkParentsOfDbTemplatesLinkedTwice($db_templates, $parents);
|
|
self::checkParentsOfInsTemplatesLinkedTwice($scoped_ins_templates, $parents);
|
|
|
|
if ($scope === null) {
|
|
self::addInsHostsParentsAndChildren($ins_hosts, $parents, $children);
|
|
$children_parents = self::getChildrenParents($children, $parents);
|
|
|
|
self::checkInsTemplatesLinkedTwiceOnTargetChildren($ins_hosts, $children_parents);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an array indexed by targets of the given $ins_templates and their templates. If the given scope is partial,
|
|
* returns null.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Array of template IDs to replace on target object.
|
|
* @param array|null $scope[<templateid>][<hostid>] The scope of template links to perform the double
|
|
* linkage check for.
|
|
*
|
|
* @return array|null
|
|
*/
|
|
private static function getInsHosts(array $ins_templates, ?array $scope): ?array {
|
|
if ($scope !== null) {
|
|
return null;
|
|
}
|
|
|
|
$ins_hosts = [];
|
|
|
|
foreach ($ins_templates as $templateid => $host_templates) {
|
|
foreach ($host_templates as $hostid => $foo) {
|
|
$ins_hosts[$hostid][$templateid] = [];
|
|
}
|
|
}
|
|
|
|
return $ins_hosts;
|
|
}
|
|
|
|
/**
|
|
* Get an array of template links from the given $ins_templates to check for double linkage.
|
|
* The same target object will be referenced to a common array of template IDs to replace (to be updated later).
|
|
* Skip template links out of the given scope.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Array of template IDs to replace on target object.
|
|
* @param array|null $scope[<templateid>][<hostid>] The scope of template links to perform the double
|
|
* linkage check for. If null, all of $ins_templates
|
|
* links will be processed.
|
|
* @param array $db_templates
|
|
* @param array $db_templates[<templateid>][<hostid>] Reference to a common array of template IDs to replace.
|
|
*
|
|
* @return array|null
|
|
*/
|
|
private static function getScopedInsTemplates(array $ins_templates, ?array $scope,
|
|
array &$db_templates = null): array {
|
|
$scoped_ins_templates = [];
|
|
$db_templates = [];
|
|
|
|
foreach ($ins_templates as $templateid => $host_templates) {
|
|
if ($scope !== null && !array_key_exists($templateid, $scope)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($host_templates as $hostid => &$templateids) {
|
|
if (($scope !== null && !array_key_exists($hostid, $scope[$templateid]))
|
|
|| (array_key_exists($templateid, $scoped_ins_templates)
|
|
&& array_key_exists($hostid, $scoped_ins_templates[$templateid]))) {
|
|
continue;
|
|
}
|
|
|
|
$scoped_ins_templates[$templateid][$hostid] = &$templateids;
|
|
|
|
foreach ($templateids as $_templateid) {
|
|
if (bccomp($_templateid, $templateid) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (array_key_exists($_templateid, $ins_templates)
|
|
&& array_key_exists($hostid, $ins_templates[$_templateid])) {
|
|
$scoped_ins_templates[$_templateid][$hostid] = &$templateids;
|
|
}
|
|
else {
|
|
$db_templates[$_templateid][$hostid] = &$templateids;
|
|
}
|
|
}
|
|
}
|
|
unset($templateids);
|
|
}
|
|
|
|
return $scoped_ins_templates;
|
|
}
|
|
|
|
/**
|
|
* Recursively get children of the given template IDs.
|
|
*
|
|
* @param array $templateids[<templateid>]
|
|
* @param array $del_links
|
|
* @param array|null $ins_templates
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function getChildren(array $templateids, array $del_links, ?array $ins_templates): array {
|
|
$processed_templateids = $templateids;
|
|
$children = [];
|
|
|
|
do {
|
|
$links = DB::select('hosts_templates', [
|
|
'output' => ['templateid', 'hostid'],
|
|
'filter' => ['templateid' => array_keys($templateids)]
|
|
]);
|
|
|
|
if ($ins_templates !== null) {
|
|
foreach (array_intersect_key($ins_templates, $templateids) as $templateid => $hostids) {
|
|
foreach ($hostids as $hostid => $foo) {
|
|
$links[] = ['templateid' => $templateid, 'hostid' => $hostid];
|
|
}
|
|
}
|
|
}
|
|
|
|
$templateids = [];
|
|
|
|
foreach ($links as $link) {
|
|
if ($ins_templates !== null) {
|
|
if (array_key_exists($link['templateid'], $del_links)
|
|
&& array_key_exists($link['hostid'], $del_links[$link['templateid']])) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!array_key_exists($link['hostid'], $processed_templateids)) {
|
|
$templateids[$link['hostid']] = true;
|
|
$processed_templateids[$link['hostid']] = true;
|
|
}
|
|
|
|
$children[$link['templateid']][] = $link['hostid'];
|
|
}
|
|
} while ($templateids);
|
|
|
|
return $children;
|
|
}
|
|
|
|
/**
|
|
* Recursively get parents of the given target IDs.
|
|
*
|
|
* @param array $targetids[<targetid>]
|
|
* @param array $del_links
|
|
* @param array|null $ins_hosts
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function getParents(array $targetids, array $del_links, ?array $ins_hosts): array {
|
|
$processed_targetids = $targetids;
|
|
$parents = [];
|
|
|
|
do {
|
|
$links = DB::select('hosts_templates', [
|
|
'output' => ['templateid', 'hostid'],
|
|
'filter' => ['hostid' => array_keys($targetids)]
|
|
]);
|
|
|
|
if ($ins_hosts !== null) {
|
|
foreach (array_intersect_key($ins_hosts, $targetids) as $hostid => $templateids) {
|
|
foreach ($templateids as $templateid => $foo) {
|
|
$links[] = ['templateid' => $templateid, 'hostid' => $hostid];
|
|
}
|
|
}
|
|
}
|
|
|
|
$targetids = [];
|
|
|
|
foreach ($links as $link) {
|
|
if ($ins_hosts !== null) {
|
|
if (array_key_exists($link['templateid'], $del_links)
|
|
&& array_key_exists($link['hostid'], $del_links[$link['templateid']])) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!array_key_exists($link['templateid'], $processed_targetids)) {
|
|
$targetids[$link['templateid']] = true;
|
|
$processed_targetids[$link['templateid']] = true;
|
|
}
|
|
|
|
$parents[$link['hostid']][] = $link['templateid'];
|
|
}
|
|
} while ($targetids);
|
|
|
|
return $parents;
|
|
}
|
|
|
|
/**
|
|
* Check whether parents of already linked templates would be linked twice to target hosts or templates through new
|
|
* template linkage.
|
|
* Populate the referenced arrays of target object template IDs with the parents of the given $db_templates.
|
|
*
|
|
* @param array $db_templates
|
|
* @param array $parents
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
private static function checkParentsOfDbTemplatesLinkedTwice(array $db_templates, array $parents): void {
|
|
$_templateids = $db_templates;
|
|
$children = [];
|
|
|
|
do {
|
|
$links = array_intersect_key($parents, $_templateids);
|
|
|
|
$_templateids = [];
|
|
|
|
foreach ($links as $link_templateid => $link_parent_templateids) {
|
|
$db_templateids = self::getRootTemplateIds([$link_templateid => true], $children);
|
|
|
|
foreach ($db_templateids as $templateid => $foo) {
|
|
foreach ($db_templates[$templateid] as $hostid => &$templateids) {
|
|
$double_templateids = array_intersect($link_parent_templateids, $templateids);
|
|
|
|
if ($double_templateids) {
|
|
$double_templateid = reset($double_templateids);
|
|
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$double_templateid, $hostid, $templateid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because it would be linked twice through template "%3$s".');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" to host prototype "%2$s", because it would be linked twice through template "%3$s".');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" to host "%2$s", because it would be linked twice through template "%3$s".');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
|
|
$objects[$double_templateid]['host'], $objects[$hostid]['host'],
|
|
$objects[$templateid]['host']
|
|
));
|
|
}
|
|
|
|
$templateids = array_merge($templateids, $link_parent_templateids);
|
|
}
|
|
unset($templateids);
|
|
}
|
|
|
|
foreach ($link_parent_templateids as $link_parent_templateid) {
|
|
if (!array_key_exists($link_parent_templateid, $children)) {
|
|
$_templateids[$link_parent_templateid] = true;
|
|
}
|
|
|
|
$children[$link_parent_templateid][] = $link_templateid;
|
|
}
|
|
}
|
|
} while ($_templateids);
|
|
}
|
|
|
|
/**
|
|
* Check whether parents of templates to link would be linked twice to target hosts or templates.
|
|
* Populate the referenced arrays of target object template IDs with the parents of the given $ins_templates.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Referenced array of target object template IDs.
|
|
* @param array $parents
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
private static function checkParentsOfInsTemplatesLinkedTwice(array $ins_templates, array $parents): void {
|
|
$_templateids = $ins_templates;
|
|
$children = [];
|
|
|
|
do {
|
|
$links = array_intersect_key($parents, $_templateids);
|
|
|
|
$_templateids = [];
|
|
|
|
foreach ($links as $link_templateid => $link_parent_templateids) {
|
|
$ins_templateids = self::getRootTemplateIds([$link_templateid => true], $children);
|
|
|
|
foreach ($ins_templateids as $ins_templateid => $foo) {
|
|
foreach ($ins_templates[$ins_templateid] as $hostid => &$templateids) {
|
|
$double_templateids = array_intersect($link_parent_templateids, $templateids);
|
|
|
|
if ($double_templateids) {
|
|
$double_templateid = reset($double_templateids);
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$ins_templateid, $hostid, $double_templateid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because its parent template "%3$s" would be linked twice.');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" to host prototype "%2$s", because its parent template "%3$s" would be linked twice.');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" to host "%2$s", because its parent template "%3$s" would be linked twice.');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$ins_templateid]['host'],
|
|
$objects[$hostid]['host'], $objects[$double_templateid]['host']
|
|
));
|
|
}
|
|
else {
|
|
$templateids = array_merge($templateids, $link_parent_templateids);
|
|
}
|
|
}
|
|
unset($templateids);
|
|
}
|
|
|
|
foreach ($link_parent_templateids as $link_parent_templateid) {
|
|
if (!array_key_exists($link_parent_templateid, $children)) {
|
|
$_templateids[$link_parent_templateid] = true;
|
|
}
|
|
|
|
$children[$link_parent_templateid][] = $link_templateid;
|
|
}
|
|
}
|
|
} while ($_templateids);
|
|
}
|
|
|
|
/**
|
|
* Add the parent and children relations of each template to link to the given $ins_hosts.
|
|
*
|
|
* @param array $ins_hosts
|
|
* @param array $parents
|
|
* @param array $children
|
|
*/
|
|
private static function addInsHostsParentsAndChildren(array &$ins_hosts, array $parents, array $children): void {
|
|
foreach ($ins_hosts as &$template_data) {
|
|
foreach ($template_data as $templateid => &$data) {
|
|
$data['parents'] = self::getTemplateOrTargetRelatedIds($parents, [$templateid => true]);
|
|
$data['children'] = self::getTemplateOrTargetRelatedIds($children, [$templateid => true]);
|
|
}
|
|
unset($data);
|
|
}
|
|
unset($template_data);
|
|
}
|
|
|
|
/**
|
|
* Get the direct parents of each given children template.
|
|
*
|
|
* @param array $children
|
|
* @param array $parents
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function getChildrenParents(array $children, array $parents): array {
|
|
$children_parents = [];
|
|
|
|
foreach ($children as $templateid => $targetids) {
|
|
foreach ($targetids as $targetid) {
|
|
$children_parents[$templateid][$targetid] =
|
|
self::getTemplateOrTargetRelatedIds($parents, [$targetid => true], $templateid);
|
|
}
|
|
}
|
|
|
|
return $children_parents;
|
|
}
|
|
|
|
/**
|
|
* Check whether templates to link, its parents, or children are encountered between parents of target templates'
|
|
* children.
|
|
*
|
|
* @param array $ins_hosts
|
|
* @param array $children_templates
|
|
*
|
|
* @throws APIException
|
|
*/
|
|
private static function checkInsTemplatesLinkedTwiceOnTargetChildren(array $ins_hosts,
|
|
array $children_parents): void {
|
|
$_templateids = $ins_hosts;
|
|
$_parents = [];
|
|
|
|
do {
|
|
$links = array_intersect_key($children_parents, $_templateids);
|
|
|
|
$_templateids = [];
|
|
|
|
foreach ($links as $link_templateid => $host_parent_templates) {
|
|
$ins_hostids = self::getRootTemplateIds([$link_templateid => true], $_parents);
|
|
|
|
foreach ($ins_hostids as $ins_hostid => $foo) {
|
|
foreach ($ins_hosts[$ins_hostid] as $templateid => $data) {
|
|
foreach ($host_parent_templates as $hostid => $parent_templateids) {
|
|
if (array_key_exists($templateid, $parent_templateids)
|
|
|| array_intersect_key($data['children'], $parent_templateids)) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$templateid, $ins_hostid, $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because it would be linked to template "%3$s" twice.');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because it would be linked to host prototype "%3$s" twice.');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because it would be linked to host "%3$s" twice.');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
|
|
$objects[$templateid]['host'], $objects[$ins_hostid]['host'],
|
|
$objects[$hostid]['host']
|
|
));
|
|
}
|
|
|
|
$double_templateids = array_intersect_key($data['parents'], $parent_templateids);
|
|
|
|
if ($double_templateids) {
|
|
$double_templateid = key($double_templateids);
|
|
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$templateid, $ins_hostid, $double_templateid, $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because its parent template "%3$s" would be linked to template "%4$s" twice.');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because its parent template "%3$s" would be linked to host prototype "%4$s" twice.');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" to template "%2$s", because its parent template "%3$s" would be linked to host "%4$s" twice.');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error,
|
|
$objects[$templateid]['host'], $objects[$ins_hostid]['host'],
|
|
$objects[$double_templateid]['host'], $objects[$hostid]['host']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($host_parent_templates as $hostid => $foo) {
|
|
if (!array_key_exists($hostid, $_parents)) {
|
|
$_templateids[$hostid] = true;
|
|
$_parents[$hostid][] = $link_templateid;
|
|
}
|
|
}
|
|
}
|
|
} while ($_templateids);
|
|
}
|
|
|
|
/**
|
|
* Get IDs of targets linked to given templates or IDs of templates linked to given targets.
|
|
*
|
|
* @param array $links
|
|
* @param array $sourceids
|
|
* @param string|null $ignore_relatedid
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function getTemplateOrTargetRelatedIds(array $links, array $sourceids,
|
|
string $ignore_relatedid = null): array {
|
|
$processed_sourceids = $sourceids;
|
|
$relatedids = [];
|
|
|
|
do {
|
|
$scoped_links = array_intersect_key($links, $sourceids);
|
|
|
|
$sourceids = [];
|
|
|
|
foreach ($scoped_links as $_relatedids) {
|
|
foreach ($_relatedids as $relatedid) {
|
|
if ($ignore_relatedid !== null && bccomp($relatedid, $ignore_relatedid) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (!array_key_exists($relatedid, $processed_sourceids)) {
|
|
$sourceids[$relatedid] = true;
|
|
$processed_sourceids[$relatedid] = true;
|
|
}
|
|
|
|
$relatedids[$relatedid] = true;
|
|
}
|
|
}
|
|
|
|
$ignore_relatedid = null;
|
|
} while ($sourceids);
|
|
|
|
return $relatedids;
|
|
}
|
|
|
|
/**
|
|
* Recursively collects the roots of the given children or parent templates.
|
|
*
|
|
* @param array $templateids
|
|
* @param array $template_links
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function getRootTemplateIds(array $templateids, array $template_links): array {
|
|
$root_templateids = $templateids;
|
|
|
|
foreach ($templateids as $templateid => $foo) {
|
|
if (array_key_exists($templateid, $template_links)) {
|
|
unset($root_templateids[$templateid]);
|
|
|
|
$root_templateids +=
|
|
self::getRootTemplateIds(array_flip($template_links[$templateid]), $template_links);
|
|
}
|
|
}
|
|
|
|
return $root_templateids;
|
|
}
|
|
|
|
/**
|
|
* Check whether all templates of triggers, from which depends the triggers of linking templates, are linked to
|
|
* target hosts or templates.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Array of template IDs to replace on target object.
|
|
*
|
|
* @throws APIException if not linked template is found.
|
|
*/
|
|
protected function checkTriggerDependenciesOfInsTemplates(array $ins_templates): void {
|
|
$result = DBselect(
|
|
'SELECT DISTINCT i.hostid AS ins_templateid,td.triggerid_down,ii.hostid'.
|
|
' FROM items i,functions f,trigger_depends td,functions ff,items ii,hosts h'.
|
|
' WHERE i.itemid=f.itemid'.
|
|
' AND f.triggerid=td.triggerid_down'.
|
|
' AND td.triggerid_up=ff.triggerid'.
|
|
' AND ff.itemid=ii.itemid'.
|
|
' AND ii.hostid=h.hostid'.
|
|
' AND '.dbConditionId('i.hostid', array_keys($ins_templates)).
|
|
' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE])
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($ins_templates[$row['ins_templateid']] as $hostid => $templateids) {
|
|
if (bccomp($row['hostid'], $hostid) == 0 && $this instanceof CTemplate) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host'],
|
|
'hostids' => [$row['ins_templateid'], $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid_down']
|
|
]);
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Cannot link template "%1$s" to template "%2$s" due to dependency of trigger "%3$s".',
|
|
$objects[$row['ins_templateid']]['host'], $objects[$hostid]['host'],
|
|
$triggers[0]['description']
|
|
)
|
|
);
|
|
}
|
|
|
|
if (!in_array($row['hostid'], $templateids)) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$row['ins_templateid'], $row['hostid'], $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid_down']
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to template "%3$s" due to dependency of trigger "%4$s".');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to host prototype "%3$s" due to dependency of trigger "%4$s".');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to host "%3$s" due to dependency of trigger "%4$s".');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$row['ins_templateid']]['host'],
|
|
$objects[$row['hostid']]['host'], $objects[$hostid]['host'], $triggers[0]['description']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this instanceof CTemplate) {
|
|
$hostids = [];
|
|
|
|
foreach ($ins_templates as $hostids_templateids) {
|
|
foreach ($hostids_templateids as $hostid => $templateids) {
|
|
$hostids[$hostid] = true;
|
|
}
|
|
}
|
|
|
|
$result = DBselect(
|
|
'SELECT DISTINCT i.hostid AS ins_templateid,td.triggerid_down,ii.hostid'.
|
|
' FROM items i,functions f,trigger_depends td,functions ff,items ii'.
|
|
' WHERE i.itemid=f.itemid'.
|
|
' AND f.triggerid=td.triggerid_up'.
|
|
' AND td.triggerid_down=ff.triggerid'.
|
|
' AND ff.itemid=ii.itemid'.
|
|
' AND '.dbConditionId('i.hostid', array_keys($ins_templates)).
|
|
' AND '.dbConditionId('ii.hostid', array_keys($hostids))
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (array_key_exists($row['hostid'], $ins_templates[$row['ins_templateid']])) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host'],
|
|
'hostids' => [$row['ins_templateid'], $row['hostid']],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid_down']
|
|
]);
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Cannot link template "%1$s" to template "%2$s" due to dependency of trigger "%3$s".',
|
|
$objects[$row['ins_templateid']]['host'], $objects[$row['hostid']]['host'],
|
|
$triggers[0]['description']
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether all templates of triggers of linking templates are linked to target hosts or templates.
|
|
*
|
|
* @param array $ins_templates
|
|
* @param array $ins_templates[<templateid>][<hostid>] Array of template IDs to replace on target object.
|
|
*
|
|
* @throws APIException if not linked template is found.
|
|
*/
|
|
protected function checkTriggerExpressionsOfInsTemplates(array $ins_templates): void {
|
|
$result = DBselect(
|
|
'SELECT DISTINCT i.hostid AS ins_templateid,f.triggerid,ii.hostid'.
|
|
' FROM items i,functions f,functions ff,items ii'.
|
|
' WHERE i.itemid=f.itemid'.
|
|
' AND f.triggerid=ff.triggerid'.
|
|
' AND ff.itemid=ii.itemid'.
|
|
' AND '.dbConditionId('i.hostid', array_keys($ins_templates))
|
|
);
|
|
|
|
while ($row = DBfetch($result)) {
|
|
foreach ($ins_templates[$row['ins_templateid']] as $hostid => $templateids) {
|
|
if (bccomp($row['hostid'], $hostid) == 0 && $this instanceof CTemplate) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host'],
|
|
'hostids' => [$row['ins_templateid'], $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid']
|
|
]);
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Cannot link template "%1$s" to template "%2$s" due to expression of trigger "%3$s".',
|
|
$objects[$row['ins_templateid']]['host'], $objects[$hostid]['host'],
|
|
$triggers[0]['description']
|
|
)
|
|
);
|
|
}
|
|
|
|
if (!in_array($row['hostid'], $templateids)) {
|
|
$objects = DB::select('hosts', [
|
|
'output' => ['host', 'status', 'flags'],
|
|
'hostids' => [$row['ins_templateid'], $row['hostid'], $hostid],
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
$triggers = DB::select('triggers', [
|
|
'output' => ['description'],
|
|
'triggerids' => $row['triggerid']
|
|
]);
|
|
|
|
if ($objects[$hostid]['status'] == HOST_STATUS_TEMPLATE) {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to template "%3$s" due to expression of trigger "%4$s".');
|
|
}
|
|
elseif ($objects[$hostid]['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to host prototype "%3$s" due to expression of trigger "%4$s".');
|
|
}
|
|
else {
|
|
$error = _('Cannot link template "%1$s" without template "%2$s" to host "%3$s" due to expression of trigger "%4$s".');
|
|
}
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, sprintf($error, $objects[$row['ins_templateid']]['host'],
|
|
$objects[$row['hostid']]['host'], $objects[$hostid]['host'], $triggers[0]['description']
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update table "hosts_templates".
|
|
*
|
|
* @param array $hosts
|
|
* @param array|null $db_hosts
|
|
* @param array|null $upd_hostids
|
|
*/
|
|
protected function updateTemplates(array &$hosts, array &$db_hosts = null, array &$upd_hostids = null): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_hosts_templates = [];
|
|
$del_hosttemplateids = [];
|
|
|
|
foreach ($hosts as $i => &$host) {
|
|
if (!array_key_exists('templates', $host) && !array_key_exists('templates_clear', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$db_templates = ($db_hosts !== null)
|
|
? array_column($db_hosts[$host[$id_field_name]]['templates'], null, 'templateid')
|
|
: [];
|
|
$changed = false;
|
|
|
|
if (array_key_exists('templates', $host)) {
|
|
foreach ($host['templates'] as &$template) {
|
|
if (array_key_exists($template['templateid'], $db_templates)) {
|
|
$template['hosttemplateid'] = $db_templates[$template['templateid']]['hosttemplateid'];
|
|
unset($db_templates[$template['templateid']]);
|
|
}
|
|
else {
|
|
$ins_hosts_templates[] = [
|
|
'hostid' => $host[$id_field_name],
|
|
'templateid' => $template['templateid']
|
|
];
|
|
$changed = true;
|
|
}
|
|
}
|
|
unset($template);
|
|
|
|
$templates_clear_indexes = [];
|
|
|
|
if (array_key_exists('templates_clear', $host)) {
|
|
foreach ($host['templates_clear'] as $index => $template) {
|
|
$templates_clear_indexes[$template['templateid']] = $index;
|
|
}
|
|
}
|
|
|
|
foreach ($db_templates as $del_template) {
|
|
$changed = true;
|
|
$del_hosttemplateids[] = $del_template['hosttemplateid'];
|
|
|
|
if (array_key_exists($del_template['templateid'], $templates_clear_indexes)) {
|
|
$index = $templates_clear_indexes[$del_template['templateid']];
|
|
$host['templates_clear'][$index]['hosttemplateid'] = $del_template['hosttemplateid'];
|
|
}
|
|
}
|
|
}
|
|
elseif (array_key_exists('templates_clear', $host)) {
|
|
foreach ($host['templates_clear'] as &$template) {
|
|
$template['hosttemplateid'] = $db_templates[$template['templateid']]['hosttemplateid'];
|
|
$del_hosttemplateids[] = $db_templates[$template['templateid']]['hosttemplateid'];
|
|
}
|
|
unset($template);
|
|
}
|
|
|
|
if ($db_hosts !== null) {
|
|
if ($changed) {
|
|
$upd_hostids[$i] = $host[$id_field_name];
|
|
}
|
|
else {
|
|
unset($host['templates'], $host['templates_clear'], $db_hosts[$host[$id_field_name]]['templates']);
|
|
}
|
|
}
|
|
}
|
|
unset($host);
|
|
|
|
if ($del_hosttemplateids) {
|
|
DB::delete('hosts_templates', ['hosttemplateid' => $del_hosttemplateids]);
|
|
}
|
|
|
|
if ($ins_hosts_templates) {
|
|
$hosttemplateids = DB::insertBatch('hosts_templates', $ins_hosts_templates);
|
|
}
|
|
|
|
foreach ($hosts as &$host) {
|
|
if (!array_key_exists('templates', $host)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($host['templates'] as &$template) {
|
|
if (!array_key_exists('hosttemplateid', $template)) {
|
|
$template['hosttemplateid'] = array_shift($hosttemplateids);
|
|
}
|
|
}
|
|
unset($template);
|
|
}
|
|
unset($host);
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array|null $db_hosts
|
|
* @param array|null $upd_hostids
|
|
*/
|
|
protected function updateTags(array &$hosts, array &$db_hosts = null, array &$upd_hostids = null): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_tags = [];
|
|
$del_hosttagids = [];
|
|
|
|
foreach ($hosts as $i => &$host) {
|
|
if (!array_key_exists('tags', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$changed = false;
|
|
$db_tags = ($db_hosts !== null) ? $db_hosts[$host[$id_field_name]]['tags'] : [];
|
|
|
|
$hosttagid_by_tag_value = [];
|
|
|
|
foreach ($db_tags as $db_tag) {
|
|
$hosttagid_by_tag_value[$db_tag['tag']][$db_tag['value']] = $db_tag['hosttagid'];
|
|
}
|
|
|
|
foreach ($host['tags'] as &$tag) {
|
|
if (array_key_exists($tag['tag'], $hosttagid_by_tag_value)
|
|
&& array_key_exists($tag['value'], $hosttagid_by_tag_value[$tag['tag']])) {
|
|
$tag['hosttagid'] = $hosttagid_by_tag_value[$tag['tag']][$tag['value']];
|
|
unset($db_tags[$tag['hosttagid']]);
|
|
}
|
|
else {
|
|
$ins_tags[] = ['hostid' => $host[$id_field_name]] + $tag;
|
|
$changed = true;
|
|
}
|
|
}
|
|
unset($tag);
|
|
|
|
$db_tags = array_filter($db_tags, static function (array $db_tag): bool {
|
|
return $db_tag['automatic'] == ZBX_TAG_MANUAL;
|
|
});
|
|
|
|
if ($db_tags) {
|
|
$del_hosttagids = array_merge($del_hosttagids, array_keys($db_tags));
|
|
$changed = true;
|
|
}
|
|
|
|
if ($db_hosts !== null) {
|
|
if ($changed) {
|
|
$upd_hostids[$i] = $host[$id_field_name];
|
|
}
|
|
else {
|
|
unset($host['tags'], $db_hosts[$host[$id_field_name]]['tags']);
|
|
}
|
|
}
|
|
}
|
|
unset($host);
|
|
|
|
if ($del_hosttagids) {
|
|
DB::delete('host_tag', ['hosttagid' => $del_hosttagids]);
|
|
}
|
|
|
|
if ($ins_tags) {
|
|
$hosttagids = DB::insert('host_tag', $ins_tags);
|
|
}
|
|
|
|
foreach ($hosts as &$host) {
|
|
if (!array_key_exists('tags', $host)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($host['tags'] as &$tag) {
|
|
if (!array_key_exists('hosttagid', $tag)) {
|
|
$tag['hosttagid'] = array_shift($hosttagids);
|
|
}
|
|
}
|
|
unset($tag);
|
|
}
|
|
unset($host);
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array|null $db_hosts
|
|
* @param array|null $upd_hostids
|
|
*/
|
|
protected function updateMacros(array &$hosts, array &$db_hosts = null, array &$upd_hostids = null): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_hostmacros = [];
|
|
$upd_hostmacros = [];
|
|
$del_hostmacroids = [];
|
|
|
|
foreach ($hosts as $i => &$host) {
|
|
if (!array_key_exists('macros', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$changed = false;
|
|
$db_macros = ($db_hosts !== null) ? $db_hosts[$host[$id_field_name]]['macros'] : [];
|
|
|
|
foreach ($host['macros'] as &$macro) {
|
|
if (array_key_exists('hostmacroid', $macro) && $db_macros) {
|
|
$upd_hostmacro = DB::getUpdatedValues('hostmacro', $macro, $db_macros[$macro['hostmacroid']]);
|
|
|
|
if ($upd_hostmacro) {
|
|
$upd_hostmacros[] = [
|
|
'values' => $upd_hostmacro,
|
|
'where' => ['hostmacroid' => $macro['hostmacroid']]
|
|
];
|
|
$changed = true;
|
|
}
|
|
|
|
unset($db_macros[$macro['hostmacroid']]);
|
|
}
|
|
else {
|
|
$ins_hostmacros[] = ['hostid' => $host[$id_field_name]] + $macro;
|
|
$changed = true;
|
|
}
|
|
}
|
|
unset($macro);
|
|
|
|
if ($db_macros) {
|
|
$del_hostmacroids = array_merge($del_hostmacroids, array_keys($db_macros));
|
|
$changed = true;
|
|
}
|
|
|
|
if ($db_hosts !== null) {
|
|
if ($changed) {
|
|
$upd_hostids[$i] = $host[$id_field_name];
|
|
}
|
|
else {
|
|
unset($host['macros'], $db_hosts[$host[$id_field_name]]['macros']);
|
|
}
|
|
}
|
|
}
|
|
unset($host);
|
|
|
|
if ($del_hostmacroids) {
|
|
DB::delete('hostmacro', ['hostmacroid' => $del_hostmacroids]);
|
|
}
|
|
|
|
if ($upd_hostmacros) {
|
|
DB::update('hostmacro', $upd_hostmacros);
|
|
}
|
|
|
|
if ($ins_hostmacros) {
|
|
$hostmacroids = DB::insert('hostmacro', $ins_hostmacros);
|
|
}
|
|
|
|
foreach ($hosts as &$host) {
|
|
if (!array_key_exists('macros', $host)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($host['macros'] as &$macro) {
|
|
if (!array_key_exists('hostmacroid', $macro)) {
|
|
$macro['hostmacroid'] = array_shift($hostmacroids);
|
|
}
|
|
}
|
|
unset($macro);
|
|
}
|
|
unset($host);
|
|
}
|
|
|
|
/**
|
|
* Links the templates to the given hosts.
|
|
*
|
|
* @param array $templateIds
|
|
* @param array $targetIds an array of host IDs to link the templates to
|
|
*
|
|
* @return array an array of added hosts_templates rows, with 'hostid' and 'templateid' set for each row
|
|
*/
|
|
protected function link(array $templateIds, array $targetIds) {
|
|
if (empty($templateIds)) {
|
|
return;
|
|
}
|
|
|
|
// check if someone passed duplicate templates in the same query
|
|
$templateIdDuplicates = zbx_arrayFindDuplicates($templateIds);
|
|
if ($templateIdDuplicates) {
|
|
$duplicatesFound = [];
|
|
foreach ($templateIdDuplicates as $value => $count) {
|
|
$duplicatesFound[] = _s('template ID "%1$s" is passed %2$s times', $value, $count);
|
|
}
|
|
self::exception(
|
|
ZBX_API_ERROR_PARAMETERS,
|
|
_s('Cannot pass duplicate template IDs for the linkage: %1$s.', implode(', ', $duplicatesFound))
|
|
);
|
|
}
|
|
|
|
$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!'));
|
|
}
|
|
// get DB templates which exists in all targets
|
|
$res = DBselect('SELECT * FROM hosts_templates WHERE '.dbConditionInt('hostid', $targetIds));
|
|
$mas = [];
|
|
while ($row = DBfetch($res)) {
|
|
if (!isset($mas[$row['templateid']])) {
|
|
$mas[$row['templateid']] = [];
|
|
}
|
|
$mas[$row['templateid']][$row['hostid']] = 1;
|
|
}
|
|
$commonDBTemplateIds = [];
|
|
foreach ($mas as $templateId => $targetList) {
|
|
if (count($targetList) == count($targetIds)) {
|
|
$commonDBTemplateIds[] = $templateId;
|
|
}
|
|
}
|
|
|
|
// check if there are any template with triggers which depends on triggers in templates which will be not linked
|
|
$commonTemplateIds = array_unique(array_merge($commonDBTemplateIds, $templateIds));
|
|
foreach ($templateIds as $templateid) {
|
|
$triggerids = [];
|
|
$dbTriggers = get_triggers_by_hostid($templateid);
|
|
while ($trigger = DBfetch($dbTriggers)) {
|
|
$triggerids[$trigger['triggerid']] = $trigger['triggerid'];
|
|
}
|
|
|
|
$sql = 'SELECT DISTINCT h.host'.
|
|
' FROM trigger_depends td,functions f,items i,hosts h'.
|
|
' WHERE ('.
|
|
dbConditionInt('td.triggerid_down', $triggerids).
|
|
' AND f.triggerid=td.triggerid_up'.
|
|
' )'.
|
|
' AND i.itemid=f.itemid'.
|
|
' AND h.hostid=i.hostid'.
|
|
' AND '.dbConditionInt('h.hostid', $commonTemplateIds, true).
|
|
' AND h.status='.HOST_STATUS_TEMPLATE;
|
|
if ($dbDepHost = DBfetch(DBselect($sql))) {
|
|
$tmpTpls = API::Template()->get([
|
|
'output'=> ['host'],
|
|
'templateids' => $templateid
|
|
]);
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Trigger in template "%1$s" has dependency with trigger in template "%2$s".', $tmpTpls[0]['host'], $dbDepHost['host']));
|
|
}
|
|
}
|
|
|
|
$res = DBselect(
|
|
'SELECT ht.hostid,ht.templateid'.
|
|
' FROM hosts_templates ht'.
|
|
' WHERE '.dbConditionInt('ht.hostid', $targetIds).
|
|
' AND '.dbConditionInt('ht.templateid', $templateIds)
|
|
);
|
|
$linked = [];
|
|
while ($row = DBfetch($res)) {
|
|
$linked[$row['templateid']][$row['hostid']] = true;
|
|
}
|
|
|
|
// add template linkages, if problems rollback later
|
|
$hostsLinkageInserts = [];
|
|
|
|
foreach ($templateIds as $templateid) {
|
|
$linked_targets = array_key_exists($templateid, $linked) ? $linked[$templateid] : [];
|
|
|
|
foreach ($targetIds as $targetid) {
|
|
if (array_key_exists($targetid, $linked_targets)) {
|
|
continue;
|
|
}
|
|
|
|
$hostsLinkageInserts[] = ['hostid' => $targetid, 'templateid' => $templateid];
|
|
}
|
|
}
|
|
|
|
if ($hostsLinkageInserts) {
|
|
self::checkCircularLinkage($hostsLinkageInserts);
|
|
self::checkDoubleLinkage($hostsLinkageInserts);
|
|
|
|
$hosttemplateids = DB::insertBatch('hosts_templates', $hostsLinkageInserts);
|
|
|
|
foreach ($hostsLinkageInserts as &$host_linkage) {
|
|
$host_linkage['hosttemplateid'] = array_shift($hosttemplateids);
|
|
}
|
|
unset($host_linkage);
|
|
}
|
|
|
|
// check if all trigger templates are linked to host.
|
|
// we try to find template that is not linked to hosts ($targetids)
|
|
// and exists trigger which reference that template and template from ($templateids)
|
|
$sql = 'SELECT DISTINCT h.host'.
|
|
' FROM functions f,items i,triggers t,hosts h'.
|
|
' WHERE f.itemid=i.itemid'.
|
|
' AND f.triggerid=t.triggerid'.
|
|
' AND i.hostid=h.hostid'.
|
|
' AND h.status='.HOST_STATUS_TEMPLATE.
|
|
' AND NOT EXISTS (SELECT 1 FROM hosts_templates ht WHERE ht.templateid=i.hostid AND '.dbConditionInt('ht.hostid', $targetIds).')'.
|
|
' AND EXISTS (SELECT 1 FROM functions ff,items ii WHERE ff.itemid=ii.itemid AND ff.triggerid=t.triggerid AND '.dbConditionInt('ii.hostid', $templateIds). ')';
|
|
if ($dbNotLinkedTpl = DBfetch(DBSelect($sql, 1))) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Trigger has items from template "%1$s" that is not linked to host.', $dbNotLinkedTpl['host'])
|
|
);
|
|
}
|
|
|
|
return $hostsLinkageInserts;
|
|
}
|
|
|
|
protected function unlink($templateids, $targetids = null) {
|
|
$cond = ['templateid' => $templateids];
|
|
if (!is_null($targetids)) {
|
|
$cond['hostid'] = $targetids;
|
|
}
|
|
DB::delete('hosts_templates', $cond);
|
|
|
|
if (!is_null($targetids)) {
|
|
$hosts = API::Host()->get([
|
|
'hostids' => $targetids,
|
|
'output' => ['hostid', 'host'],
|
|
'nopermissions' => true
|
|
]);
|
|
}
|
|
else {
|
|
$hosts = API::Host()->get([
|
|
'templateids' => $templateids,
|
|
'output' => ['hostid', 'host'],
|
|
'nopermissions' => true
|
|
]);
|
|
}
|
|
|
|
if (!empty($hosts)) {
|
|
$templates = API::Template()->get([
|
|
'templateids' => $templateids,
|
|
'output' => ['hostid', 'host'],
|
|
'nopermissions' => true
|
|
]);
|
|
|
|
$hosts = implode(', ', zbx_objectValues($hosts, 'host'));
|
|
$templates = implode(', ', zbx_objectValues($templates, 'host'));
|
|
|
|
info(_s('Templates "%1$s" unlinked from hosts "%2$s".', $templates, $hosts));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Searches for circular linkages.
|
|
*
|
|
* @param array $host_templates
|
|
* @param string $host_templates[]['templateid']
|
|
* @param string $host_templates[]['hostid']
|
|
*/
|
|
private static function checkCircularLinkage(array $host_templates) {
|
|
$links = [];
|
|
|
|
foreach ($host_templates as $host_template) {
|
|
$links[$host_template['templateid']][$host_template['hostid']] = true;
|
|
}
|
|
|
|
$templateids = array_keys($links);
|
|
$_templateids = $templateids;
|
|
|
|
do {
|
|
$result = DBselect(
|
|
'SELECT ht.templateid,ht.hostid'.
|
|
' FROM hosts_templates ht'.
|
|
' WHERE '.dbConditionId('ht.hostid', $_templateids)
|
|
);
|
|
|
|
$_templateids = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (!array_key_exists($row['templateid'], $links)) {
|
|
$_templateids[$row['templateid']] = true;
|
|
}
|
|
|
|
$links[$row['templateid']][$row['hostid']] = true;
|
|
}
|
|
|
|
$_templateids = array_keys($_templateids);
|
|
}
|
|
while ($_templateids);
|
|
|
|
foreach ($templateids as $templateid) {
|
|
self::checkTemplateCircularLinkage($links, $templateid, $links[$templateid]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Searches for circular linkages for specific template.
|
|
*
|
|
* @param array $links[<templateid>][<hostid>] The list of linkages.
|
|
* @param string $templateid ID of the template to check circular linkages.
|
|
* @param array $hostids[<hostid>]
|
|
*
|
|
* @throws APIException if circular linkage is found.
|
|
*/
|
|
private static function checkTemplateCircularLinkage(array $links, $templateid, array $hostids): void {
|
|
if (array_key_exists($templateid, $hostids)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, _('Circular template linkage is not allowed.'));
|
|
}
|
|
|
|
foreach ($hostids as $hostid => $foo) {
|
|
if (array_key_exists($hostid, $links)) {
|
|
self::checkTemplateCircularLinkage($links, $templateid, $links[$hostid]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Searches for double linkages.
|
|
*
|
|
* @param array $host_templates
|
|
* @param string $host_templates[]['templateid']
|
|
* @param string $host_templates[]['hostid']
|
|
*/
|
|
private static function checkDoubleLinkage(array $host_templates) {
|
|
$links = [];
|
|
$templateids = [];
|
|
$hostids = [];
|
|
|
|
foreach ($host_templates as $host_template) {
|
|
$links[$host_template['hostid']][$host_template['templateid']] = true;
|
|
$templateids[$host_template['templateid']] = true;
|
|
$hostids[$host_template['hostid']] = true;
|
|
}
|
|
|
|
$_hostids = array_keys($hostids);
|
|
|
|
do {
|
|
$result = DBselect(
|
|
'SELECT ht.hostid'.
|
|
' FROM hosts_templates ht'.
|
|
' WHERE '.dbConditionId('ht.templateid', $_hostids)
|
|
);
|
|
|
|
$_hostids = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (!array_key_exists($row['hostid'], $hostids)) {
|
|
$_hostids[$row['hostid']] = true;
|
|
}
|
|
|
|
$hostids[$row['hostid']] = true;
|
|
}
|
|
|
|
$_hostids = array_keys($_hostids);
|
|
}
|
|
while ($_hostids);
|
|
|
|
$_templateids = array_keys($templateids + $hostids);
|
|
$templateids = [];
|
|
|
|
do {
|
|
$result = DBselect(
|
|
'SELECT ht.templateid,ht.hostid'.
|
|
' FROM hosts_templates ht'.
|
|
' WHERE '.dbConditionId('hostid', $_templateids)
|
|
);
|
|
|
|
$_templateids = [];
|
|
|
|
while ($row = DBfetch($result)) {
|
|
if (!array_key_exists($row['templateid'], $templateids)) {
|
|
$_templateids[$row['templateid']] = true;
|
|
}
|
|
|
|
$templateids[$row['templateid']] = true;
|
|
$links[$row['hostid']][$row['templateid']] = true;
|
|
}
|
|
|
|
$_templateids = array_keys($_templateids);
|
|
}
|
|
while ($_templateids);
|
|
|
|
foreach ($hostids as $hostid => $foo) {
|
|
self::checkTemplateDoubleLinkage($links, $hostid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Searches for double linkages.
|
|
*
|
|
* @param array $links[<hostid>][<templateid>] The list of linked template IDs by host ID.
|
|
* @param string $hostid
|
|
*
|
|
* @throws APIException if double linkage is found.
|
|
*
|
|
* @return array An array of the linked templates for the selected host.
|
|
*/
|
|
private static function checkTemplateDoubleLinkage(array $links, $hostid): array {
|
|
$templateids = $links[$hostid];
|
|
|
|
foreach ($links[$hostid] as $templateid => $foo) {
|
|
if (array_key_exists($templateid, $links)) {
|
|
$_templateids = self::checkTemplateDoubleLinkage($links, $templateid);
|
|
|
|
if (array_intersect_key($templateids, $_templateids)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_('Template cannot be linked to another template more than once even through other templates.')
|
|
);
|
|
}
|
|
|
|
$templateids += $_templateids;
|
|
}
|
|
}
|
|
|
|
return $templateids;
|
|
}
|
|
|
|
/**
|
|
* Creates user macros for hosts, templates and host prototypes.
|
|
*
|
|
* @param array $hosts
|
|
* @param array $hosts[]['templateid|hostid']
|
|
* @param array $hosts[]['macros'] (optional)
|
|
*/
|
|
protected function createHostMacros(array $hosts): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_hostmacros = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (array_key_exists('macros', $host)) {
|
|
foreach ($host['macros'] as $macro) {
|
|
$ins_hostmacros[] = ['hostid' => $host[$id_field_name]] + $macro;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($ins_hostmacros) {
|
|
DB::insert('hostmacro', $ins_hostmacros);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adding "macros" to the each host object.
|
|
*
|
|
* @param array $db_hosts
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getHostMacros(array $db_hosts): array {
|
|
foreach ($db_hosts as &$db_host) {
|
|
$db_host['macros'] = [];
|
|
}
|
|
unset($db_host);
|
|
|
|
$options = [
|
|
'output' => ['hostmacroid', 'hostid', 'macro', 'type', 'value', 'description', 'automatic'],
|
|
'filter' => ['hostid' => array_keys($db_hosts)]
|
|
];
|
|
$db_macros = DBselect(DB::makeSql('hostmacro', $options));
|
|
|
|
while ($db_macro = DBfetch($db_macros)) {
|
|
$hostid = $db_macro['hostid'];
|
|
unset($db_macro['hostid']);
|
|
|
|
$db_hosts[$hostid]['macros'][$db_macro['hostmacroid']] = $db_macro;
|
|
}
|
|
|
|
return $db_hosts;
|
|
}
|
|
|
|
/**
|
|
* Checks user macros for host.update, template.update and hostprototype.update methods.
|
|
*
|
|
* @param array $hosts
|
|
* @param array $hosts[]['templateid|hostid']
|
|
* @param array $hosts[]['macros'] (optional)
|
|
* @param array $db_hosts
|
|
* @param array $db_hosts[<hostid>]['macros']
|
|
*
|
|
* @return array Array of passed hosts/templates with padded macros data, when it's necessary.
|
|
*
|
|
* @throws APIException if input of host macros data is invalid.
|
|
*/
|
|
protected function validateHostMacros(array $hosts, array $db_hosts): array {
|
|
$hostmacro_defaults = [
|
|
'type' => DB::getDefault('hostmacro', 'type')
|
|
];
|
|
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
foreach ($hosts as $i1 => &$host) {
|
|
if (!array_key_exists('macros', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$db_host = $db_hosts[$host[$id_field_name]];
|
|
$path = '/'.($i1 + 1).'/macros';
|
|
|
|
$db_macros = array_column($db_host['macros'], 'hostmacroid', 'macro');
|
|
$macros = [];
|
|
|
|
foreach ($host['macros'] as $i2 => &$hostmacro) {
|
|
if (!array_key_exists('hostmacroid', $hostmacro)) {
|
|
foreach (['macro', 'value'] as $field_name) {
|
|
if (!array_key_exists($field_name, $hostmacro)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
|
|
$path.'/'.($i2 + 1), _s('the parameter "%1$s" is missing', $field_name)
|
|
));
|
|
}
|
|
}
|
|
|
|
$hostmacro += $hostmacro_defaults;
|
|
}
|
|
else {
|
|
if (!array_key_exists($hostmacro['hostmacroid'], $db_host['macros'])) {
|
|
self::exception(ZBX_API_ERROR_PERMISSIONS,
|
|
_('No permissions to referred object or it does not exist!')
|
|
);
|
|
}
|
|
|
|
$db_hostmacro = $db_host['macros'][$hostmacro['hostmacroid']];
|
|
|
|
// Check if this is not an attempt to modify automatic host macro.
|
|
if ($this instanceof CHost) {
|
|
$macro_fields = array_flip(['macro', 'value', 'type', 'description']);
|
|
$hostmacro += array_intersect_key($db_hostmacro, array_flip(['automatic']));
|
|
|
|
if ($hostmacro['automatic'] == ZBX_USERMACRO_AUTOMATIC
|
|
&& array_diff_assoc(array_intersect_key($hostmacro, $macro_fields), $db_hostmacro)) {
|
|
self::exception(ZBX_API_ERROR_PERMISSIONS,
|
|
_s('Not allowed to modify automatic user macro "%1$s".', $db_hostmacro['macro'])
|
|
);
|
|
}
|
|
}
|
|
|
|
$hostmacro += array_intersect_key($db_hostmacro, array_flip(['macro', 'type']));
|
|
|
|
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']];
|
|
}
|
|
}
|
|
|
|
$macros[$hostmacro['hostmacroid']] = $hostmacro['macro'];
|
|
}
|
|
|
|
if (array_key_exists('value', $hostmacro) && $hostmacro['type'] == ZBX_MACRO_TYPE_VAULT) {
|
|
if (!CApiInputValidator::validate([
|
|
'type' => API_VAULT_SECRET,
|
|
'provider' => CSettingsHelper::get(CSettingsHelper::VAULT_PROVIDER)
|
|
], $hostmacro['value'], $path.'/'.($i2 + 1).'/value', $error)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
|
|
}
|
|
}
|
|
}
|
|
unset($hostmacro);
|
|
|
|
// Checking for cross renaming of existing macros.
|
|
foreach ($macros as $hostmacroid => $macro) {
|
|
if (array_key_exists($macro, $db_macros) && bccomp($hostmacroid, $db_macros[$macro]) != 0
|
|
&& array_key_exists($db_macros[$macro], $macros)) {
|
|
$hosts = DB::select('hosts', [
|
|
'output' => ['name'],
|
|
'hostids' => $host[$id_field_name]
|
|
]);
|
|
|
|
self::exception(ZBX_API_ERROR_PARAMETERS,
|
|
_s('Macro "%1$s" already exists on "%2$s".', $macro, $hosts[0]['name'])
|
|
);
|
|
}
|
|
}
|
|
|
|
$api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['macro']], 'fields' => [
|
|
'macro' => ['type' => API_USER_MACRO]
|
|
]];
|
|
|
|
if (!CApiInputValidator::validateUniqueness($api_input_rules, $host['macros'], $path, $error)) {
|
|
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
|
|
}
|
|
}
|
|
unset($host);
|
|
|
|
return $hosts;
|
|
}
|
|
|
|
/**
|
|
* Updates user macros for hosts, templates and host prototypes.
|
|
*
|
|
* @param array $hosts
|
|
* @param array $hosts[]['templateid|hostid']
|
|
* @param array $hosts[]['macros'] (optional)
|
|
* @param array $db_hosts
|
|
* @param array $db_hosts[<hostid>]['macros'] An array of host macros indexed by hostmacroid.
|
|
*/
|
|
protected function updateHostMacros(array $hosts, array $db_hosts): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$ins_hostmacros = [];
|
|
$upd_hostmacros = [];
|
|
$del_hostmacroids = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (!array_key_exists('macros', $host)) {
|
|
continue;
|
|
}
|
|
|
|
$db_host = $db_hosts[$host[$id_field_name]];
|
|
|
|
foreach ($host['macros'] as $hostmacro) {
|
|
if (array_key_exists('hostmacroid', $hostmacro)) {
|
|
$db_hostmacro = $db_host['macros'][$hostmacro['hostmacroid']];
|
|
unset($db_host['macros'][$hostmacro['hostmacroid']]);
|
|
|
|
$upd_hostmacro = DB::getUpdatedValues('hostmacro', $hostmacro, $db_hostmacro);
|
|
|
|
if ($upd_hostmacro) {
|
|
$upd_hostmacros[] = [
|
|
'values' => $upd_hostmacro,
|
|
'where' => ['hostmacroid' => $hostmacro['hostmacroid']]
|
|
];
|
|
}
|
|
}
|
|
else {
|
|
$ins_hostmacros[] = $hostmacro + ['hostid' => $host[$id_field_name]];
|
|
}
|
|
}
|
|
|
|
$del_hostmacroids = array_merge($del_hostmacroids, array_keys($db_host['macros']));
|
|
}
|
|
|
|
if ($del_hostmacroids) {
|
|
DB::delete('hostmacro', ['hostmacroid' => $del_hostmacroids]);
|
|
}
|
|
|
|
if ($upd_hostmacros) {
|
|
DB::update('hostmacro', $upd_hostmacros);
|
|
}
|
|
|
|
if ($ins_hostmacros) {
|
|
DB::insert('hostmacro', $ins_hostmacros);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array $db_hosts
|
|
*/
|
|
protected function addAffectedObjects(array $hosts, array &$db_hosts): void {
|
|
$this->addAffectedTemplates($hosts, $db_hosts);
|
|
$this->addAffectedTags($hosts, $db_hosts);
|
|
$this->addAffectedMacros($hosts, $db_hosts);
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array $db_hosts
|
|
*/
|
|
private function addAffectedTemplates(array $hosts, array &$db_hosts): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$hostids = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (array_key_exists('templates', $host) || array_key_exists('templates_clear', $host)) {
|
|
$hostids[] = $host[$id_field_name];
|
|
$db_hosts[$host[$id_field_name]]['templates'] = [];
|
|
}
|
|
}
|
|
|
|
if (!$hostids) {
|
|
return;
|
|
}
|
|
|
|
$permitted_templates = [];
|
|
|
|
if (self::$userData['type'] == USER_TYPE_ZABBIX_ADMIN) {
|
|
$permitted_templates = API::Template()->get([
|
|
'output' => [],
|
|
'hostids' => $hostids,
|
|
'preservekeys' => true
|
|
]);
|
|
}
|
|
|
|
$options = [
|
|
'output' => ['hosttemplateid', 'hostid', 'templateid'],
|
|
'filter' => ['hostid' => $hostids]
|
|
];
|
|
$db_templates = DBselect(DB::makeSql('hosts_templates', $options));
|
|
|
|
while ($db_template = DBfetch($db_templates)) {
|
|
if (self::$userData['type'] == USER_TYPE_SUPER_ADMIN
|
|
|| array_key_exists($db_template['templateid'], $permitted_templates)) {
|
|
$db_hosts[$db_template['hostid']]['templates'][$db_template['hosttemplateid']] =
|
|
array_diff_key($db_template, array_flip(['hostid']));
|
|
}
|
|
else {
|
|
$db_hosts[$db_template['hostid']]['nopermissions_templates'][$db_template['hosttemplateid']] =
|
|
array_diff_key($db_template, array_flip(['hostid']));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array $db_hosts
|
|
*/
|
|
protected function addAffectedTags(array $hosts, array &$db_hosts): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$hostids = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (array_key_exists('tags', $host)) {
|
|
$hostids[] = $host[$id_field_name];
|
|
$db_hosts[$host[$id_field_name]]['tags'] = [];
|
|
}
|
|
}
|
|
|
|
if (!$hostids) {
|
|
return;
|
|
}
|
|
|
|
$options = [
|
|
'output' => ['hosttagid', 'hostid', 'tag', 'value', 'automatic'],
|
|
'filter' => ['hostid' => $hostids]
|
|
];
|
|
$db_tags = DBselect(DB::makeSql('host_tag', $options));
|
|
|
|
while ($db_tag = DBfetch($db_tags)) {
|
|
$db_hosts[$db_tag['hostid']]['tags'][$db_tag['hosttagid']] =
|
|
array_diff_key($db_tag, array_flip(['hostid']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $hosts
|
|
* @param array $db_hosts
|
|
*/
|
|
private function addAffectedMacros(array $hosts, array &$db_hosts): void {
|
|
$id_field_name = $this instanceof CTemplate ? 'templateid' : 'hostid';
|
|
|
|
$hostids = [];
|
|
|
|
foreach ($hosts as $host) {
|
|
if (array_key_exists('macros', $host)) {
|
|
$hostids[] = $host[$id_field_name];
|
|
$db_hosts[$host[$id_field_name]]['macros'] = [];
|
|
}
|
|
}
|
|
|
|
if (!$hostids) {
|
|
return;
|
|
}
|
|
|
|
$options = [
|
|
'output' => ['hostmacroid', 'hostid', 'macro', 'value', 'description', 'type'],
|
|
'filter' => ['hostid' => $hostids]
|
|
];
|
|
$db_macros = DBselect(DB::makeSql('hostmacro', $options));
|
|
|
|
while ($db_macro = DBfetch($db_macros)) {
|
|
$db_hosts[$db_macro['hostid']]['macros'][$db_macro['hostmacroid']] =
|
|
array_diff_key($db_macro, array_flip(['hostid']));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves and adds additional requested data to the result set.
|
|
*
|
|
* @param array $options
|
|
* @param array $result
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function addRelatedObjects(array $options, array $result) {
|
|
$result = parent::addRelatedObjects($options, $result);
|
|
|
|
$hostids = array_keys($result);
|
|
|
|
// adding macros
|
|
if ($options['selectMacros'] !== null && $options['selectMacros'] !== API_OUTPUT_COUNT) {
|
|
$macros = API::UserMacro()->get([
|
|
'output' => $this->outputExtend($options['selectMacros'], ['hostid', 'hostmacroid']),
|
|
'hostids' => $hostids,
|
|
'preservekeys' => true,
|
|
'nopermissions' => true
|
|
]);
|
|
|
|
$relationMap = $this->createRelationMap($macros, 'hostid', 'hostmacroid');
|
|
$macros = $this->unsetExtraFields($macros, ['hostid', 'hostmacroid'], $options['selectMacros']);
|
|
$result = $relationMap->mapMany($result, $macros, 'macros',
|
|
array_key_exists('limitSelects', $options) ? $options['limitSelects'] : null
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|