['min_user_type' => USER_TYPE_ZABBIX_USER], 'create' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 'update' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_USER] ]; protected $tableName = 'services'; protected $tableAlias = 's'; protected $sortColumns = ['serviceid', 'name', 'status', 'sortorder', 'created_at']; public const OUTPUT_FIELDS = ['serviceid', 'uuid', 'name', 'status', 'algorithm', 'sortorder', 'weight', 'propagation_rule', 'propagation_value', 'description', 'created_at', 'readonly' ]; /** * @param array $options * * @throws APIException * * @return array|string */ public function get(array $options = []) { return $this->doGet($options, self::getPermissions()); } /** * @param array $options * @param array|null $permissions * * @throws APIException * * @return array|string */ private function doGet(array $options = [], array $permissions = null) { $api_input_rules = ['type' => API_OBJECT, 'fields' => [ // filter 'serviceids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'parentids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'deep_parentids' => ['type' => API_BOOLEAN, 'default' => false], 'childids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'evaltype' => ['type' => API_INT32, 'in' => implode(',', [TAG_EVAL_TYPE_AND_OR, TAG_EVAL_TYPE_OR]), 'default' => TAG_EVAL_TYPE_AND_OR], 'tags' => ['type' => API_OBJECTS, 'default' => [], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED], 'value' => ['type' => API_STRING_UTF8], 'operator' => ['type' => API_INT32, 'in' => implode(',', [TAG_OPERATOR_LIKE, TAG_OPERATOR_EQUAL, TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_EXISTS, TAG_OPERATOR_NOT_EXISTS])] ]], 'problem_tags' => ['type' => API_OBJECTS, 'default' => [], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED], 'value' => ['type' => API_STRING_UTF8], 'operator' => ['type' => API_INT32, 'in' => implode(',', [TAG_OPERATOR_LIKE, TAG_OPERATOR_EQUAL, TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_EXISTS, TAG_OPERATOR_NOT_EXISTS])] ]], 'without_problem_tags' => ['type' => API_BOOLEAN, 'default' => false], 'slaids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 'filter' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['uuid', 'serviceid', 'name', 'status', 'algorithm']], 'search' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['name']], 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 'startSearch' => ['type' => API_FLAG, 'default' => false], 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], // output 'output' => ['type' => API_OUTPUT, 'in' => implode(',', self::OUTPUT_FIELDS), 'default' => API_OUTPUT_EXTEND], 'countOutput' => ['type' => API_FLAG, 'default' => false], 'selectParents' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', self::OUTPUT_FIELDS), 'default' => null], 'selectChildren' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', self::OUTPUT_FIELDS), 'default' => null], 'selectTags' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['tag', 'value']), 'default' => null], 'selectProblemTags' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['tag', 'operator', 'value']), 'default' => null], 'selectProblemEvents' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['eventid', 'severity', 'name']), 'default' => null], 'selectStatusRules' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['type', 'limit_value', 'limit_status', 'new_status']), 'default' => null], 'selectStatusTimeline' => ['type' => API_OBJECTS, 'flags' => API_ALLOW_NULL | API_NOT_EMPTY, 'uniq' => [['period_from', 'period_to']], 'default' => null, 'fields' => [ 'period_from' => ['type' => API_INT32, 'in' => '0:'.ZBX_MAX_DATE, 'flags' => API_REQUIRED], 'period_to' => ['type' => API_INT32, 'in' => '0:'.ZBX_MAX_DATE, 'flags' => API_REQUIRED] ]], // sort and limit 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', ['serviceid', 'name', 'status', 'sortorder', 'created_at']), 'uniq' => true, 'default' => []], 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], // flags 'editable' => ['type' => API_BOOLEAN, 'default' => false], 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false] ]]; if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } if ($permissions === null) { $accessible_services = null; } elseif ($options['editable']) { $accessible_services = $permissions['rw_services']; } elseif ($permissions['r_services'] === null || $permissions['rw_services'] === null) { $accessible_services = null; } else { $accessible_services = $permissions['r_services'] + $permissions['rw_services']; } if ($options['parentids'] !== null && $accessible_services !== null) { $options['parentids'] = array_intersect($options['parentids'], array_keys([0 => true] + $accessible_services) ); } if ($options['parentids'] !== null && $options['deep_parentids']) { $limit_services = self::getServicesByDeepParents($options['parentids']); if ($accessible_services !== null) { $limit_services = $limit_services !== null ? array_intersect_key($limit_services, $accessible_services) : $accessible_services; } $options['parentids'] = null; } else { $limit_services = $accessible_services; } $options['root_services'] = $permissions !== null ? $permissions['root_services'] : null; $count_output = $options['countOutput']; if ($count_output) { $options['output'] = ['serviceid']; $options['countOutput'] = false; } $resource = DBselect($this->createSelectQuery('services', $options)); $db_services = []; while (($options['limit'] === null || count($db_services) < $options['limit']) && $row = DBfetch($resource)) { if ($limit_services !== null && !array_key_exists($row['serviceid'], $limit_services)) { continue; } if (!$count_output && $this->outputIsRequested('readonly', $options['output'])) { $row['readonly'] = $permissions !== null && $permissions['rw_services'] !== null && !array_key_exists($row['serviceid'], $permissions['rw_services']); } $db_services[$row['serviceid']] = $row; } if ($count_output) { return (string) count($db_services); } if ($db_services) { $db_services = $this->addRelatedObjects($options, $db_services, $permissions); $db_services = $this->unsetExtraFields($db_services, ['serviceid'], $options['output']); if (!$options['preservekeys']) { $db_services = array_values($db_services); } } return $db_services; } /** * @param array $services * * @throws APIException * * @return array */ public function create(array $services): array { self::validateCreate($services); $created_at = time(); $ins_services = []; foreach ($services as $service) { unset($service['tags'], $service['parents'], $service['children']); $ins_services[] = $service + ['created_at' => $created_at]; } $serviceids = DB::insert('services', $ins_services); foreach ($services as $index => &$service) { $service['serviceid'] = $serviceids[$index]; } unset($service); self::updateTags($services); self::updateProblemTags($services); self::updateParents($services); self::updateChildren($services); self::updateStatusRules($services); self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_IT_SERVICE, $services); return ['serviceids' => $serviceids]; } /** * @param array $services * * @throws APIException */ private static function validateCreate(array &$services): void { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid']], 'fields' => [ 'uuid' => ['type' => API_UUID], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('services', 'name')], 'algorithm' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_SERVICE_STATUS_CALC_SET_OK, ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ALL, ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ONE])], 'sortorder' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:999'], 'weight' => ['type' => API_INT32, 'in' => '0:1000000'], 'propagation_rule' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SERVICE_STATUS_PROPAGATION_AS_IS, ZBX_SERVICE_STATUS_PROPAGATION_INCREASE, ZBX_SERVICE_STATUS_PROPAGATION_DECREASE, ZBX_SERVICE_STATUS_PROPAGATION_IGNORE, ZBX_SERVICE_STATUS_PROPAGATION_FIXED])], 'propagation_value' => ['type' => API_INT32], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('services', 'description')], 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('service_tag', 'tag')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('service_tag', 'value'), 'default' => DB::getDefault('service_tag', 'value')] ]], 'problem_tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('service_problem_tag', 'tag')], 'operator' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SERVICE_PROBLEM_TAG_OPERATOR_EQUAL, ZBX_SERVICE_PROBLEM_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('service_problem_tag', 'operator')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('service_problem_tag', 'value'), 'default' => DB::getDefault('service_problem_tag', 'value')] ]], 'parents' => ['type' => API_OBJECTS, 'uniq' => [['serviceid']], 'fields' => [ 'serviceid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'children' => ['type' => API_OBJECTS, 'uniq' => [['serviceid']], 'fields' => [ 'serviceid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'status_rules' => ['type' => API_OBJECTS, 'uniq' => [['type', 'limit_value', 'limit_status']], 'fields' => [ 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_N_GE, ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_N_L, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_W_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_W_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L])], 'limit_value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_N_GE, ZBX_SERVICE_STATUS_RULE_TYPE_N_L, ZBX_SERVICE_STATUS_RULE_TYPE_W_GE, ZBX_SERVICE_STATUS_RULE_TYPE_W_L])], 'type' => API_INT32, 'in' => '1:1000000'], ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L])], 'type' => API_INT32, 'in' => '1:100'] ]], 'limit_status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', array_merge([ZBX_SEVERITY_OK], range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1)))], 'new_status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1))] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $services, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } self::checkPermissions(self::getPermissions(), $services); self::addUuid($services); self::checkUuidDuplicates($services); self::checkStatusPropagation($services); self::checkChildrenOrProblemTags($services); self::checkChildrenOrProblemTagsInParents($services); self::checkCircularReferences($services); } /** * @param array $services * * @throws APIException * * @return array */ public function update(array $services): array { self::validateUpdate($services, $db_services); $upd_services = []; foreach ($services as $service) { $upd_service = DB::getUpdatedValues('services', $service, $db_services[$service['serviceid']]); if ($upd_service) { $upd_services[] = [ 'values' => $upd_service, 'where' => ['serviceid' => $service['serviceid']] ]; } } if ($upd_services) { DB::update('services', $upd_services); } self::updateTags($services, $db_services); self::updateProblemTags($services, $db_services); self::updateParents($services, $db_services); self::updateChildren($services, $db_services); self::updateStatusRules($services, $db_services); self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_IT_SERVICE, $services, $db_services); return ['serviceids' => array_column($services, 'serviceid')]; } /** * @param array $services * @param array|null $db_services * * @throws APIException */ private static function validateUpdate(array &$services, ?array &$db_services): void { $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['serviceid']], 'fields' => [ 'uuid' => ['type' => API_UUID], 'serviceid' => ['type' => API_ID, 'flags' => API_REQUIRED], 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('services', 'name')], 'algorithm' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SERVICE_STATUS_CALC_SET_OK, ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ALL, ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ONE])], 'sortorder' => ['type' => API_INT32, 'in' => '0:999'], 'weight' => ['type' => API_INT32, 'in' => '0:1000000'], 'propagation_rule' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SERVICE_STATUS_PROPAGATION_AS_IS, ZBX_SERVICE_STATUS_PROPAGATION_INCREASE, ZBX_SERVICE_STATUS_PROPAGATION_DECREASE, ZBX_SERVICE_STATUS_PROPAGATION_IGNORE, ZBX_SERVICE_STATUS_PROPAGATION_FIXED])], 'propagation_value' => ['type' => API_INT32], 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('services', 'description')], 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('service_tag', 'tag')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('service_tag', 'value'), 'default' => DB::getDefault('service_tag', 'value')] ]], 'problem_tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('service_problem_tag', 'tag')], 'operator' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SERVICE_PROBLEM_TAG_OPERATOR_EQUAL, ZBX_SERVICE_PROBLEM_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('service_problem_tag', 'operator')], 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('service_problem_tag', 'value'), 'default' => DB::getDefault('service_problem_tag', 'value')] ]], 'parents' => ['type' => API_OBJECTS, 'uniq' => [['serviceid']], 'fields' => [ 'serviceid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'children' => ['type' => API_OBJECTS, 'uniq' => [['serviceid']], 'fields' => [ 'serviceid' => ['type' => API_ID, 'flags' => API_REQUIRED] ]], 'status_rules' => ['type' => API_OBJECTS, 'uniq' => [['type', 'limit_value', 'limit_status']], 'fields' => [ 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_N_GE, ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_N_L, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_W_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_W_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L])], 'limit_value' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_N_GE, ZBX_SERVICE_STATUS_RULE_TYPE_N_L, ZBX_SERVICE_STATUS_RULE_TYPE_W_GE, ZBX_SERVICE_STATUS_RULE_TYPE_W_L])], 'type' => API_INT32, 'in' => '1:1000000'], ['if' => ['field' => 'type', 'in' => implode(',', [ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L])], 'type' => API_INT32, 'in' => '1:100'] ]], 'limit_status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', array_merge([ZBX_SEVERITY_OK], range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1)))], 'new_status' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1))] ]] ]]; if (!CApiInputValidator::validate($api_input_rules, $services, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $db_services = DB::select('services', [ 'output' => ['uuid', 'serviceid', 'name', 'status', 'algorithm', 'sortorder', 'weight', 'propagation_rule', 'propagation_value', 'description' ], 'serviceids' => array_column($services, 'serviceid'), 'preservekeys' => true ]); if (count($db_services) != count($services)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } $permissions = self::getPermissions(); self::addAffectedObjects($services, $db_services, $permissions); self::checkPermissions($permissions, $services, $db_services); self::checkUuidDuplicates($services, $db_services); self::checkParentChildRelations($services, $db_services); self::checkStatusPropagation($services, $db_services); self::checkChildrenOrProblemTags($services, $db_services); self::checkChildrenOrProblemTagsInParents($services); self::checkCircularReferences($services, $db_services); } /** * @param array $serviceids * * @throws APIException * * @return array */ public function delete(array $serviceids): array { $this->validateDelete($serviceids, $db_services); DB::delete('services', ['serviceid' => $serviceids]); $ins_housekeeper = []; foreach ($serviceids as $serviceid) { $ins_housekeeper[] = [ 'tablename' => 'events', 'field' => 'serviceid', 'value' => $serviceid ]; } DB::insertBatch('housekeeper', $ins_housekeeper); self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_IT_SERVICE, $db_services); return ['serviceids' => $serviceids]; } /** * @param array $serviceids * @param array|null $db_services * * @throws APIException */ private function validateDelete(array $serviceids, ?array &$db_services): void { $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; if (!CApiInputValidator::validate($api_input_rules, $serviceids, '/', $error)) { self::exception(ZBX_API_ERROR_PARAMETERS, $error); } $permissions = self::getPermissions(); $db_services = $this->doGet([ 'output' => ['serviceid', 'name', 'readonly'], 'selectChildren' => $permissions['rw_services'] !== null ? ['serviceid', 'name'] : null, 'serviceids' => $serviceids ], $permissions); if (count($db_services) != count($serviceids)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); } if ($permissions['rw_services'] === null) { return; } foreach ($db_services as $db_service) { if ($db_service['readonly'] == 1) { $error_detail = _('read-write access to the service is required'); $error = _s('Cannot delete service "%1$s": %2$s.', $db_service['name'], $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } } foreach ($db_services as $db_service) { foreach ($db_service['children'] as $child_service) { if ($permissions['rw_services'][$child_service['serviceid']] !== null) { $permissions['rw_services'][$child_service['serviceid']]--; if ($permissions['rw_services'][$child_service['serviceid']] == 0) { $error_detail = _s('read-write access to the child service "%1$s" must be retained', $child_service['name'] ); $error = _s('Cannot delete service "%1$s": %2$s.', $db_service['name'], $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } } } } } /** * @param array $parentids * * @return array|null */ private static function getServicesByDeepParents(array $parentids): ?array { if (in_array(0, $parentids)) { return null; } $parents = array_fill_keys($parentids, true); $sql_options = [ 'output' => ['serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); $relations = []; while ($db_link = DBfetch($db_links)) { $relations[$db_link['serviceupid']][$db_link['servicedownid']] = true; } $limit_services = []; while ($parents) { $next_parents = []; foreach (array_intersect_key($relations, $parents) as $children) { $next_parents += $children; } $parents = $next_parents; $limit_services += $next_parents; } return $limit_services; } /** * @param string $table_name * @param string $table_alias * @param array $options * @param array $sql_parts * * @return array */ protected function applyQueryFilterOptions($table_name, $table_alias, array $options, array $sql_parts): array { $sql_parts = parent::applyQueryFilterOptions($table_name, $table_alias, $options, $sql_parts); if ($options['parentids'] !== null) { if (in_array(0, $options['parentids']) && $options['root_services'] !== null) { if (count($options['parentids']) > 1) { $conditions = [ 'slp.serviceupid' => dbConditionId('slp.serviceupid', array_diff($options['parentids'], [0])), 's.serviceid' => dbConditionId('s.serviceid', array_keys($options['root_services'])) ]; } else { $conditions = [ 's.serviceid' => dbConditionId('s.serviceid', array_keys($options['root_services'])) ]; } } else { $conditions = [ 'slp.serviceupid' => dbConditionId('slp.serviceupid', $options['parentids']) ]; } if (array_key_exists('slp.serviceupid', $conditions)) { $sql_parts['left_table'] = ['table' => 'services', 'alias' => 's']; $sql_parts['left_join'][] = [ 'table' => 'services_links', 'alias' => 'slp', 'using' => 'servicedownid' ]; } $sql_parts['where'][] = count($conditions) > 1 ? '('.implode(' OR ', $conditions).')' : reset($conditions); } if ($options['childids'] !== null) { $sql_parts['left_table'] = ['table' => 'services', 'alias' => 's']; $sql_parts['left_join'][] = [ 'table' => 'services_links', 'alias' => 'slc', 'using' => 'serviceupid' ]; $sql_parts['where'][] = dbConditionId('slc.servicedownid', $options['childids']); } if ($options['tags']) { $sql_parts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 's', 'service_tag', 'serviceid' ); } if ($options['problem_tags']) { $sql_parts['where'][] = CApiTagHelper::addWhereCondition($options['problem_tags'], $options['evaltype'], 's', 'service_problem_tag', 'serviceid' ); } elseif ($options['without_problem_tags']) { $sql_parts['left_table'] = ['table' => 'services', 'alias' => 's']; $sql_parts['left_join'][] = [ 'table' => 'service_problem_tag', 'alias' => 'spt', 'using' => 'serviceid' ]; $sql_parts['where'][] = dbConditionId('spt.service_problem_tagid', [0]); } if ($options['slaids'] !== null) { $sql_parts['where'][] = 'EXISTS ('. 'SELECT NULL'. ' FROM service_tag st, sla_service_tag sst'. ' WHERE sst.tag=st.tag'. ' AND ('. '(sst.operator='.ZBX_SLA_SERVICE_TAG_OPERATOR_EQUAL.' AND st.value=sst.value)'. ' OR (sst.operator='.ZBX_SLA_SERVICE_TAG_OPERATOR_LIKE." AND UPPER(st.value) LIKE CONCAT('%', ". "CONCAT(REPLACE(REPLACE(REPLACE(UPPER(sst.value), '%', '!%'), '_', '!_'), '!', '!!'), '%')". ") ESCAPE '!')". ')'. ' AND st.serviceid=s.serviceid'. ' AND '.dbConditionId('sst.slaid', $options['slaids']). ')'; } return $sql_parts; } /** * @param array $options * @param array $result * @param array|null $permissions * * @throws APIException * * @return array */ protected function addRelatedObjects(array $options, array $result, array $permissions = null): array { $result = parent::addRelatedObjects($options, $result); $this->addRelatedParents($options, $result, $permissions); $this->addRelatedChildren($options, $result, $permissions); self::addRelatedTags($options, $result); self::addRelatedProblemTags($options, $result); self::addRelatedProblemEvents($options, $result); self::addRelatedStatusRules($options, $result); self::addRelatedStatusTimeline($options, $result); return $result; } /** * @param array $options * @param array $result * @param array|null $permissions * * @throws APIException */ private function addRelatedParents(array $options, array &$result, ?array $permissions): void { if ($options['selectParents'] === null) { return; } $sql_options = [ 'output' => ['linkid', 'serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); $relations = []; while ($db_link = DBfetch($db_links)) { $relations[$db_link['servicedownid']][$db_link['serviceupid']] = $db_link['linkid']; } /* * Performance optimized: * - Not filtering the output by the related service IDs. */ $services = $this->doGet([ 'output' => ($options['selectParents'] === API_OUTPUT_COUNT) ? [] : $options['selectParents'], 'sortfield' => $options['sortfield'], 'sortorder' => $options['sortorder'], 'preservekeys' => true ], $permissions); foreach ($result as $serviceid => &$row) { $row['parents'] = array_key_exists($serviceid, $relations) ? array_intersect_key($services, $relations[$serviceid]) : []; if ($options['selectParents'] === API_OUTPUT_COUNT) { $row['parents'] = (string) count($row['parents']); } else { $row['parents'] = array_values($row['parents']); } } unset($row); } /** * @param array $options * @param array $result * @param array|null $permissions * * @throws APIException */ private function addRelatedChildren(array $options, array &$result, ?array $permissions): void { if ($options['selectChildren'] === null) { return; } $sql_options = [ 'output' => ['linkid', 'serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); $relations = []; while ($db_link = DBfetch($db_links)) { $relations[$db_link['serviceupid']][$db_link['servicedownid']] = $db_link['linkid']; } /* * Performance optimized: * - Not filtering the output by the related service IDs. */ $services = $this->doGet([ 'output' => ($options['selectChildren'] === API_OUTPUT_COUNT) ? [] : $options['selectChildren'], 'sortfield' => $options['sortfield'], 'sortorder' => $options['sortorder'], 'preservekeys' => true ], $permissions); foreach ($result as $serviceid => &$row) { $row['children'] = array_key_exists($serviceid, $relations) ? array_intersect_key($services, $relations[$serviceid]) : []; if ($options['selectChildren'] === API_OUTPUT_COUNT) { $row['children'] = (string) count($row['children']); } else { $row['children'] = array_values($row['children']); } } unset($row); } /** * @param array $options * @param array $result */ private static function addRelatedTags(array $options, array &$result): void { if ($options['selectTags'] === null) { return; } foreach ($result as &$row) { $row['tags'] = []; } unset($row); if ($options['selectTags'] === API_OUTPUT_COUNT) { $output = ['servicetagid', 'serviceid']; } elseif ($options['selectTags'] === API_OUTPUT_EXTEND) { $output = ['servicetagid', 'serviceid', 'tag', 'value']; } else { $output = array_unique(array_merge(['servicetagid', 'serviceid'], $options['selectTags'])); } $sql_options = [ 'output' => $output, 'filter' => ['serviceid' => array_keys($result)] ]; $db_tags = DBselect(DB::makeSql('service_tag', $sql_options)); while ($db_tag = DBfetch($db_tags)) { $serviceid = $db_tag['serviceid']; unset($db_tag['servicetagid'], $db_tag['serviceid']); $result[$serviceid]['tags'][] = $db_tag; } if ($options['selectTags'] === API_OUTPUT_COUNT) { foreach ($result as &$row) { $row['tags'] = (string) count($row['tags']); } unset($row); } } /** * @param array $options * @param array $result */ private static function addRelatedProblemTags(array $options, array &$result): void { if ($options['selectProblemTags'] === null) { return; } foreach ($result as &$row) { $row['problem_tags'] = []; } unset($row); if ($options['selectProblemTags'] === API_OUTPUT_COUNT) { $output = ['service_problem_tagid', 'serviceid']; } elseif ($options['selectProblemTags'] === API_OUTPUT_EXTEND) { $output = ['service_problem_tagid', 'serviceid', 'tag', 'operator', 'value']; } else { $output = array_unique(array_merge(['service_problem_tagid', 'serviceid'], $options['selectProblemTags'])); } $sql_options = [ 'output' => $output, 'filter' => ['serviceid' => array_keys($result)] ]; $db_problem_tags = DBselect(DB::makeSql('service_problem_tag', $sql_options)); while ($db_problem_tag = DBfetch($db_problem_tags)) { $serviceid = $db_problem_tag['serviceid']; unset($db_problem_tag['service_problem_tagid'], $db_problem_tag['serviceid']); $result[$serviceid]['problem_tags'][] = $db_problem_tag; } if ($options['selectProblemTags'] === API_OUTPUT_COUNT) { foreach ($result as &$row) { $row['problem_tags'] = (string) count($row['problem_tags']); } unset($row); } } /** * @param array $options * @param array $result * * @throws APIException */ private static function addRelatedProblemEvents(array $options, array &$result): void { if ($options['selectProblemEvents'] === null) { return; } $sql_options = [ 'output' => ['serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); $relations = []; while ($db_link = DBfetch($db_links)) { $relations[$db_link['serviceupid']][$db_link['servicedownid']] = true; } $services_without_children = []; $parents = $result; while ($parents) { $next_parents = []; foreach (array_keys($parents) as $serviceid) { if (array_key_exists($serviceid, $relations)) { $next_parents += $relations[$serviceid]; } else { $services_without_children[$serviceid] = true; } } $parents = $next_parents; } /* * Performance optimized: * - Not filtering the output by the related service IDs. */ $services = DB::select('services', [ 'output' => ['status', 'algorithm', 'weight', 'propagation_rule', 'propagation_value'], 'preservekeys' => true ]); foreach ($services as &$service) { $service['status_rules'] = []; } unset($service); $sql_options = [ 'output' => ['serviceid', 'type', 'limit_value', 'limit_status', 'new_status'], 'filter' => ['serviceid' => array_keys($services)] ]; $db_status_rules = DBselect(DB::makeSql('service_status_rule', $sql_options)); while ($db_status_rule = DBfetch($db_status_rules)) { $services[$db_status_rule['serviceid']]['status_rules'][] = $db_status_rule; } if ($options['selectProblemEvents'] === API_OUTPUT_COUNT) { $output = ['serviceid']; } elseif ($options['selectProblemEvents'] === API_OUTPUT_EXTEND) { $output = ['serviceid', 'eventid', 'severity', 'name']; } else { $output = array_unique(array_merge(['serviceid', 'eventid'], $options['selectProblemEvents'])); } $do_output_name = in_array('name', $output); if ($do_output_name) { $output = array_diff($output, ['name']); } $sql_options = [ 'output' => $output ]; $db_service_problems = DBselect(DB::makeSql('service_problem', $sql_options)); $service_problems = array_fill_keys(array_keys($services_without_children), []); while ($db_service_problem = DBfetch($db_service_problems)) { unset($db_service_problem['service_problemid']); $service_problems[$db_service_problem['serviceid']][] = $db_service_problem; } $problem_events = []; $problem_events_ungrouped = []; foreach (array_keys($result) as $serviceid) { $problem_events[$serviceid] = $services[$serviceid]['status'] != ZBX_SEVERITY_OK ? self::getProblemEvents((string) $serviceid, $services, $relations, $service_problems) : []; $problem_events_ungrouped += $problem_events[$serviceid]; } if ($do_output_name && $problem_events_ungrouped) { $events = API::Event()->get([ 'output' => ['name'], 'eventids' => array_keys($problem_events_ungrouped), 'source' => EVENT_SOURCE_TRIGGERS, 'object' => EVENT_OBJECT_TRIGGER, 'value' => TRIGGER_VALUE_TRUE, 'nopermissions' => true, 'preservekeys' => true ]); if (count($events) != count($problem_events_ungrouped)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } } foreach ($result as $serviceid => &$service) { if ($options['selectProblemEvents'] === API_OUTPUT_COUNT) { $service['problem_events'] = count($problem_events[$serviceid]); } else { $unset_fields = ['serviceid' => true]; if ($options['selectProblemEvents'] !== API_OUTPUT_EXTEND && !in_array('eventid', $options['selectProblemEvents'])) { $unset_fields['eventid'] = true; } $service['problem_events'] = []; foreach ($problem_events[$serviceid] as $eventid => $problem_event) { $problem_event = array_diff_key($problem_event, $unset_fields); if ($do_output_name) { $problem_event['name'] = $events[$eventid]['name']; } $service['problem_events'][] = $problem_event; } } } unset($service); } /** * @param array $options * @param array $result */ private static function addRelatedStatusRules(array $options, array &$result): void { if ($options['selectStatusRules'] === null) { return; } foreach ($result as &$row) { $row['status_rules'] = []; } unset($row); if ($options['selectStatusRules'] === API_OUTPUT_COUNT) { $output = ['service_status_ruleid', 'serviceid']; } elseif ($options['selectStatusRules'] === API_OUTPUT_EXTEND) { $output = ['service_status_ruleid', 'serviceid', 'type', 'limit_value', 'limit_status', 'new_status']; } else { $output = array_unique(array_merge(['service_status_ruleid', 'serviceid'], $options['selectStatusRules'])); } $sql_options = [ 'output' => $output, 'filter' => ['serviceid' => array_keys($result)] ]; $db_status_rules = DBselect(DB::makeSql('service_status_rule', $sql_options)); while ($db_status_rule = DBfetch($db_status_rules)) { $serviceid = $db_status_rule['serviceid']; unset($db_status_rule['service_status_ruleid'], $db_status_rule['serviceid']); $result[$serviceid]['status_rules'][] = $db_status_rule; } if ($options['selectStatusRules'] === API_OUTPUT_COUNT) { foreach ($result as &$row) { $row['status_rules'] = (string) count($row['status_rules']); } unset($row); } } /** * @param array $options * @param array $result */ private static function addRelatedStatusTimeline(array $options, array &$result): void { if ($options['selectStatusTimeline'] === null) { return; } foreach ($result as &$row) { $row['status_timeline'] = array_fill(0, count($options['selectStatusTimeline']), [ 'start_value' => ZBX_SEVERITY_OK, 'alarms' => [] ]); } unset($row); foreach ($options['selectStatusTimeline'] as $index => $period) { $db_alarms_start_resource = DBselect('SELECT sa.serviceid,sa.value'. ' FROM service_alarms sa'. ' JOIN ('. 'SELECT sa2.serviceid,MAX(sa2.clock) AS clock'. ' FROM service_alarms sa2'. ' WHERE '.dbConditionId('sa2.serviceid', array_keys($result)). ' AND sa2.clock<'.dbQuoteInt($period['period_from']). ' GROUP BY sa2.serviceid'. ') sa_max'. ' ON (sa.serviceid=sa_max.serviceid AND sa.clock=sa_max.clock)' ); while ($db_alarm_start = DBfetch($db_alarms_start_resource)) { $result[$db_alarm_start['serviceid']]['status_timeline'][$index]['start_value'] = $db_alarm_start['value']; } } $where_or = []; foreach ($options['selectStatusTimeline'] as $period) { $where_or[] = 'sa.clock BETWEEN '.dbQuoteInt($period['period_from']).' AND '.dbQuoteInt($period['period_to'] - 1); } $db_alarms_resource = DBselect('SELECT sa.serviceid,sa.clock,sa.value'. ' FROM service_alarms sa'. ' WHERE '.dbConditionId('sa.serviceid', array_keys($result)). ' AND ('.implode(' OR ', $where_or).')'. ' ORDER BY sa.clock, sa.servicealarmid' ); while ($db_alarm = DBfetch($db_alarms_resource)) { foreach ($options['selectStatusTimeline'] as $index => $period) { if ($db_alarm['clock'] >= $period['period_from'] && $db_alarm['clock'] < $period['period_to']) { $result[$db_alarm['serviceid']]['status_timeline'][$index]['alarms'][] = [ 'clock' => $db_alarm['clock'], 'value' => $db_alarm['value'] ]; } } } } /** * @param string $parent_serviceid * @param array $services * @param array $relations * @param array $service_problems * @param int|null $min_status * * @return array */ private static function getProblemEvents(string $parent_serviceid, array $services, array $relations, array $service_problems, int $min_status = null): array { $parent = $services[$parent_serviceid]; if (!array_key_exists($parent_serviceid, $relations)) { if ($min_status !== null) { $problem_events = array_filter($service_problems[$parent_serviceid], static function (array $problem) use ($min_status): bool { return $problem['severity'] >= $min_status; } ); } else { $problem_events = $service_problems[$parent_serviceid]; } return array_column($problem_events, null, 'eventid'); } $children = array_filter(array_intersect_key($services, $relations[$parent_serviceid]), static function (array $service): bool { return $service['propagation_rule'] != ZBX_SERVICE_STATUS_PROPAGATION_IGNORE; } ); $children_upstream_status = []; foreach ($children as $child_serviceid => $child) { if ($child['status'] == ZBX_SEVERITY_OK) { $status = ZBX_SEVERITY_OK; } else { switch ($child['propagation_rule']) { case ZBX_SERVICE_STATUS_PROPAGATION_INCREASE: $status = min(TRIGGER_SEVERITY_COUNT - 1, $child['status'] + $child['propagation_value']); break; case ZBX_SERVICE_STATUS_PROPAGATION_DECREASE: $status = max(TRIGGER_SEVERITY_NOT_CLASSIFIED, $child['status'] - $child['propagation_value']); break; case ZBX_SERVICE_STATUS_PROPAGATION_FIXED: $status = $child['propagation_value']; break; default: $status = $child['status']; break; } } $children_upstream_status[$child_serviceid] = $status; } $not_ok_children = array_intersect_key($children, array_filter($children_upstream_status, static function (int $status): bool { return $status != ZBX_SEVERITY_OK; } )); switch ($parent['algorithm']) { case ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ALL: if (count($not_ok_children) == count($children)) { $evaluate_children = array_fill_keys(array_keys($not_ok_children), null); $evaluate_additional_rules = false; } else { $evaluate_children = []; $evaluate_additional_rules = true; } break; case ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ONE: if ($min_status !== null) { $evaluate_children = []; foreach ($not_ok_children as $child_serviceid => $child) { if ($child['status'] < $min_status) { continue; } switch ($child['propagation_rule']) { case ZBX_SERVICE_STATUS_PROPAGATION_INCREASE: $reverse_min_status = max(TRIGGER_SEVERITY_NOT_CLASSIFIED, $min_status - $child['propagation_value'] ); break; case ZBX_SERVICE_STATUS_PROPAGATION_DECREASE: $reverse_min_status = min(TRIGGER_SEVERITY_COUNT - 1, $min_status + $child['propagation_value'] ); break; case ZBX_SERVICE_STATUS_PROPAGATION_FIXED: $reverse_min_status = null; break; default: $reverse_min_status = $min_status; break; } $evaluate_children[$child_serviceid] = $reverse_min_status; } $evaluate_additional_rules = true; } else { $evaluate_children = array_fill_keys(array_keys($not_ok_children), null); $evaluate_additional_rules = false; } break; default: $evaluate_children = []; $evaluate_additional_rules = true; } if ($evaluate_additional_rules) { foreach ($parent['status_rules'] as $status_rule) { if ($min_status !== null && $status_rule['new_status'] < $min_status) { continue; } $is_less_than = in_array($status_rule['type'], [ ZBX_SERVICE_STATUS_RULE_TYPE_N_L, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_W_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L ]); $is_weight = in_array($status_rule['type'], [ ZBX_SERVICE_STATUS_RULE_TYPE_W_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_W_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L ]); $is_percentage = in_array($status_rule['type'], [ ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_NP_L, ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE, ZBX_SERVICE_STATUS_RULE_TYPE_WP_L ]); $rule_children = []; foreach ($children as $child_serviceid => $child) { $status_matched = $is_less_than ? $children_upstream_status[$child_serviceid] > $status_rule['limit_status'] : $children_upstream_status[$child_serviceid] >= $status_rule['limit_status']; $weight_matched = !$is_weight || $child['weight'] > 0; if ($status_matched && $weight_matched) { $rule_children[$child_serviceid] = $child; } } if ($is_weight) { $value = 0; foreach ($rule_children as $child) { $value += $child['weight']; } $value_total = 0; foreach ($children as $child) { $value_total += $child['weight']; } } else { $value = count($rule_children); $value_total = count($children); } $limit_value = $is_percentage ? $status_rule['limit_value'] * $value_total / 100 : $status_rule['limit_value']; $rule_qualifies = $is_less_than ? $value_total - $value < $limit_value : $value >= $limit_value; if ($rule_qualifies) { $rule_min_status = $is_less_than ? $status_rule['limit_status'] + 1 : $status_rule['limit_status']; foreach ($rule_children as $child_serviceid => $child) { switch ($child['propagation_rule']) { case ZBX_SERVICE_STATUS_PROPAGATION_INCREASE: $reverse_rule_min_status = max(TRIGGER_SEVERITY_NOT_CLASSIFIED, $rule_min_status - $child['propagation_value'] ); break; case ZBX_SERVICE_STATUS_PROPAGATION_DECREASE: $reverse_rule_min_status = min(TRIGGER_SEVERITY_COUNT - 1, $rule_min_status + $child['propagation_value'] ); break; case ZBX_SERVICE_STATUS_PROPAGATION_FIXED: $reverse_rule_min_status = null; break; default: $reverse_rule_min_status = $rule_min_status; break; } if (array_key_exists($child_serviceid, $evaluate_children)) { if ($evaluate_children[$child_serviceid] !== null) { $evaluate_children[$child_serviceid] = min($evaluate_children[$child_serviceid], $reverse_rule_min_status ); } } else { $evaluate_children[$child_serviceid] = $reverse_rule_min_status; } } } } } $problem_events = []; foreach ($evaluate_children as $child_serviceid => $child_min_status) { $problem_events += self::getProblemEvents((string) $child_serviceid, $services, $relations, $service_problems, (int) $child_min_status ); } return $problem_events; } /** * @param array $services * @param array $db_services * * @throws APIException */ private static function checkParentChildRelations(array $services, array $db_services): void { $services = array_column($services, null, 'serviceid'); foreach ($services as $serviceid => $service) { if (!array_key_exists('parents', $service)) { continue; } $parent_services = array_intersect_key($services, array_column($service['parents'], 'serviceid', 'serviceid') ); foreach ($parent_services as $parent_serviceid => $parent_service) { if (!array_key_exists('children', $parent_service)) { continue; } $parent_child_services = array_column($parent_service['children'], 'serviceid', 'serviceid'); if (!array_key_exists($serviceid, $parent_child_services)) { self::exception(ZBX_API_ERROR_PARAMETERS, _s( 'Parent-child relation conflict in services "%1$s" and "%2$s".', $db_services[$parent_serviceid]['name'], $db_services[$serviceid]['name'] )); } } } } /** * Add the UUID to those of the given services that don't have the 'uuid' parameter set. * * @param array $services */ private static function addUuid(array &$services): void { foreach ($services as &$service) { if (!array_key_exists('uuid', $service)) { $service['uuid'] = generateUuidV4(); } } unset($service); } /** * Verify service UUIDs are not repeated. * * @param array $services * @param array|null $db_services * * @throws APIException */ private static function checkUuidDuplicates(array $services, array $db_services = null): void { $service_indexes = []; foreach ($services as $i => $service) { if (!array_key_exists('uuid', $service)) { continue; } if ($db_services === null || $service['uuid'] !== $db_services[$service['serviceid']]['uuid']) { $service_indexes[$service['uuid']] = $i; } } if (!$service_indexes) { return; } $duplicates = DB::select('services', [ 'output' => ['uuid'], 'filter' => [ 'uuid' => array_keys($service_indexes) ], 'limit' => 1 ]); if ($duplicates) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.($service_indexes[$duplicates[0]['uuid']] + 1), _('service with the same UUID already exists') ) ); } } /** * @param array $services * @param array|null $db_services * * @throws APIException */ private static function checkStatusPropagation(array $services, array $db_services = null): void { foreach ($services as $service) { $name = $db_services !== null ? $db_services[$service['serviceid']]['name'] : $service['name']; if (array_key_exists('propagation_rule', $service) && !array_key_exists('propagation_value', $service)) { self::exception(ZBX_API_ERROR_PARAMETERS, _s( 'Cannot specify "propagation_rule" parameter without specifying "propagation_value" parameter for service "%1$s".', $name )); } if (!array_key_exists('propagation_value', $service)) { continue; } if (array_key_exists('propagation_rule', $service)) { $propagation_rule = $service['propagation_rule']; } elseif ($db_services !== null) { $propagation_rule = $db_services[$service['serviceid']]['propagation_rule']; } else { $propagation_rule = DB::getDefault('services', 'propagation_rule'); } switch ($propagation_rule) { case ZBX_SERVICE_STATUS_PROPAGATION_INCREASE: case ZBX_SERVICE_STATUS_PROPAGATION_DECREASE: $propagation_values = range(1, TRIGGER_SEVERITY_COUNT - 1); break; case ZBX_SERVICE_STATUS_PROPAGATION_FIXED: $propagation_values = array_merge([ZBX_SEVERITY_OK], range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1) ); break; default: $propagation_values = [0]; break; } if (!in_array($service['propagation_value'], $propagation_values)) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incompatible "propagation_rule" and "propagation_value" parameters for service "%1$s".', $name) ); } } } /** * @param array $services * @param array|null $db_services * * @throws APIException */ private static function checkChildrenOrProblemTags(array $services, array $db_services = null): void { foreach ($services as $service) { $name = $db_services !== null ? $db_services[$service['serviceid']]['name'] : $service['name']; if (array_key_exists('problem_tags', $service)) { $has_problem_tags = count($service['problem_tags']) > 0; } elseif ($db_services !== null) { $has_problem_tags = count($db_services[$service['serviceid']]['problem_tags']) > 0; } else { $has_problem_tags = false; } if (array_key_exists('children', $service)) { $has_children = count($service['children']) > 0; } elseif ($db_services !== null) { $has_children = count($db_services[$service['serviceid']]['children']) > 0; } else { $has_children = false; } if ($has_problem_tags && $has_children) { self::exception(ZBX_API_ERROR_PARAMETERS, _s('Service "%1$s" cannot have problem tags and children at the same time.', $name) ); } } } /** * @param array $services * * @throws APIException */ private static function checkChildrenOrProblemTagsInParents(array $services): void { $parent_serviceids = []; foreach ($services as $service) { if (array_key_exists('parents', $service)) { $parent_serviceids += array_column($service['parents'], 'serviceid', 'serviceid'); } } if (!$parent_serviceids) { return; } $db_problem_tags = DB::select('service_problem_tag', [ 'output' => ['serviceid'], 'filter' => ['serviceid' => $parent_serviceids], 'limit' => 1 ]); if (!$db_problem_tags) { return; } $db_parent_services = DB::select('services', [ 'output' => ['name'], 'serviceids' => $db_problem_tags[0]['serviceid'] ]); self::exception(ZBX_API_ERROR_PARAMETERS, _s('Service "%1$s" cannot have problem tags and children at the same time.', $db_parent_services[0]['name'] ) ); } /** * @param array $services * @param array|null $db_services * * @throws APIException */ private static function checkCircularReferences(array $services, array $db_services = null): void { $add_references = []; $del_references = []; foreach ($services as $service) { if ($db_services !== null) { $db_service = $db_services[$service['serviceid']]; if (array_key_exists('parents', $service)) { foreach ($db_service['parents'] as $parent) { $del_references[$parent['serviceid']][$service['serviceid']] = true; } foreach ($service['parents'] as $parent) { $add_references[$parent['serviceid']][$service['serviceid']] = true; } } if (array_key_exists('children', $service)) { foreach ($db_service['children'] as $child) { $del_references[$service['serviceid']][$child['serviceid']] = true; } foreach ($service['children'] as $child) { $add_references[$service['serviceid']][$child['serviceid']] = true; } } } else if (array_key_exists('parents', $service) && array_key_exists('children', $service)) { foreach ($service['children'] as $child) { foreach ($service['parents'] as $parent) { $add_references[$parent['serviceid']][$child['serviceid']] = true; } } } } foreach (array_keys(array_intersect_key($add_references, $del_references)) as $parent_serviceid) { $common_references = array_intersect_key($add_references[$parent_serviceid], $del_references[$parent_serviceid] ); $add_references[$parent_serviceid] = array_diff_key($add_references[$parent_serviceid], $common_references); $del_references[$parent_serviceid] = array_diff_key($del_references[$parent_serviceid], $common_references); } if (self::hasCircularReferences($add_references, $del_references)) { self::exception(ZBX_API_ERROR_PARAMETERS, _('Services form a circular dependency.')); } } /** * @param array $add_references * @param array $del_references * * @return bool */ private static function hasCircularReferences(array $add_references, array $del_references): bool { $reverse_references = []; foreach ($add_references as $parent_serviceid => $children) { foreach (array_keys($children) as $child_serviceid) { $reverse_references[$child_serviceid][$parent_serviceid] = true; } } while ($add_references) { $db_links = DB::select('services_links', [ 'output' => ['serviceupid', 'servicedownid'], 'filter' => ['servicedownid' => array_keys($add_references)] ]); $db_parents = []; foreach ($db_links as $db_link) { if (!array_key_exists($db_link['serviceupid'], $del_references) || !array_key_exists($db_link['servicedownid'], $del_references[$db_link['serviceupid']])) { $db_parents[$db_link['servicedownid']][$db_link['serviceupid']] = true; } } $next_references = []; foreach ($add_references as $parent_serviceid => $children) { foreach (array_keys($children) as $child_serviceid) { if ((string) $child_serviceid === (string) $parent_serviceid) { return true; } if (array_key_exists($parent_serviceid, $reverse_references)) { foreach (array_keys($reverse_references[$parent_serviceid]) as $serviceid) { $next_references[$serviceid][$child_serviceid] = true; } } if (array_key_exists($parent_serviceid, $db_parents)) { foreach (array_keys($db_parents[$parent_serviceid]) as $serviceid) { $next_references[$serviceid][$child_serviceid] = true; } } } } $add_references = $next_references; } return false; } /** * @param array $services * @param array $db_services * @param array $permissions */ private static function addAffectedObjects(array $services, array &$db_services, array $permissions): void { self::addAffectedParentsAndChildren($db_services, $permissions); self::addAffectedTags($db_services); self::addAffectedProblemTags($db_services); self::addAffectedStatusRules($services, $db_services); } /** * @param array $db_services * @param array $permissions */ private static function addAffectedParentsAndChildren(array &$db_services, array $permissions): void { foreach ($db_services as &$db_service) { $db_service['parents'] = []; $db_service['children'] = []; } unset($db_service); if ($permissions['r_services'] === null || $permissions['rw_services'] === null) { $accessible_services = null; } else { $accessible_services = $permissions['r_services'] + $permissions['rw_services']; } $sql_options = [ 'output' => ['linkid', 'serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); while ($db_link = DBfetch($db_links)) { if (array_key_exists($db_link['servicedownid'], $db_services)) { if ($accessible_services === null || array_key_exists($db_link['serviceupid'], $accessible_services)) { $db_services[$db_link['servicedownid']]['parents'][$db_link['linkid']] = [ 'linkid' => $db_link['linkid'], 'serviceid' => $db_link['serviceupid'] ]; } } elseif (array_key_exists($db_link['serviceupid'], $db_services)) { $db_services[$db_link['serviceupid']]['children'][$db_link['linkid']] = [ 'linkid' => $db_link['linkid'], 'serviceid' => $db_link['servicedownid'] ]; } } } /** * @param array $db_services */ private static function addAffectedTags(array &$db_services): void { foreach ($db_services as &$db_service) { $db_service['tags'] = []; } unset($db_service); $sql_options = [ 'output' => ['servicetagid', 'serviceid', 'tag', 'value'], 'filter' => ['serviceid' => array_keys($db_services)] ]; $db_tags = DBselect(DB::makeSql('service_tag', $sql_options)); while ($db_tag = DBfetch($db_tags)) { $serviceid = $db_tag['serviceid']; unset($db_tag['serviceid']); $db_services[$serviceid]['tags'][$db_tag['servicetagid']] = $db_tag; } } /** * @param array $db_services */ private static function addAffectedProblemTags(array &$db_services): void { foreach ($db_services as &$db_service) { $db_service['problem_tags'] = []; } unset($db_service); $sql_options = [ 'output' => ['service_problem_tagid', 'serviceid', 'tag', 'operator', 'value'], 'filter' => ['serviceid' => array_keys($db_services)] ]; $db_problem_tags = DBselect(DB::makeSql('service_problem_tag', $sql_options)); while ($db_problem_tag = DBfetch($db_problem_tags)) { $serviceid = $db_problem_tag['serviceid']; unset($db_problem_tag['serviceid']); $db_services[$serviceid]['problem_tags'][$db_problem_tag['service_problem_tagid']] = $db_problem_tag; } } /** * @param array $services * @param array $db_services */ private static function addAffectedStatusRules(array $services, array &$db_services): void { $affected_serviceids = []; foreach ($services as $service) { if (array_key_exists('status_rules', $service)) { $affected_serviceids[$service['serviceid']] = true; $db_services[$service['serviceid']]['status_rules'] = []; } } $sql_options = [ 'output' => ['service_status_ruleid', 'serviceid', 'type', 'limit_value', 'limit_status', 'new_status'], 'filter' => ['serviceid' => array_keys($affected_serviceids)] ]; $db_status_rules = DBselect(DB::makeSql('service_status_rule', $sql_options)); while ($db_status_rule = DBfetch($db_status_rules)) { $serviceid = $db_status_rule['serviceid']; unset($db_status_rule['serviceid']); $db_services[$serviceid]['status_rules'][$db_status_rule['service_status_ruleid']] = $db_status_rule; } } /** * @param array $services * @param array|null $db_services */ private static function updateTags(array &$services, array $db_services = null): void { $ins_tags = []; $del_tags = []; foreach ($services as &$service) { if (!array_key_exists('tags', $service)) { continue; } $db_tags = []; if ($db_services !== null) { foreach ($db_services[$service['serviceid']]['tags'] as $db_tag) { $db_tags[$db_tag['tag']][$db_tag['value']] = $db_tag['servicetagid']; $del_tags[$db_tag['servicetagid']] = true; } } foreach ($service['tags'] as &$tag) { if (array_key_exists($tag['tag'], $db_tags) && array_key_exists($tag['value'], $db_tags[$tag['tag']])) { $tag['servicetagid'] = $db_tags[$tag['tag']][$tag['value']]; unset($del_tags[$tag['servicetagid']]); } else { $ins_tags[] = ['serviceid' => $service['serviceid']] + $tag; } } unset($tag); } unset($service); if ($del_tags) { DB::delete('service_tag', ['servicetagid' => array_keys($del_tags)]); } if ($ins_tags) { $servicetagids = DB::insert('service_tag', $ins_tags); $servicetagids_index = 0; foreach ($services as &$service) { if (!array_key_exists('tags', $service)) { continue; } foreach ($service['tags'] as &$tag) { if (array_key_exists('servicetagid', $tag)) { continue; } $tag['servicetagid'] = $servicetagids[$servicetagids_index]; $servicetagids_index++; } unset($tag); } unset($service); } } /** * @param array $services * @param array|null $db_services */ private static function updateProblemTags(array &$services, array $db_services = null): void { $ins_problem_tags = []; $del_problem_tags = []; foreach ($services as &$service) { if (!array_key_exists('problem_tags', $service)) { continue; } $db_problem_tags = []; if ($db_services !== null) { foreach ($db_services[$service['serviceid']]['problem_tags'] as $db_problem_tag) { $db_problem_tags[$db_problem_tag['tag']][$db_problem_tag['operator']][$db_problem_tag['value']] = $db_problem_tag['service_problem_tagid']; $del_problem_tags[$db_problem_tag['service_problem_tagid']] = true; } } foreach ($service['problem_tags'] as &$problem_tag) { if (array_key_exists($problem_tag['tag'], $db_problem_tags) && array_key_exists($problem_tag['operator'], $db_problem_tags[$problem_tag['tag']]) && array_key_exists($problem_tag['value'], $db_problem_tags[$problem_tag['tag']][$problem_tag['operator']] )) { $problem_tag['service_problem_tagid'] = $db_problem_tags[$problem_tag['tag']][$problem_tag['operator']][$problem_tag['value']]; unset($del_problem_tags[$problem_tag['service_problem_tagid']]); } else { $ins_problem_tags[] = ['serviceid' => $service['serviceid']] + $problem_tag; } } unset($problem_tag); } unset($service); if ($del_problem_tags) { DB::delete('service_problem_tag', ['service_problem_tagid' => array_keys($del_problem_tags)]); } if ($ins_problem_tags) { $service_problem_tagids = DB::insert('service_problem_tag', $ins_problem_tags); $service_problem_tagids_index = 0; foreach ($services as &$service) { if (!array_key_exists('problem_tags', $service)) { continue; } foreach ($service['problem_tags'] as &$problem_tag) { if (array_key_exists('service_problem_tagid', $problem_tag)) { continue; } $problem_tag['service_problem_tagid'] = $service_problem_tagids[$service_problem_tagids_index]; $service_problem_tagids_index++; } unset($problem_tag); } unset($service); } } /** * @param array $services * @param array|null $db_services */ private static function updateParents(array &$services, array $db_services = null): void { $ins_parents = []; $del_parents = []; foreach ($services as &$service) { if (!array_key_exists('parents', $service)) { continue; } $db_parents = []; if ($db_services !== null) { foreach ($db_services[$service['serviceid']]['parents'] as $db_parent) { $db_parents[$db_parent['serviceid']] = $db_parent['linkid']; $del_parents[$db_parent['linkid']] = true; } } foreach ($service['parents'] as &$parent) { if (array_key_exists($parent['serviceid'], $db_parents)) { $parent['linkid'] = $db_parents[$parent['serviceid']]; unset($del_parents[$parent['linkid']]); } else { $ins_parents[] = ['servicedownid' => $service['serviceid'], 'serviceupid' => $parent['serviceid']]; } } unset($parent); } unset($service); if ($del_parents) { DB::delete('services_links', ['linkid' => array_keys($del_parents)]); } if ($ins_parents) { $linkids = DB::insert('services_links', $ins_parents); $linkids_index = 0; foreach ($services as &$service) { if (!array_key_exists('parents', $service)) { continue; } foreach ($service['parents'] as &$parent) { if (array_key_exists('linkid', $parent)) { continue; } $parent['linkid'] = $linkids[$linkids_index]; $linkids_index++; } unset($parent); } unset($service); } } /** * @param array $services * @param array|null $db_services */ private static function updateChildren(array &$services, array $db_services = null): void { $ins_children = []; $del_children = []; foreach ($services as &$service) { if (!array_key_exists('children', $service)) { continue; } $db_children = []; if ($db_services !== null) { foreach ($db_services[$service['serviceid']]['children'] as $db_child) { $db_children[$db_child['serviceid']] = $db_child['linkid']; $del_children[$db_child['linkid']] = true; } } foreach ($service['children'] as &$child) { if (array_key_exists($child['serviceid'], $db_children)) { $child['linkid'] = $db_children[$child['serviceid']]; unset($del_children[$child['linkid']]); } else { $ins_children[] = ['serviceupid' => $service['serviceid'], 'servicedownid' => $child['serviceid']]; } } unset($child); } unset($service); if ($del_children) { DB::delete('services_links', ['linkid' => array_keys($del_children)]); } if ($ins_children) { $linkids = DB::insert('services_links', $ins_children); $linkids_index = 0; foreach ($services as &$service) { if (!array_key_exists('children', $service)) { continue; } foreach ($service['children'] as &$child) { if (array_key_exists('linkid', $child)) { continue; } $child['linkid'] = $linkids[$linkids_index]; $linkids_index++; } unset($child); } unset($service); } } /** * @param array $services * @param array|null $db_services */ private static function updateStatusRules(array &$services, array $db_services = null): void { $ins_status_rules = []; $upd_status_rules = []; $del_status_rules = []; foreach ($services as &$service) { if (!array_key_exists('status_rules', $service)) { continue; } $db_status_rules = []; if ($db_services !== null) { foreach ($db_services[$service['serviceid']]['status_rules'] as $db_status_rule) { $db_status_rules[$db_status_rule['type']][$db_status_rule['limit_value']] [$db_status_rule['limit_status']] = $db_status_rule; $del_status_rules[$db_status_rule['service_status_ruleid']] = true; } } foreach ($service['status_rules'] as &$status_rule) { if (array_key_exists($status_rule['type'], $db_status_rules) && array_key_exists($status_rule['limit_value'], $db_status_rules[$status_rule['type']]) && array_key_exists($status_rule['limit_status'], $db_status_rules[$status_rule['type']][$status_rule['limit_value']] )) { $status_rule['service_status_ruleid'] = $db_status_rules[$status_rule['type']] [$status_rule['limit_value']][$status_rule['limit_status']]['service_status_ruleid']; unset($del_status_rules[$status_rule['service_status_ruleid']]); $upd_status_rule = DB::getUpdatedValues('service_status_rule', $status_rule, $db_status_rules [$status_rule['type']][$status_rule['limit_value']][$status_rule['limit_status']] ); if ($upd_status_rule) { $upd_status_rules[] = [ 'values' => $upd_status_rule, 'where' => ['service_status_ruleid' => $status_rule['service_status_ruleid']] ]; } } else { $ins_status_rules[] = ['serviceid' => $service['serviceid']] + $status_rule; } } unset($status_rule); } unset($service); if ($del_status_rules) { DB::delete('service_status_rule', ['service_status_ruleid' => array_keys($del_status_rules)]); } if ($ins_status_rules) { $service_status_ruleids = DB::insert('service_status_rule', $ins_status_rules); $service_status_ruleids_index = 0; foreach ($services as &$service) { if (!array_key_exists('status_rules', $service)) { continue; } foreach ($service['status_rules'] as &$status_rule) { if (array_key_exists('service_status_ruleid', $status_rule)) { continue; } $status_rule['service_status_ruleid'] = $service_status_ruleids[$service_status_ruleids_index]; $service_status_ruleids_index++; } unset($status_rule); } unset($service); } if ($upd_status_rules) { DB::update('service_status_rule', $upd_status_rules); } } /** * @throws APIException * * @return array */ private static function getPermissions(): array { $role = API::Role()->get([ 'output' => [], 'selectRules' => ['services.read.mode', 'services.read.list', 'services.read.tag', 'services.write.mode', 'services.write.list', 'services.write.tag' ], 'roleids' => self::$userData['roleid'] ]); if (!$role) { return [ 'r_services' => [], 'rw_services' => [], 'root_services' => [], 'rw_tag' => ['tag' => '', 'value' => ''] ]; } $rules = $role[0]['rules']; if ($rules['services.write.mode'] == ZBX_ROLE_RULE_SERVICES_ACCESS_ALL) { return [ 'r_services' => null, 'rw_services' => null, 'root_services' => null, 'rw_tag' => ['tag' => '', 'value' => ''] ]; } if ($rules['services.read.mode'] == ZBX_ROLE_RULE_SERVICES_ACCESS_ALL) { $r_services = null; } else { $r_services = array_column($rules['services.read.list'], 'serviceid', 'serviceid'); if ($rules['services.read.tag']['tag'] !== '') { $tags = DB::select('service_tag', [ 'output' => ['serviceid'], 'filter' => $rules['services.read.tag']['value'] !== '' ? ['tag' => $rules['services.read.tag']['tag'], 'value' => $rules['services.read.tag']['value']] : ['tag' => $rules['services.read.tag']['tag']] ]); $r_services += array_column($tags, 'serviceid', 'serviceid'); } } $rw_services = array_fill_keys(array_column($rules['services.write.list'], 'serviceid'), null); if ($rules['services.write.tag']['tag'] !== '') { $tags = DB::select('service_tag', [ 'output' => ['serviceid'], 'filter' => $rules['services.write.tag']['value'] !== '' ? ['tag' => $rules['services.write.tag']['tag'], 'value' => $rules['services.write.tag']['value']] : ['tag' => $rules['services.write.tag']['tag']] ]); $rw_services += array_fill_keys(array_column($tags, 'serviceid'), 0); } $sql_options = [ 'output' => ['serviceupid', 'servicedownid'] ]; $db_links = DBselect(DB::makeSql('services_links', $sql_options)); $relations = []; while ($db_link = DBfetch($db_links)) { $relations[$db_link['serviceupid']][$db_link['servicedownid']] = true; } $root_r_services = $r_services; $root_rw_services = $rw_services; if ($r_services !== null) { $services = $r_services; while ($services) { $_services = []; foreach (array_intersect_key($relations, $services) as $child_services) { $root_r_services = array_diff_key($root_r_services, $child_services); $root_rw_services = array_diff_key($root_rw_services, $child_services); $r_services += $child_services; $_services += $child_services; } $services = $_services; } $r_services = array_diff_key($r_services, $rw_services); } $services = $rw_services; while ($services) { $_services = []; foreach (array_intersect_key($relations, $services) as $child_services) { if ($root_r_services !== null) { $root_r_services = array_diff_key($root_r_services, $child_services); } $root_rw_services = array_diff_key($root_rw_services, $child_services); $_services += $child_services; foreach (array_keys($child_services) as $serviceid) { if (array_key_exists($serviceid, $rw_services)) { if ($rw_services[$serviceid] !== null) { $rw_services[$serviceid]++; } } else { $rw_services[$serviceid] = 1; } } } $services = $_services; } $root_services = $root_r_services === null ? null : $root_r_services + $root_rw_services; return [ 'r_services' => $r_services, 'rw_services' => $rw_services, 'root_services' => $root_services, 'rw_tag' => $rules['services.write.tag'] ]; } /** * @param array $permissions * @param array $services * @param array|null $db_services * * @throws APIException */ private static function checkPermissions(array $permissions, array $services, array $db_services = null): void { [ 'r_services' => $r_services, 'rw_services' => $rw_services, 'rw_tag' => $rw_tag ] = $permissions; if ($r_services === null || $rw_services === null) { $accessible_services = null; } else { $accessible_services = $r_services + $rw_services; } $referred_services = $db_services !== null ? array_column($services, 'serviceid', 'serviceid') : []; foreach ($services as $service) { if (array_key_exists('parents', $service)) { $referred_services += array_column($service['parents'], 'serviceid', 'serviceid'); } if (array_key_exists('children', $service)) { $referred_services += array_column($service['children'], 'serviceid', 'serviceid'); } } if ($referred_services) { if ($accessible_services !== null) { if (array_diff_key($referred_services, $accessible_services)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } } else { $count = DB::select('services', [ 'countOutput' => true, 'serviceids' => $referred_services ]); if ($count != count($referred_services)) { self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!') ); } } } if ($rw_services === null) { return; } foreach ($services as $service) { $name = $db_services !== null ? $db_services[$service['serviceid']]['name'] : $service['name']; if ($db_services !== null && !array_key_exists($service['serviceid'], $rw_services)) { $error_detail = _('read-write access to the service is required'); $error = _s('Cannot update service "%1$s": %2$s.', $name, $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } $is_rw_service = $db_services !== null && array_key_exists($service['serviceid'], $rw_services) && $rw_services[$service['serviceid']] === null; if (!$is_rw_service) { $has_rw_tag = false; if ($rw_tag['tag'] !== '') { if (array_key_exists('tags', $service)) { $tags = $service['tags']; } elseif ($db_services !== null) { $tags = $db_services[$service['serviceid']]['tags']; } else { $tags = []; } foreach ($tags as $tag) { if ($tag['tag'] === $rw_tag['tag'] && ($tag['value'] === $rw_tag['value'] || $rw_tag['value'] === '')) { $has_rw_tag = true; break; } } } if ($has_rw_tag && $db_services !== null) { $rw_services[$service['serviceid']] = null; } $is_rw_service = $has_rw_tag; } if (!$is_rw_service) { if (array_key_exists('parents', $service)) { $parent_services = array_column($service['parents'], 'serviceid', 'serviceid'); $has_rw_parents = (bool) array_intersect_key($parent_services, $rw_services); if ($has_rw_parents && $db_services !== null) { $rw_services[$service['serviceid']] = null; } } else { $has_rw_parents = $db_services !== null; } $is_rw_service = $has_rw_parents; } if (!$is_rw_service) { $error_detail = $db_services !== null ? _('read-write access to the service must be retained') : _('read-write access to the service is required'); $error = $db_services !== null ? _s('Cannot update service "%1$s": %2$s.', $name, $error_detail) : _s('Cannot create service "%1$s": %2$s.', $name, $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } if (array_key_exists('children', $service)) { $new_child_services = array_column($service['children'], 'serviceid', 'serviceid'); $old_child_services = $db_services !== null ? array_column($db_services[$service['serviceid']]['children'], 'serviceid', 'serviceid') : []; $inaccessible_services = array_diff_key($new_child_services, $rw_services); if ($inaccessible_services) { $inaccessible_service = DB::select('services', [ 'output' => ['name'], 'serviceids' => array_keys($inaccessible_services)[0] ])[0]; $error_detail = _s('read-write access to the child service "%1$s" is required', $inaccessible_service['name'] ); $error = $db_services !== null ? _s('Cannot update service "%1$s": %2$s.', $name, $error_detail) : _s('Cannot create service "%1$s": %2$s.', $name, $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } if ($db_services !== null) { foreach (array_keys(array_diff_key($old_child_services, $new_child_services)) as $serviceid) { if ($rw_services[$serviceid] !== null) { $rw_services[$serviceid]--; } } foreach (array_keys(array_diff_key($new_child_services, $old_child_services)) as $serviceid) { if ($rw_services[$serviceid] !== null) { $rw_services[$serviceid]++; } } } } } if ($db_services !== null) { foreach ($rw_services as $serviceid => $num_rw_parents) { if ($num_rw_parents === null || $num_rw_parents > 0) { continue; } $inaccessible_service = DB::select('services', [ 'output' => ['name'], 'serviceids' => $serviceid ])[0]; $error_detail = _('read-write access to the service must be retained'); $error = _s('Cannot update service "%1$s": %2$s.', $inaccessible_service['name'], $error_detail); self::exception(ZBX_API_ERROR_PERMISSIONS, $error); } } } }