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.

621 lines
17 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
/**
* Class containing methods for operations with icon maps.
*
* Icon maps work only with map elements that represent hosts, and used for automatic changing
* icons based on host inventory data.
* Icon maps has read access for everyone and write access only for superadmins
*/
class CIconMap extends CApiService {
public const ACCESS_RULES = [
'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN],
'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN],
'delete' => ['min_user_type' => USER_TYPE_SUPER_ADMIN]
];
protected $tableName = 'icon_map';
protected $tableAlias = 'im';
protected $sortColumns = ['iconmapid', 'name'];
/**
* Get IconMap data.
* @param array $options
* @param array $options['iconmapids']
* @param array $options['sysmapids']
* @param bool $options['editable']
* @param array $options['count']
* @param array $options['limit']
* @param array $options['order']
* @return array
*/
public function get(array $options = []) {
$result = [];
$sqlParts = [
'select' => ['icon_map' => 'im.iconmapid'],
'from' => ['icon_map' => 'icon_map im'],
'where' => [],
'order' => [],
'limit' => null
];
$defOptions = [
'iconmapids' => null,
'sysmapids' => null,
'nopermissions' => null,
'editable' => false,
// filter
'filter' => null,
'search' => null,
'searchByAny' => null,
'startSearch' => false,
'excludeSearch' => false,
'searchWildcardsEnabled' => null,
// output
'output' => API_OUTPUT_EXTEND,
'selectMappings' => null,
'countOutput' => false,
'preservekeys' => false,
'sortfield' => '',
'sortorder' => '',
'limit' => null
];
$options = zbx_array_merge($defOptions, $options);
// editable + PERMISSION CHECK
if ($options['editable'] && self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
return [];
}
// iconmapids
if (!is_null($options['iconmapids'])) {
zbx_value2array($options['iconmapids']);
$sqlParts['where'][] = dbConditionInt('im.iconmapid', $options['iconmapids']);
}
// sysmapids
if (!is_null($options['sysmapids'])) {
zbx_value2array($options['sysmapids']);
$sqlParts['from']['sysmaps'] = 'sysmaps s';
$sqlParts['where'][] = dbConditionInt('s.sysmapid', $options['sysmapids']);
$sqlParts['where']['ims'] = 'im.iconmapid=s.iconmapid';
}
// filter
if (is_array($options['filter'])) {
$this->dbFilter('icon_map im', $options, $sqlParts);
}
// search
if (is_array($options['search'])) {
zbx_db_search('icon_map im', $options, $sqlParts);
}
// limit
if (zbx_ctype_digit($options['limit']) && $options['limit']) {
$sqlParts['limit'] = $options['limit'];
}
$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
$dbRes = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
while ($iconMap = DBfetch($dbRes)) {
if ($options['countOutput']) {
$result = $iconMap['rowscount'];
}
else {
$result[$iconMap['iconmapid']] = $iconMap;
}
}
if ($options['countOutput']) {
return $result;
}
if ($result) {
$result = $this->addRelatedObjects($options, $result);
}
// removing keys (hash -> array)
if (!$options['preservekeys']) {
$result = zbx_cleanHashes($result);
}
return $result;
}
/**
* @param array $iconmaps
*
* @return array
*/
public function create(array $iconmaps) {
if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_s('No permissions to call "%1$s.%2$s".', 'iconmap', __FUNCTION__)
);
}
self::validateCreate($iconmaps);
$ins_iconmaps = [];
foreach ($iconmaps as $iconmap) {
unset($iconmap['mappings']);
$ins_iconmaps[] = $iconmap;
}
$iconmapids = DB::insert('icon_map', $ins_iconmaps);
foreach ($iconmaps as $index => &$iconmap) {
$iconmap['iconmapid'] = $iconmapids[$index];
}
unset($iconmap);
self::updateMappings($iconmaps, __FUNCTION__);
self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_ICON_MAP, $iconmaps);
return ['iconmapids' => $iconmapids];
}
/**
* @static
*
* @param array $iconmaps
*
* @throws APIException if the input is invalid.
*/
private static function validateCreate(array &$iconmaps) {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('icon_map', 'name')],
'default_iconid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'mappings' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['inventory_link', 'expression']], 'fields' => [
'iconid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'expression' => ['type' => API_REGEX, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_GLOBAL_REGEX, 'length' => DB::getFieldLength('icon_mapping', 'expression')],
'inventory_link' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '1:70']
]]
]];
if (!CApiInputValidator::validate($api_input_rules, $iconmaps, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
self::checkDuplicates($iconmaps);
self::checkMappings($iconmaps);
self::checkIcons($iconmaps);
}
/**
* @param array $iconmaps
*
* @return array
*/
public function update(array $iconmaps) {
if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_s('No permissions to call "%1$s.%2$s".', 'iconmap', __FUNCTION__)
);
}
self::validateUpdate($iconmaps, $db_iconmaps);
$upd_iconmaps = [];
foreach ($iconmaps as $iconmap) {
$upd_iconmap = DB::getUpdatedValues('icon_map', $iconmap, $db_iconmaps[$iconmap['iconmapid']]);
if ($upd_iconmap) {
$upd_iconmaps[] = [
'values' => $upd_iconmap,
'where' => ['iconmapid' => $iconmap['iconmapid']]
];
}
}
if ($upd_iconmaps) {
DB::update('icon_map', $upd_iconmaps);
}
self::updateMappings($iconmaps, __FUNCTION__, $db_iconmaps);
self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_ICON_MAP, $iconmaps, $db_iconmaps);
return ['iconmapids' => array_column($iconmaps, 'iconmapid')];
}
/**
* @static
*
* @param array $iconmaps
*
* @throws APIException if the input is invalid.
*/
private static function validateUpdate(array &$iconmaps, array &$db_iconmaps = null) {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['iconmapid'], ['name']], 'fields' => [
'iconmapid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('icon_map', 'name')],
'default_iconid' => ['type' => API_ID],
'mappings' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['inventory_link', 'expression']], 'fields' => [
'iconid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'expression' => ['type' => API_REGEX, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_GLOBAL_REGEX, 'length' => DB::getFieldLength('icon_mapping', 'expression')],
'inventory_link' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '1:70']
]]
]];
if (!CApiInputValidator::validate($api_input_rules, $iconmaps, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_iconmaps = DB::select('icon_map', [
'output' => ['iconmapid', 'name', 'default_iconid'],
'iconmapids' => array_column($iconmaps, 'iconmapid'),
'preservekeys' => true
]);
if (count($db_iconmaps) != count($iconmaps)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
self::addAffectedObjects($iconmaps, $db_iconmaps);
self::checkDuplicates($iconmaps, $db_iconmaps);
self::checkMappings($iconmaps);
self::checkIcons($iconmaps);
}
/**
* Check for duplicated icon maps.
*
* @static
*
* @param array $iconmaps
* @param array|null $db_iconmaps
*
* @throws APIException if user already exists.
*/
private static function checkDuplicates(array $iconmaps, array $db_iconmaps = null) {
$names = [];
foreach ($iconmaps as $iconmap) {
if (!array_key_exists('name', $iconmap)) {
continue;
}
if ($db_iconmaps === null || $iconmap['name'] !== $db_iconmaps[$iconmap['iconmapid']]['name']) {
$names[] = $iconmap['name'];
}
}
if (!$names) {
return;
}
$duplicates = DB::select('icon_map', [
'output' => ['name'],
'filter' => ['name' => $names],
'limit' => 1
]);
if ($duplicates) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Icon map "%1$s" already exists.', $duplicates[0]['name']));
}
}
/**
* Check icon mappings.
*
* @static
*
* @param array $iconmaps
*
* @throws APIException
*/
private static function checkMappings(array $iconmaps): void {
$names = [];
foreach ($iconmaps as $iconmap) {
if (array_key_exists('mappings', $iconmap)) {
foreach ($iconmap['mappings'] as $mapping) {
if ($mapping['expression'][0] === '@') {
/*
* substr() performs differently on PHP < 7. Prior to PHP 7 version, FALSE was returned if
* string is equal to start characters long. As of PHP 7, an empty string will be returned.
*/
$names[isset($mapping['expression'][1]) ? substr($mapping['expression'], 1) : ''] = true;
}
}
}
}
if ($names) {
$names = array_keys($names);
$db_regexps = DB::select('regexps', [
'output' => ['name'],
'filter' => ['name' => $names]
]);
$db_regexps = array_column($db_regexps, null, 'name');
foreach ($names as $name) {
if (!array_key_exists($name, $db_regexps)) {
self::exception(ZBX_API_ERROR_PARAMETERS,
_s('Global regular expression "%1$s" does not exist.', $name)
);
}
}
}
}
/**
* Check icons.
*
* @static
*
* @param array $iconmaps
*
* @throws APIException
*/
private static function checkIcons(array $iconmaps): void {
$iconids = [];
foreach ($iconmaps as $iconmap) {
if (array_key_exists('default_iconid', $iconmap)) {
$iconids[$iconmap['default_iconid']] = true;
}
if (array_key_exists('mappings', $iconmap)) {
foreach ($iconmap['mappings'] as $mapping) {
$iconids[$mapping['iconid']] = true;
}
}
}
if ($iconids) {
$iconids = array_keys($iconids);
$db_icons = API::Image()->get([
'output' => [],
'imageids' => $iconids,
'filter' => ['imagetype' => IMAGE_TYPE_ICON],
'preservekeys' => true
]);
foreach ($iconids as $iconid) {
if (!array_key_exists($iconid, $db_icons)) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Icon with ID "%1$s" is not available.', $iconid));
}
}
}
}
/**
* Update table "icon_mapping".
*
* @static
*
* @param array $iconmaps
* @param string $method
* @param array|null $db_iconmaps
*/
private static function updateMappings(array &$iconmaps, string $method, array $db_iconmaps = null): void {
$ins_mappings = [];
$upd_mappings = [];
$del_mappingids = [];
foreach ($iconmaps as &$iconmap) {
if (!array_key_exists('mappings', $iconmap)) {
continue;
}
$db_mappings = ($method === 'update') ? $db_iconmaps[$iconmap['iconmapid']]['mappings'] : [];
foreach ($iconmap['mappings'] as $index => &$mapping) {
$mapping['sortorder'] = $index;
$db_mapping = current(array_filter($db_mappings,
static function (array $db_mapping) use ($mapping): bool {
return ($mapping['inventory_link'] == $db_mapping['inventory_link']
&& $mapping['expression'] === $db_mapping['expression']);
}
));
if ($db_mapping) {
$mapping['iconmappingid'] = $db_mapping['iconmappingid'];
$upd_mapping = DB::getUpdatedValues('icon_mapping', $mapping, $db_mapping);
if ($upd_mapping) {
$upd_mappings[] = [
'values' => $upd_mapping,
'where' => ['iconmappingid' => $db_mapping['iconmappingid']]
];
}
unset($db_mappings[$db_mapping['iconmappingid']]);
}
else {
$ins_mappings[] = ['iconmapid' => $iconmap['iconmapid']] + $mapping;
}
}
unset($mapping);
$del_mappingids = array_merge($del_mappingids, array_keys($db_mappings));
}
unset($iconmap);
if ($ins_mappings) {
$iconmappingids = DB::insert('icon_mapping', $ins_mappings);
}
if ($upd_mappings) {
DB::update('icon_mapping', $upd_mappings);
}
if ($del_mappingids) {
DB::delete('icon_mapping', ['iconmappingid' => $del_mappingids]);
}
foreach ($iconmaps as &$iconmap) {
if (!array_key_exists('mappings', $iconmap)) {
continue;
}
foreach ($iconmap['mappings'] as &$mapping) {
if (!array_key_exists('iconmappingid', $mapping)) {
$mapping['iconmappingid'] = array_shift($iconmappingids);
}
}
unset($mapping);
}
unset($iconmap);
}
/**
* @param array $iconmapids
*
* @return array
*/
public function delete(array $iconmapids) {
if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
self::exception(ZBX_API_ERROR_PERMISSIONS,
_s('No permissions to call "%1$s.%2$s".', 'iconmap', __FUNCTION__)
);
}
self::validateDelete($iconmapids, $db_iconmaps);
DB::delete('icon_mapping', ['iconmapid' => $iconmapids]);
DB::delete('icon_map', ['iconmapid' => $iconmapids]);
self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_ICON_MAP, $db_iconmaps);
return ['iconmapids' => $iconmapids];
}
/**
* @static
*
* @param array $iconmapids
* @param array|null $db_iconmaps
*
* @throws APIException if the input is invalid.
*/
private static function validateDelete(array &$iconmapids, array &$db_iconmaps = null) {
$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
if (!CApiInputValidator::validate($api_input_rules, $iconmapids, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_iconmaps = DB::select('icon_map', [
'output' => ['iconmapid', 'name'],
'iconmapids' => $iconmapids,
'preservekeys' => true
]);
if (count($db_iconmaps) != count($iconmapids)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
self::checkUsedSysMaps($iconmapids, $db_iconmaps);
}
/**
* @static
*
* @param array $iconmapids
* @param array|null $db_iconmaps
*
* @throws APIException if the input is invalid.
*/
private static function checkUsedSysMaps(array $iconmapids, array $db_iconmaps): void {
$db_sysmaps = DB::select('sysmaps', [
'output' => ['name', 'iconmapid'],
'filter' => ['iconmapid' => $iconmapids],
'limit' => 1
]);
if ($db_sysmaps) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Icon map "%1$s" cannot be deleted. Used in map "%2$s".',
$db_iconmaps[$db_sysmaps[0]['iconmapid']]['name'], $db_sysmaps[0]['name']
));
}
}
protected function addRelatedObjects(array $options, array $result) {
$result = parent::addRelatedObjects($options, $result);
$iconMapIds = array_keys($result);
if ($options['selectMappings'] !== null && $options['selectMappings'] != API_OUTPUT_COUNT) {
$mappings = API::getApiService()->select('icon_mapping', [
'output' => $this->outputExtend($options['selectMappings'], ['iconmapid', 'iconmappingid']),
'filter' => ['iconmapid' => $iconMapIds],
'preservekeys' => true
]);
$relationMap = $this->createRelationMap($mappings, 'iconmapid', 'iconmappingid');
$mappings = $this->unsetExtraFields($mappings, ['iconmapid', 'iconmappingid'], $options['selectMappings']);
$result = $relationMap->mapMany($result, $mappings, 'mappings');
}
return $result;
}
/**
* Add the existing mappings to $db_iconmaps whether these are affected by the update.
*
* @static
*
* @param array $iconmaps
* @param array $db_iconmaps
*/
private static function addAffectedObjects(array $iconmaps, array &$db_iconmaps): void {
$iconmapids = [];
foreach ($iconmaps as $iconmap) {
if (array_key_exists('mappings', $iconmap)) {
$iconmapids[] = $iconmap['iconmapid'];
$db_iconmaps[$iconmap['iconmapid']]['mappings'] = [];
}
}
if ($iconmapids) {
$options = [
'output' => ['iconmappingid', 'iconmapid', 'iconid', 'inventory_link', 'expression', 'sortorder'],
'filter' => ['iconmapid' => $iconmapids]
];
$db_mappings = DBselect(DB::makeSql('icon_mapping', $options));
while ($db_mapping = DBfetch($db_mappings)) {
$db_iconmaps[$db_mapping['iconmapid']]['mappings'][$db_mapping['iconmappingid']] =
array_diff_key($db_mapping, array_flip(['iconmapid']));
}
}
}
}