1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * Class containing methods for operations with correlations. 24 */ 25class CCorrelation extends CApiService { 26 27 protected $tableName = 'correlation'; 28 protected $tableAlias = 'c'; 29 protected $sortColumns = ['correlationid', 'name', 'status']; 30 31 /** 32 * Set correlation default options in addition to global options. 33 */ 34 public function __construct() { 35 parent::__construct(); 36 37 $this->getOptions = array_merge($this->getOptions, [ 38 'selectFilter' => null, 39 'selectOperations' => null, 40 'correlationids' => null, 41 'editable' => false, 42 'sortfield' => '', 43 'sortorder' => '' 44 ]); 45 } 46 47 /** 48 * Get correlation data. 49 * 50 * @param array $options 51 * 52 * @return array|string 53 */ 54 public function get($options = []) { 55 $options = zbx_array_merge($this->getOptions, $options); 56 57 if ($options['editable'] && self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 58 return ($options['countOutput'] && !$options['groupCount']) ? '0' : []; 59 } 60 61 $res = DBselect($this->createSelectQuery($this->tableName(), $options), $options['limit']); 62 63 $result = []; 64 while ($row = DBfetch($res)) { 65 if ($options['countOutput']) { 66 if ($options['groupCount']) { 67 $result[] = $row; 68 } 69 else { 70 $result = $row['rowscount']; 71 } 72 } 73 else { 74 $result[$row[$this->pk()]] = $row; 75 } 76 } 77 78 if ($options['countOutput']) { 79 return $result; 80 } 81 82 if ($result) { 83 $result = $this->addRelatedObjects($options, $result); 84 85 foreach ($result as &$correlation) { 86 // Unset the fields that are returned in the filter. 87 unset($correlation['formula'], $correlation['evaltype']); 88 89 if ($options['selectFilter'] !== null) { 90 $filter = $this->unsetExtraFields( 91 [$correlation['filter']], 92 ['conditions', 'formula', 'evaltype'], 93 $options['selectFilter'] 94 ); 95 $filter = reset($filter); 96 97 if (array_key_exists('conditions', $filter)) { 98 foreach ($filter['conditions'] as &$condition) { 99 unset($condition['correlationid'], $condition['corr_conditionid']); 100 } 101 unset($condition); 102 } 103 104 $correlation['filter'] = $filter; 105 } 106 } 107 unset($correlation); 108 } 109 110 // removing keys (hash -> array) 111 if (!$options['preservekeys']) { 112 $result = zbx_cleanHashes($result); 113 } 114 115 return $result; 116 } 117 118 /** 119 * Add correlations. 120 * 121 * @param array $correlations An array of correlations. 122 * @param string $correlations[]['name'] Correlation name. 123 * @param string $correlations[]['description'] Correlation description (optional). 124 * @param int $correlations[]['status'] Correlation status (optional). 125 * Possible values are: 126 * 0 - ZBX_CORRELATION_ENABLED; 127 * 1 - ZBX_CORRELATION_DISABLED. 128 * @param array $correlations[]['filter'] Correlation filter that contains evaluation 129 * method, formula and conditions. 130 * @param int $correlations[]['filter']['evaltype'] Correlation condition evaluation method. 131 * Possible values are: 132 * 0 - CONDITION_EVAL_TYPE_AND_OR; 133 * 1 - CONDITION_EVAL_TYPE_AND; 134 * 2 - CONDITION_EVAL_TYPE_OR; 135 * 3 - CONDITION_EVAL_TYPE_EXPRESSION. 136 * @param string $correlations[]['filter']['formula'] User-defined expression to be used for 137 * evaluating conditions of filters with a 138 * custom expression. Optional, but required 139 * when evaluation method is: 140 * 3 - CONDITION_EVAL_TYPE_EXPRESSION. 141 * @param array $correlations[]['filter']['conditions'] An array of correlation conditions. 142 * @param int $correlations[]['filter']['conditions'][]['type'] Condition type. 143 * Possible values are: 144 * 0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG; 145 * 1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG; 146 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 147 * 3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR; 148 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 149 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 150 * @param string $correlations[]['filter']['conditions'][]['formulaid'] Condition formula ID. Optional, but required 151 * when evaluation method is: 152 * 3 - CONDITION_EVAL_TYPE_EXPRESSION. 153 * @param string $correlations[]['filter']['conditions'][]['tag'] Correlation condition tag. 154 * @param int $correlations[]['filter']['conditions'][]['operator'] Correlation condition operator. Optional, 155 * but required when "type" is one of the following: 156 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 157 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 158 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 159 * Possible values depend on type: 160 * for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 161 * 0 - CONDITION_OPERATOR_EQUAL 162 * 1 - CONDITION_OPERATOR_NOT_EQUAL 163 * for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE 164 * or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 165 * 0 - CONDITION_OPERATOR_EQUAL; 166 * 1 - CONDITION_OPERATOR_NOT_EQUAL; 167 * 2 - CONDITION_OPERATOR_LIKE; 168 * 3 - CONDITION_OPERATOR_NOT_LIKE. 169 * @param string $correlations[]['filter']['conditions'][]['groupid'] Correlation host group ID. Optional, but 170 * required when "type" is: 171 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP. 172 * @param string $correlations[]['filter']['conditions'][]['newtag'] Correlation condition new (current) tag. 173 * @param string $correlations[]['filter']['conditions'][]['oldtag'] Correlation condition old (target/matching) 174 * tag. 175 * @param string $correlations[]['filter']['conditions'][]['value'] Correlation condition tag value (optional). 176 * @param array $correlations[]['operations'] An array of correlation operations. 177 * @param int $correlations[]['operations'][]['type'] Correlation operation type. 178 * Possible values are: 179 * 0 - ZBX_CORR_OPERATION_CLOSE_OLD; 180 * 1 - ZBX_CORR_OPERATION_CLOSE_NEW. 181 * 182 * @return array 183 */ 184 public function create($correlations) { 185 $correlations = zbx_toArray($correlations); 186 187 $this->validateCreate($correlations); 188 189 foreach ($correlations as &$correlation) { 190 $correlation['evaltype'] = $correlation['filter']['evaltype']; 191 unset($correlation['formula']); 192 } 193 unset($correlation); 194 195 // Insert correlations into DB, get back array with new correlation IDs. 196 $correlations = DB::save('correlation', $correlations); 197 $correlations = zbx_toHash($correlations, 'correlationid'); 198 199 $conditions_to_create = []; 200 $operations_to_create = []; 201 202 // Collect conditions and operations to be created and set appropriate correlation ID. 203 foreach ($correlations as $correlationid => &$correlation) { 204 foreach ($correlation['filter']['conditions'] as $condition) { 205 $condition['correlationid'] = $correlationid; 206 $conditions_to_create[] = $condition; 207 } 208 209 foreach ($correlation['operations'] as $operation) { 210 $operation['correlationid'] = $correlationid; 211 $operations_to_create[] = $operation; 212 } 213 } 214 unset($correlation); 215 216 $conditions = $this->addConditions($conditions_to_create); 217 218 // Group back created correlation conditions by correlation ID to be used for updating correlation formula. 219 $conditions_for_correlations = []; 220 foreach ($conditions as $condition) { 221 $conditions_for_correlations[$condition['correlationid']][$condition['corr_conditionid']] = $condition; 222 } 223 224 // Update "formula" field if evaluation method is a custom expression. 225 foreach ($correlations as $correlationid => $correlation) { 226 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 227 $this->updateFormula($correlationid, $correlation['filter']['formula'], 228 $conditions_for_correlations[$correlationid] 229 ); 230 } 231 } 232 233 DB::save('corr_operation', $operations_to_create); 234 235 return ['correlationids' => array_keys($correlations)]; 236 } 237 238 /** 239 * Update correlations. 240 * 241 * @param array $correlations An array of correlations. 242 * @param string $correlations[]['name'] Correlation name (optional). 243 * @param string $correlations[]['description'] Correlation description (optional). 244 * @param int $correlations[]['status'] Correlation status (optional). 245 * Possible values are: 246 * 0 - ZBX_CORRELATION_ENABLED; 247 * 1 - ZBX_CORRELATION_DISABLED. 248 * @param array $correlations[]['filter'] Correlation filter that contains evaluation 249 * method, formula and conditions. 250 * @param int $correlations[]['filter']['evaltype'] Correlation condition evaluation 251 * method (optional). 252 * Possible values are: 253 * 0 - CONDITION_EVAL_TYPE_AND_OR; 254 * 1 - CONDITION_EVAL_TYPE_AND; 255 * 2 - CONDITION_EVAL_TYPE_OR; 256 * 3 - CONDITION_EVAL_TYPE_EXPRESSION. 257 * @param string $correlations[]['filter']['formula'] User-defined expression to be used for 258 * evaluating conditions of filters with a 259 * custom expression. Optional, but required 260 * when evaluation method is changed to 261 * CONDITION_EVAL_TYPE_EXPRESSION (or remains the same) 262 * and new conditions are set. 263 * @param array $correlations[]['filter']['conditions'] An array of correlation conditions (optional). 264 * @param int $correlations[]['filter']['conditions'][]['type'] Condition type. Optional, but required when 265 * new conditions are set. 266 * Possible values are: 267 * 0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG; 268 * 1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG; 269 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 270 * 3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR; 271 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 272 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 273 * @param string $correlations[]['filter']['conditions'][]['formulaid'] Condition formula ID. Optional, but required 274 * when evaluation method is changed to 275 * CONDITION_EVAL_TYPE_EXPRESSION (or remains the same) 276 * and new conditions are set. 277 * @param string $correlations[]['filter']['conditions'][]['tag'] Correlation condition tag. 278 * @param int $correlations[]['filter']['conditions'][]['operator'] Correlation condition operator. Optional, 279 * but required when "type" is changed to one 280 * of the following: 281 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 282 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 283 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 284 * Possible values depend on type: 285 * for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 286 * 0 - CONDITION_OPERATOR_EQUAL 287 * 1 - CONDITION_OPERATOR_NOT_EQUAL 288 * for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE 289 * or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 290 * 0 - CONDITION_OPERATOR_EQUAL; 291 * 1 - CONDITION_OPERATOR_NOT_EQUAL; 292 * 2 - CONDITION_OPERATOR_LIKE; 293 * 3 - CONDITION_OPERATOR_NOT_LIKE. 294 * @param string $correlations[]['filter']['conditions'][]['groupid'] Correlation host group ID. Optional, but 295 * required when "type" is changed to: 296 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP. 297 * @param string $correlations[]['filter']['conditions'][]['newtag'] Correlation condition new (current) tag. 298 * @param string $correlations[]['filter']['conditions'][]['oldtag'] Correlation condition old (target/matching) 299 * tag. 300 * @param string $correlations[]['filter']['conditions'][]['value'] Correlation condition tag value (optional). 301 * @param array $correlations[]['operations'] An array of correlation operations (optional). 302 * @param int $correlations[]['operations'][]['type'] Correlation operation type (optional). 303 * Possible values are: 304 * 0 - ZBX_CORR_OPERATION_CLOSE_OLD; 305 * 1 - ZBX_CORR_OPERATION_CLOSE_NEW. 306 * 307 * @return array 308 */ 309 public function update($correlations) { 310 $correlations = zbx_toArray($correlations); 311 $db_correlations = []; 312 313 $this->validateUpdate($correlations, $db_correlations); 314 315 $correlations_to_update = []; 316 $conditions_to_create = []; 317 $conditions_to_delete = []; 318 $operations_to_create = []; 319 $operations_to_delete = []; 320 321 foreach ($correlations as $correlation) { 322 $correlationid = $correlation['correlationid']; 323 324 unset($correlation['evaltype'], $correlation['formula'], $correlation['correlationid']); 325 326 $db_correlation = $db_correlations[$correlationid]; 327 328 // Remove fields that have not been changed for correlations. 329 if (array_key_exists('name', $correlation) && $correlation['name'] === $db_correlation['name']) { 330 unset($correlation['name']); 331 } 332 333 if (array_key_exists('description', $correlation) 334 && $correlation['description'] === $db_correlation['description']) { 335 unset($correlation['description']); 336 } 337 338 if (array_key_exists('status', $correlation) && $correlation['status'] == $db_correlation['status']) { 339 unset($correlation['status']); 340 } 341 342 $evaltype_changed = false; 343 344 // If the filter is set, something might have changed. 345 if (array_key_exists('filter', $correlation)) { 346 // Delete old correlation conditions and create new conditions. 347 if (array_key_exists('conditions', $correlation['filter'])) { 348 $conditions_to_delete[$correlationid] = true; 349 350 foreach ($correlation['filter']['conditions'] as $condition) { 351 $condition['correlationid'] = $correlationid; 352 $conditions_to_create[] = $condition; 353 } 354 } 355 356 // Check if evaltype has changed. 357 if (array_key_exists('evaltype', $correlation['filter'])) { 358 if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) { 359 // Clear formula field evaluation method if evaltype has changed. 360 $correlation['evaltype'] = $correlation['filter']['evaltype']; 361 362 if ($correlation['evaltype'] != CONDITION_EVAL_TYPE_EXPRESSION) { 363 $correlation['formula'] = ''; 364 } 365 } 366 } 367 } 368 369 // Delete old correlation operations and create new operations. 370 if (array_key_exists('operations', $correlation)) { 371 $operations_to_delete[$correlationid] = true; 372 373 foreach ($correlation['operations'] as $operation) { 374 $operation['correlationid'] = $correlationid; 375 $operations_to_create[] = $operation; 376 } 377 } 378 379 // Add values only if something is set for update. 380 if (array_key_exists('name', $correlation) 381 || array_key_exists('description', $correlation) 382 || array_key_exists('status', $correlation) 383 || array_key_exists('evaltype', $correlation)) { 384 $correlations_to_update[] = [ 385 'values' => $correlation, 386 'where' => ['correlationid' => $correlationid] 387 ]; 388 } 389 } 390 391 // Update correlations. 392 if ($correlations_to_update) { 393 DB::update('correlation', $correlations_to_update); 394 } 395 396 // Update conditions. Delete the old ones and create new ones. 397 if ($conditions_to_delete) { 398 DB::delete('corr_condition', ['correlationid' => array_keys($conditions_to_delete)]); 399 } 400 401 if ($conditions_to_create) { 402 $conditions = $this->addConditions($conditions_to_create); 403 404 // Group back created correlation conditions by correlation ID to be used for updating correlation formula. 405 $conditions_for_correlations = []; 406 foreach ($conditions as $condition) { 407 $conditions_for_correlations[$condition['correlationid']][$condition['corr_conditionid']] = $condition; 408 } 409 410 // Update "formula" field if evaluation method is a custom expression. 411 foreach ($correlations as $correlation) { 412 if (array_key_exists('filter', $correlation)) { 413 $db_correlation = $db_correlations[$correlation['correlationid']]; 414 415 // Check if evaluation method has changed. 416 if (array_key_exists('evaltype', $correlation['filter'])) { 417 if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) { 418 // The new evaluation method will be saved to DB. 419 $correlation['evaltype'] = $correlation['filter']['evaltype']; 420 421 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 422 // When evaluation method has changed, update the custom formula. 423 $this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'], 424 $conditions_for_correlations[$correlation['correlationid']] 425 ); 426 } 427 } 428 else { 429 /* 430 * The evaluation method has not been changed, but it has been set and it's a custom 431 * expression. The formula needs to be updated in this case. 432 */ 433 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 434 $this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'], 435 $conditions_for_correlations[$correlation['correlationid']] 436 ); 437 } 438 } 439 } 440 elseif ($db_correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 441 /* 442 * The evaluation method has not been changed and was not set. It's read from DB, but it's still 443 * a custom expression and there are new conditions, so the formula needs to be updated. 444 */ 445 $this->updateFormula($correlation['correlationid'], $correlation['filter']['formula'], 446 $conditions_for_correlations[$correlation['correlationid']] 447 ); 448 } 449 } 450 } 451 } 452 453 // Update operations. Delete the old ones and create new ones. 454 if ($operations_to_delete) { 455 DB::delete('corr_operation', ['correlationid' => array_keys($operations_to_delete)]); 456 } 457 458 if ($operations_to_create) { 459 DB::save('corr_operation', $operations_to_create); 460 } 461 462 return ['correlationids' => zbx_objectValues($correlations, 'correlationid')]; 463 } 464 465 /** 466 * Delete correlations. 467 * 468 * @param array $correlationids An array of correlation IDs. 469 * 470 * @return array 471 */ 472 public function delete(array $correlationids) { 473 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 474 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 475 } 476 477 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 478 if (!CApiInputValidator::validate($api_input_rules, $correlationids, '/', $error)) { 479 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 480 } 481 482 $db_correlations = $this->get([ 483 'output' => ['correlationid', 'name'], 484 'correlationids' => $correlationids, 485 'preservekeys' => true 486 ]); 487 488 foreach ($correlationids as $correlationid) { 489 if (!array_key_exists($correlationid, $db_correlations)) { 490 self::exception(ZBX_API_ERROR_PERMISSIONS, 491 _('No permissions to referred object or it does not exist!') 492 ); 493 } 494 } 495 496 DB::delete('correlation', ['correlationid' => $correlationids]); 497 498 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_CORRELATION, $db_correlations); 499 500 return ['correlationids' => $correlationids]; 501 } 502 503 /** 504 * Validates the input parameters for the create() method. 505 * 506 * @param array $correlations 507 * 508 * @throws APIException if the input is invalid. 509 */ 510 protected function validateCreate(array $correlations) { 511 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 512 self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only super admins can create correlations.')); 513 } 514 515 if (!$correlations) { 516 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 517 } 518 519 $required_fields = ['name', 'filter', 'operations']; 520 521 // Validate required fields and check if "name" is not empty. 522 foreach ($correlations as $correlation) { 523 if (!is_array($correlation)) { 524 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 525 } 526 527 // Check required parameters. 528 $missing_keys = array_diff($required_fields, array_keys($correlation)); 529 530 if ($missing_keys) { 531 self::exception(ZBX_API_ERROR_PARAMETERS, 532 _s('Correlation is missing parameters: %1$s', implode(', ', $missing_keys)) 533 ); 534 } 535 536 // Validate "name" field. 537 if (is_array($correlation['name'])) { 538 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 539 } 540 elseif ($correlation['name'] === '' || $correlation['name'] === null || $correlation['name'] === false) { 541 self::exception(ZBX_API_ERROR_PARAMETERS, _('Correlation name cannot be empty.')); 542 } 543 } 544 545 // Check for duplicate names. 546 $duplicate = CArrayHelper::findDuplicate($correlations, 'name'); 547 if ($duplicate) { 548 self::exception(ZBX_API_ERROR_PARAMETERS, 549 _s('Duplicate "%1$s" value "%2$s" for correlation.', 'name', $duplicate['name']) 550 ); 551 } 552 553 // Check if correlation already exists. 554 $db_correlations = API::getApiService()->select('correlation', [ 555 'output' => ['name'], 556 'filter' => ['name' => zbx_objectValues($correlations, 'name')], 557 'limit' => 1 558 ]); 559 560 if ($db_correlations) { 561 self::exception(ZBX_API_ERROR_PARAMETERS, 562 _s('Correlation "%1$s" already exists.', $correlations[0]['name']) 563 ); 564 } 565 566 // Set all necessary validators and parser before cycling each correlation. 567 $status_validator = new CLimitedSetValidator([ 568 'values' => [ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED] 569 ]); 570 571 $filter_evaltype_validator = new CLimitedSetValidator([ 572 'values' => [CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_AND_OR, 573 CONDITION_EVAL_TYPE_EXPRESSION 574 ] 575 ]); 576 577 $filter_condition_type_validator = new CLimitedSetValidator([ 578 'values' => [ZBX_CORR_CONDITION_OLD_EVENT_TAG, ZBX_CORR_CONDITION_NEW_EVENT_TAG, 579 ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP, ZBX_CORR_CONDITION_EVENT_TAG_PAIR, 580 ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE, ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE 581 ] 582 ]); 583 584 $filter_condition_hg_operator_validator = new CLimitedSetValidator([ 585 'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL] 586 ]); 587 588 $filter_condition_tagval_operator_validator = new CLimitedSetValidator([ 589 'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, 590 CONDITION_OPERATOR_NOT_LIKE 591 ] 592 ]); 593 594 $filter_operations_validator = new CLimitedSetValidator([ 595 'values' => [ZBX_CORR_OPERATION_CLOSE_OLD, ZBX_CORR_OPERATION_CLOSE_NEW] 596 ]); 597 598 $parser = new CConditionFormula(); 599 600 $groupids = []; 601 602 foreach ($correlations as $correlation) { 603 // Validate "status" field (optional). 604 if (array_key_exists('status', $correlation) && !$status_validator->validate($correlation['status'])) { 605 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 606 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 607 $correlation['status'], 608 'status', 609 $correlation['name'] 610 )); 611 } 612 613 // Validate "filter" field. 614 if (!is_array($correlation['filter'])) { 615 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 616 } 617 618 // Validate "evaltype" field. 619 if (!array_key_exists('evaltype', $correlation['filter'])) { 620 self::exception(ZBX_API_ERROR_PARAMETERS, 621 _s('Incorrect type of calculation for correlation "%1$s".', $correlation['name']) 622 ); 623 } 624 elseif (!$filter_evaltype_validator->validate($correlation['filter']['evaltype'])) { 625 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 626 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 627 $correlation['filter']['evaltype'], 628 'evaltype', 629 $correlation['name'] 630 )); 631 } 632 633 // Check if conditions exist and that array is not empty. 634 if (!array_key_exists('conditions', $correlation['filter'])) { 635 self::exception(ZBX_API_ERROR_PARAMETERS, 636 _s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name']) 637 ); 638 } 639 640 // Validate condition operators and other parameters depending on type. 641 $groupids = $this->validateConditions($correlation, $filter_condition_type_validator, 642 $filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator 643 ); 644 645 // Validate custom expressions and formula. 646 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 647 // Check formula. 648 if (!array_key_exists('formula', $correlation['filter'])) { 649 self::exception(ZBX_API_ERROR_PARAMETERS, 650 _s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name']) 651 ); 652 } 653 654 $this->validateFormula($correlation, $parser); 655 656 // Check condition formula IDs. 657 $this->validateConditionFormulaIDs($correlation, $parser); 658 } 659 660 // Validate operations. 661 $this->validateOperations($correlation, $filter_operations_validator); 662 } 663 664 // Validate collected group IDs if at least one of correlation conditions was "New event host group". 665 if ($groupids) { 666 $groups_count = API::HostGroup()->get([ 667 'countOutput' => true, 668 'groupids' => array_keys($groupids) 669 ]); 670 671 if ($groups_count != count($groupids)) { 672 self::exception(ZBX_API_ERROR_PERMISSIONS, 673 _('No permissions to referred object or it does not exist!') 674 ); 675 } 676 } 677 } 678 679 /** 680 * Validates the input parameters for the update() method. 681 * 682 * @param array $correlations 683 * @param array $db_correlations 684 * 685 * @throws APIException if the input is invalid. 686 */ 687 protected function validateUpdate(array $correlations, array &$db_correlations) { 688 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 689 self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only super admins can update correlations.')); 690 } 691 692 if (!$correlations) { 693 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 694 } 695 696 // Validate given IDs. 697 $this->checkObjectIds($correlations, 'correlationid', 698 _('No "%1$s" given for correlation.'), 699 _('Empty correlation ID.'), 700 _('Incorrect correlation ID.') 701 ); 702 703 $db_correlations = $this->get([ 704 'output' => ['correlationid', 'name', 'description', 'status'], 705 'selectFilter' => ['formula', 'eval_formula', 'evaltype', 'conditions'], 706 'selectOperations' => ['type'], 707 'correlationids' => zbx_objectValues($correlations, 'correlationid'), 708 'preservekeys' => true 709 ]); 710 711 $check_names = []; 712 713 foreach ($correlations as $correlation) { 714 // Check if this correlation exists. 715 if (!array_key_exists($correlation['correlationid'], $db_correlations)) { 716 self::exception(ZBX_API_ERROR_PERMISSIONS, 717 _('No permissions to referred object or it does not exist!') 718 ); 719 } 720 721 // Validate "name" field (optional). 722 if (array_key_exists('name', $correlation)) { 723 if (is_array($correlation['name'])) { 724 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 725 } 726 elseif ($correlation['name'] === '' || $correlation['name'] === null || $correlation['name'] === false) { 727 self::exception(ZBX_API_ERROR_PARAMETERS, _('Correlation name cannot be empty.')); 728 } 729 730 if ($db_correlations[$correlation['correlationid']]['name'] !== $correlation['name']) { 731 $check_names[] = $correlation; 732 } 733 } 734 } 735 736 // Check only if names have changed. 737 if ($check_names) { 738 // Check for duplicate names. 739 $duplicate = CArrayHelper::findDuplicate($check_names, 'name'); 740 if ($duplicate) { 741 self::exception(ZBX_API_ERROR_PARAMETERS, 742 _s('Duplicate "%1$s" value "%2$s" for correlation.', 'name', $duplicate['name']) 743 ); 744 } 745 746 // Check if correlation already exists. 747 $db_correlation_names = API::getApiService()->select('correlation', [ 748 'output' => ['correlationid', 'name'], 749 'filter' => ['name' => zbx_objectValues($check_names, 'name')] 750 ]); 751 $db_correlation_names = zbx_toHash($db_correlation_names, 'name'); 752 753 foreach ($check_names as $correlation) { 754 if (array_key_exists($correlation['name'], $db_correlation_names) 755 && bccomp($db_correlation_names[$correlation['name']]['correlationid'], 756 $correlation['correlationid']) != 0) { 757 self::exception(ZBX_API_ERROR_PARAMETERS, 758 _s('Correlation "%1$s" already exists.', $correlation['name']) 759 ); 760 } 761 } 762 } 763 764 // Set all necessary validators and parser before cycling each correlation. 765 $status_validator = new CLimitedSetValidator([ 766 'values' => [ZBX_CORRELATION_ENABLED, ZBX_CORRELATION_DISABLED] 767 ]); 768 769 $filter_evaltype_validator = new CLimitedSetValidator([ 770 'values' => [CONDITION_EVAL_TYPE_OR, CONDITION_EVAL_TYPE_AND, CONDITION_EVAL_TYPE_AND_OR, 771 CONDITION_EVAL_TYPE_EXPRESSION 772 ] 773 ]); 774 775 $filter_condition_type_validator = new CLimitedSetValidator([ 776 'values' => [ZBX_CORR_CONDITION_OLD_EVENT_TAG, ZBX_CORR_CONDITION_NEW_EVENT_TAG, 777 ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP, ZBX_CORR_CONDITION_EVENT_TAG_PAIR, 778 ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE, ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE 779 ] 780 ]); 781 782 $filter_condition_hg_operator_validator = new CLimitedSetValidator([ 783 'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL] 784 ]); 785 786 $filter_condition_tagval_operator_validator = new CLimitedSetValidator([ 787 'values' => [CONDITION_OPERATOR_EQUAL, CONDITION_OPERATOR_NOT_EQUAL, CONDITION_OPERATOR_LIKE, 788 CONDITION_OPERATOR_NOT_LIKE 789 ] 790 ]); 791 792 $filter_operations_validator = new CLimitedSetValidator([ 793 'values' => [ZBX_CORR_OPERATION_CLOSE_OLD, ZBX_CORR_OPERATION_CLOSE_NEW] 794 ]); 795 796 $parser = new CConditionFormula(); 797 798 $groupids = []; 799 800 // Populate "name" field, if not set. 801 $correlations = $this->extendFromObjects(zbx_toHash($correlations, 'correlationid'), $db_correlations, 802 ['name'] 803 ); 804 805 foreach ($correlations as $correlation) { 806 $db_correlation = $db_correlations[$correlation['correlationid']]; 807 808 // Validate "status" field (optional). 809 if (array_key_exists('status', $correlation) && !$status_validator->validate($correlation['status'])) { 810 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 811 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 812 $correlation['status'], 813 'status', 814 $correlation['name'] 815 )); 816 } 817 818 // Validate "filter" field. If filter is set, then something else must exist. 819 if (array_key_exists('filter', $correlation)) { 820 if (!is_array($correlation['filter']) || !$correlation['filter']) { 821 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 822 } 823 824 $evaltype_changed = false; 825 826 // Validate "evaltype" field. 827 if (array_key_exists('evaltype', $correlation['filter'])) { 828 // Check if evaltype has changed. 829 if ($correlation['filter']['evaltype'] != $db_correlation['filter']['evaltype']) { 830 if (!$filter_evaltype_validator->validate($correlation['filter']['evaltype'])) { 831 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 832 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 833 $correlation['filter']['evaltype'], 834 'evaltype', 835 $correlation['name'] 836 )); 837 } 838 839 $evaltype_changed = true; 840 } 841 } 842 else { 843 // Populate "evaltype" field if not set, so we can later check for custom expressions. 844 $correlation['filter']['evaltype'] = $db_correlation['filter']['evaltype']; 845 } 846 847 if ($evaltype_changed) { 848 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 849 if (!array_key_exists('formula', $correlation['filter'])) { 850 self::exception(ZBX_API_ERROR_PARAMETERS, 851 _s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name']) 852 ); 853 } 854 855 $this->validateFormula($correlation, $parser); 856 857 if (!array_key_exists('conditions', $correlation['filter'])) { 858 self::exception(ZBX_API_ERROR_PARAMETERS, 859 _s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name']) 860 ); 861 } 862 863 $groupids = $this->validateConditions($correlation, $filter_condition_type_validator, 864 $filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator 865 ); 866 867 $this->validateConditionFormulaIDs($correlation, $parser); 868 } 869 else { 870 if (!array_key_exists('conditions', $correlation['filter'])) { 871 self::exception(ZBX_API_ERROR_PARAMETERS, 872 _s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name']) 873 ); 874 } 875 876 $groupids = $this->validateConditions($correlation, $filter_condition_type_validator, 877 $filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator 878 ); 879 } 880 } 881 else { 882 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 883 if (array_key_exists('formula', $correlation['filter'])) { 884 $this->validateFormula($correlation, $parser); 885 886 if (!array_key_exists('conditions', $correlation['filter'])) { 887 self::exception(ZBX_API_ERROR_PARAMETERS, 888 _s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name']) 889 ); 890 } 891 892 $groupids = $this->validateConditions($correlation, $filter_condition_type_validator, 893 $filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator 894 ); 895 896 $this->validateConditionFormulaIDs($correlation, $parser); 897 } 898 elseif (array_key_exists('conditions', $correlation['filter'])) { 899 self::exception(ZBX_API_ERROR_PARAMETERS, 900 _s('No "%1$s" given for correlation "%2$s".', 'formula', $correlation['name']) 901 ); 902 } 903 } 904 elseif (array_key_exists('conditions', $correlation['filter'])) { 905 $groupids = $this->validateConditions($correlation, $filter_condition_type_validator, 906 $filter_condition_hg_operator_validator, $filter_condition_tagval_operator_validator 907 ); 908 } 909 } 910 } 911 912 // Validate operations (optional). 913 if (array_key_exists('operations', $correlation)) { 914 $this->validateOperations($correlation, $filter_operations_validator); 915 } 916 } 917 918 // Validate collected group IDs if at least one of correlation conditions was "New event host group". 919 if ($groupids) { 920 $groups_count = API::HostGroup()->get([ 921 'countOutput' => true, 922 'groupids' => array_keys($groupids) 923 ]); 924 925 if ($groups_count != count($groupids)) { 926 self::exception(ZBX_API_ERROR_PERMISSIONS, 927 _('No permissions to referred object or it does not exist!') 928 ); 929 } 930 } 931 } 932 933 /** 934 * Converts a formula with letters to a formula with IDs and updates it. 935 * 936 * @param string $correlationid 937 * @param string $formula_with_letters Formula with letters. 938 * @param array $conditions 939 */ 940 protected function updateFormula($correlationid, $formula_with_letters, array $conditions) { 941 $formulaid_to_conditionid = []; 942 943 foreach ($conditions as $condition) { 944 $formulaid_to_conditionid[$condition['formulaid']] = $condition['corr_conditionid']; 945 } 946 $formula = CConditionHelper::replaceLetterIds($formula_with_letters, $formulaid_to_conditionid); 947 948 DB::updateByPk('correlation', $correlationid, ['formula' => $formula]); 949 } 950 951 /** 952 * Validate correlation conditions. Check the "conditions" array, check the "type" field and other fields that 953 * depend on it. As a result return host group IDs that need to be validated afterwards. Otherwise don't return 954 * anything, just throw an error. 955 * 956 * @param array $correlation One correlation containing the conditions. 957 * @param string $correlation['name'] Correlation name for error messages. 958 * @param array $correlation['filter'] Correlation filter array containing the conditions. 959 * @param array $correlation['filter']['conditions'] An array of correlation conditions. 960 * @param int $correlation['filter']['conditions'][]['type'] Condition type. 961 * Possible values are: 962 * 0 - ZBX_CORR_CONDITION_OLD_EVENT_TAG; 963 * 1 - ZBX_CORR_CONDITION_NEW_EVENT_TAG; 964 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 965 * 3 - ZBX_CORR_CONDITION_EVENT_TAG_PAIR; 966 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 967 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 968 * @param int $correlation['filter']['conditions'][]['operator'] Correlation condition operator. 969 * Possible values when "type" 970 * is one of the following: 971 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP; 972 * 4 - ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE; 973 * 5 - ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE. 974 * Possible values depend on type: 975 * for type ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 976 * 0 - CONDITION_OPERATOR_EQUAL 977 * 1 - CONDITION_OPERATOR_NOT_EQUAL 978 * for types ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE 979 * or ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 980 * 0 - CONDITION_OPERATOR_EQUAL; 981 * 1 - CONDITION_OPERATOR_NOT_EQUAL; 982 * 2 - CONDITION_OPERATOR_LIKE; 983 * 3 - CONDITION_OPERATOR_NOT_LIKE. 984 * @param string $correlations['filter']['conditions'][]['groupid'] Correlation host group ID. 985 * Required when "type" is: 986 * 2 - ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP. 987 * @param CLimitedSetValidator $filter_condition_type_validator Validator for conditype type. 988 * @param CLimitedSetValidator $filter_condition_hg_operator_validator Validator for host group operator. 989 * @param CLimitedSetValidator $filter_condition_tagval_operator_validator Validator for tag value operator. 990 * 991 * @throws APIException if the input is invalid. 992 * 993 * @return array 994 */ 995 protected function validateConditions(array $correlation, CLimitedSetValidator $filter_condition_type_validator, 996 CLimitedSetValidator $filter_condition_hg_operator_validator, 997 CLimitedSetValidator $filter_condition_tagval_operator_validator) { 998 if (!$correlation['filter']['conditions']) { 999 self::exception(ZBX_API_ERROR_PARAMETERS, 1000 _s('No "%1$s" given for correlation "%2$s".', 'conditions', $correlation['name']) 1001 ); 1002 } 1003 1004 if (!is_array($correlation['filter']['conditions'])) { 1005 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1006 } 1007 1008 $groupids = []; 1009 $formulaIds = []; 1010 $conditions = []; 1011 1012 foreach ($correlation['filter']['conditions'] as $condition) { 1013 if (!array_key_exists('type', $condition)) { 1014 self::exception(ZBX_API_ERROR_PARAMETERS, 1015 _s('No condition type given for correlation "%1$s".', $correlation['name']) 1016 ); 1017 } 1018 elseif (is_array($condition['type'])) { 1019 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1020 } 1021 elseif (!$filter_condition_type_validator->validate($condition['type'])) { 1022 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1023 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1024 $condition['type'], 1025 'type', 1026 $correlation['name'] 1027 )); 1028 } 1029 1030 switch ($condition['type']) { 1031 case ZBX_CORR_CONDITION_OLD_EVENT_TAG: 1032 case ZBX_CORR_CONDITION_NEW_EVENT_TAG: 1033 if (!array_key_exists('tag', $condition)) { 1034 self::exception(ZBX_API_ERROR_PARAMETERS, 1035 _s('No "%1$s" given for correlation "%2$s".', 'tag', $correlation['name']) 1036 ); 1037 } 1038 elseif (is_array($condition['tag'])) { 1039 self::exception(ZBX_API_ERROR_PARAMETERS, 1040 _('Incorrect arguments passed to function.') 1041 ); 1042 } 1043 elseif ($condition['tag'] === '' || $condition['tag'] === null || $condition['tag'] === false) { 1044 self::exception(ZBX_API_ERROR_PARAMETERS, 1045 _s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty')) 1046 ); 1047 } 1048 break; 1049 1050 case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 1051 if (array_key_exists('operator', $condition)) { 1052 if (is_array($condition['operator'])) { 1053 self::exception(ZBX_API_ERROR_PARAMETERS, 1054 _('Incorrect arguments passed to function.') 1055 ); 1056 } 1057 elseif (!$filter_condition_hg_operator_validator->validate($condition['operator'])) { 1058 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1059 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1060 $condition['operator'], 1061 'operator', 1062 $correlation['name'] 1063 )); 1064 } 1065 } 1066 1067 if (!array_key_exists('groupid', $condition)) { 1068 self::exception(ZBX_API_ERROR_PARAMETERS, 1069 _s('No "%1$s" given for correlation "%2$s".', 'groupid', $correlation['name']) 1070 ); 1071 } 1072 elseif (is_array($condition['groupid'])) { 1073 self::exception(ZBX_API_ERROR_PARAMETERS, 1074 _('Incorrect arguments passed to function.') 1075 ); 1076 } 1077 elseif ($condition['groupid'] === '' || $condition['groupid'] === null 1078 || $condition['groupid'] === false) { 1079 self::exception(ZBX_API_ERROR_PARAMETERS, 1080 _s('Incorrect value for field "%1$s": %2$s.', 'groupid', _('cannot be empty')) 1081 ); 1082 } 1083 1084 $groupids[$condition['groupid']] = true; 1085 break; 1086 1087 case ZBX_CORR_CONDITION_EVENT_TAG_PAIR: 1088 if (!array_key_exists('oldtag', $condition)) { 1089 self::exception(ZBX_API_ERROR_PARAMETERS, 1090 _s('No "%1$s" given for correlation "%2$s".', 'oldtag', $correlation['name']) 1091 ); 1092 } 1093 elseif (is_array($condition['oldtag'])) { 1094 self::exception(ZBX_API_ERROR_PARAMETERS, 1095 _('Incorrect arguments passed to function.') 1096 ); 1097 } 1098 elseif ($condition['oldtag'] === '' || $condition['oldtag'] === null 1099 || $condition['oldtag'] === false) { 1100 self::exception(ZBX_API_ERROR_PARAMETERS, 1101 _s('Incorrect value for field "%1$s": %2$s.', 'oldtag', _('cannot be empty')) 1102 ); 1103 } 1104 1105 if (!array_key_exists('newtag', $condition)) { 1106 self::exception(ZBX_API_ERROR_PARAMETERS, 1107 _s('No "%1$s" given for correlation "%2$s".', 'newtag', $correlation['name']) 1108 ); 1109 } 1110 elseif (is_array($condition['newtag'])) { 1111 self::exception(ZBX_API_ERROR_PARAMETERS, 1112 _('Incorrect arguments passed to function.') 1113 ); 1114 } 1115 elseif ($condition['newtag'] === '' || $condition['newtag'] === null 1116 || $condition['newtag'] === false) { 1117 self::exception(ZBX_API_ERROR_PARAMETERS, 1118 _s('Incorrect value for field "%1$s": %2$s.', 'newtag', _('cannot be empty')) 1119 ); 1120 } 1121 break; 1122 1123 case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE: 1124 case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 1125 if (!array_key_exists('tag', $condition)) { 1126 self::exception(ZBX_API_ERROR_PARAMETERS, 1127 _s('No "%1$s" given for correlation "%2$s".', 'tag', $correlation['name']) 1128 ); 1129 } 1130 elseif (is_array($condition['tag'])) { 1131 self::exception(ZBX_API_ERROR_PARAMETERS, 1132 _('Incorrect arguments passed to function.') 1133 ); 1134 } 1135 elseif ($condition['tag'] === '' || $condition['tag'] === null || $condition['tag'] === false) { 1136 self::exception(ZBX_API_ERROR_PARAMETERS, 1137 _s('Incorrect value for field "%1$s": %2$s.', 'tag', _('cannot be empty')) 1138 ); 1139 } 1140 1141 if (array_key_exists('operator', $condition)) { 1142 if (is_array($condition['operator'])) { 1143 self::exception(ZBX_API_ERROR_PARAMETERS, 1144 _('Incorrect arguments passed to function.') 1145 ); 1146 } 1147 elseif (!$filter_condition_tagval_operator_validator->validate($condition['operator'])) { 1148 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1149 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1150 $condition['operator'], 1151 'operator', 1152 $correlation['name'] 1153 )); 1154 } 1155 } 1156 break; 1157 } 1158 1159 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1160 if (array_key_exists($condition['formulaid'], $formulaIds)) { 1161 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1162 'Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'formulaid', $condition['formulaid'], 1163 $correlation['name'] 1164 )); 1165 } 1166 else { 1167 $formulaIds[$condition['formulaid']] = true; 1168 } 1169 } 1170 1171 unset($condition['formulaid']); 1172 $conditions[] = $condition; 1173 } 1174 1175 if (count($conditions) != count(array_unique($conditions, SORT_REGULAR))) { 1176 self::exception(ZBX_API_ERROR_PARAMETERS, 1177 _s('Conditions duplicates for correlation "%1$s".', $correlation['name']) 1178 ); 1179 } 1180 1181 return $groupids; 1182 } 1183 1184 /** 1185 * Validate correlation filter "formula" field. 1186 * 1187 * @param array $correlation One correlation containing the filter, formula and name. 1188 * @param string $correlation['name'] Correlation name for error messages. 1189 * @param array $correlation['filter'] Correlation filter array containing the formula. 1190 * @param string $correlation['filter']['formula'] User-defined expression to be used for evaluating 1191 * conditions of filters with a custom expression. 1192 * @param CConditionFormula $parser Condition formula parser. 1193 * 1194 * @throws APIException if the input is invalid. 1195 */ 1196 protected function validateFormula(array $correlation, CConditionFormula $parser) { 1197 if (is_array($correlation['filter']['formula'])) { 1198 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1199 } 1200 1201 if (!$parser->parse($correlation['filter']['formula'])) { 1202 self::exception(ZBX_API_ERROR_PARAMETERS, 1203 _s('Incorrect custom expression "%2$s" for correlation "%1$s": %3$s.', 1204 $correlation['name'], $correlation['filter']['formula'], $parser->error 1205 ) 1206 ); 1207 } 1208 } 1209 1210 /** 1211 * Validate correlation condition formula IDs. Check the "formulaid" field and that formula matches the conditions. 1212 * 1213 * @param array $correlation One correlation containing array of 1214 * conditions and name. 1215 * @param string $correlation['name'] Correlation name for error messages. 1216 * @param array $correlation['filter'] Correlation filter array containing 1217 * the conditions. 1218 * @param array $correlation['filter']['conditions'] An array of correlation conditions. 1219 * @param string $correlation['filter']['conditions'][]['formulaid'] Condition formula ID. 1220 * @param CConditionFormula $parser Condition formula parser. 1221 * 1222 * @throws APIException if the input is invalid. 1223 */ 1224 protected function validateConditionFormulaIDs(array $correlation, CConditionFormula $parser) { 1225 foreach ($correlation['filter']['conditions'] as $condition) { 1226 if (!array_key_exists('formulaid', $condition)) { 1227 self::exception(ZBX_API_ERROR_PARAMETERS, 1228 _s('No "%1$s" given for correlation "%2$s".', 'formulaid', $correlation['name']) 1229 ); 1230 } 1231 elseif (is_array($condition['formulaid'])) { 1232 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1233 } 1234 elseif (!preg_match('/[A-Z]+/', $condition['formulaid'])) { 1235 self::exception(ZBX_API_ERROR_PARAMETERS, 1236 _s('Incorrect filter condition formula ID given for correlation "%1$s".', $correlation['name']) 1237 ); 1238 } 1239 } 1240 1241 $conditions = zbx_toHash($correlation['filter']['conditions'], 'formulaid'); 1242 $constants = array_unique(zbx_objectValues($parser->constants, 'value')); 1243 1244 foreach ($constants as $constant) { 1245 if (!array_key_exists($constant, $conditions)) { 1246 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1247 'Condition "%2$s" used in formula "%3$s" for correlation "%1$s" is not defined.', 1248 $correlation['name'], $constant, $correlation['filter']['formula'] 1249 )); 1250 } 1251 1252 unset($conditions[$constant]); 1253 } 1254 1255 // Check that the "conditions" array has no unused conditions. 1256 if ($conditions) { 1257 $condition = reset($conditions); 1258 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1259 'Condition "%2$s" is not used in formula "%3$s" for correlation "%1$s".', $correlation['name'], 1260 $condition['formulaid'], $correlation['filter']['formula'] 1261 )); 1262 } 1263 } 1264 1265 /** 1266 * Validate correlation operations. Check if "operations" is valid, if "type" is valid and there are no duplicate 1267 * operations in correlation. 1268 * 1269 * @param array $correlation One correlation containing array of operations and name. 1270 * @param string $correlation['name'] Correlation name for error messages. 1271 * @param array $correlation['operations'] An array of correlation operations. 1272 * @param int $correlation['operations']['type'] Correlation operation type. 1273 * Possible values are: 1274 * 0 - ZBX_CORR_OPERATION_CLOSE_OLD; 1275 * 1 - ZBX_CORR_OPERATION_CLOSE_NEW. 1276 * @param CLimitedSetValidator $filter_operations_validator Operations validator. 1277 * 1278 * @throws APIException if the input is invalid. 1279 */ 1280 protected function validateOperations(array $correlation, CLimitedSetValidator $filter_operations_validator) { 1281 if (!is_array($correlation['operations'])) { 1282 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1283 } 1284 elseif (!$correlation['operations']) { 1285 self::exception(ZBX_API_ERROR_PARAMETERS, 1286 _s('No "%1$s" given for correlation "%2$s".', 'operations', $correlation['name']) 1287 ); 1288 } 1289 1290 foreach ($correlation['operations'] as $operation) { 1291 if (!array_key_exists('type', $operation)) { 1292 self::exception(ZBX_API_ERROR_PARAMETERS, 1293 _s('No operation type given for correlation "%1$s".', $correlation['name']) 1294 ); 1295 } 1296 elseif (is_array($operation['type'])) { 1297 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1298 } 1299 1300 if (!$filter_operations_validator->validate($operation['type'])) { 1301 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1302 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1303 $operation['type'], 1304 'type', 1305 $correlation['name'] 1306 )); 1307 } 1308 } 1309 1310 // Check that same operation types do not repeat. 1311 $duplicate = CArrayHelper::findDuplicate($correlation['operations'], 'type'); 1312 if ($duplicate) { 1313 self::exception(ZBX_API_ERROR_PARAMETERS, 1314 _s('Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'type', $duplicate['type'], 1315 $correlation['name'] 1316 ) 1317 ); 1318 } 1319 } 1320 1321 /** 1322 * Insert correlation condition values to their corresponding DB tables. 1323 * 1324 * @param array $conditions An array of conditions to create. 1325 * 1326 * @return array 1327 */ 1328 protected function addConditions(array $conditions) { 1329 $conditions = DB::save('corr_condition', $conditions); 1330 1331 $corr_condition_tags_to_create = []; 1332 $corr_condition_hostgroups_to_create = []; 1333 $corr_condition_tag_pairs_to_create = []; 1334 $corr_condition_tag_values_to_create = []; 1335 1336 foreach ($conditions as $condition) { 1337 switch ($condition['type']) { 1338 case ZBX_CORR_CONDITION_OLD_EVENT_TAG: 1339 case ZBX_CORR_CONDITION_NEW_EVENT_TAG: 1340 $corr_condition_tags_to_create[] = $condition; 1341 break; 1342 1343 case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 1344 $corr_condition_hostgroups_to_create[] = $condition; 1345 break; 1346 1347 case ZBX_CORR_CONDITION_EVENT_TAG_PAIR: 1348 $corr_condition_tag_pairs_to_create[] = $condition; 1349 break; 1350 1351 case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE: 1352 case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 1353 $corr_condition_tag_values_to_create[] = $condition; 1354 break; 1355 } 1356 } 1357 1358 if ($corr_condition_tags_to_create) { 1359 DB::insert('corr_condition_tag', $corr_condition_tags_to_create, false); 1360 } 1361 1362 if ($corr_condition_hostgroups_to_create) { 1363 DB::insert('corr_condition_group', $corr_condition_hostgroups_to_create, false); 1364 } 1365 1366 if ($corr_condition_tag_pairs_to_create) { 1367 DB::insert('corr_condition_tagpair', $corr_condition_tag_pairs_to_create, false); 1368 } 1369 1370 if ($corr_condition_tag_values_to_create) { 1371 DB::insert('corr_condition_tagvalue', $corr_condition_tag_values_to_create, false); 1372 } 1373 1374 return $conditions; 1375 } 1376 1377 /** 1378 * Apply query output options. 1379 * 1380 * @param type $table_name 1381 * @param type $table_alias 1382 * @param array $options 1383 * @param array $sql_parts 1384 * 1385 * @return array 1386 */ 1387 protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts) { 1388 $sql_parts = parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts); 1389 1390 if (!$options['countOutput']) { 1391 // Add filter fields. 1392 if ($this->outputIsRequested('formula', $options['selectFilter']) 1393 || $this->outputIsRequested('eval_formula', $options['selectFilter']) 1394 || $this->outputIsRequested('conditions', $options['selectFilter'])) { 1395 1396 $sql_parts = $this->addQuerySelect('c.formula', $sql_parts); 1397 $sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts); 1398 } 1399 1400 if ($this->outputIsRequested('evaltype', $options['selectFilter'])) { 1401 $sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts); 1402 } 1403 } 1404 1405 return $sql_parts; 1406 } 1407 1408 /** 1409 * Extend result with requested objects. 1410 * 1411 * @param array $options 1412 * @param array $result 1413 * 1414 * @return array 1415 */ 1416 protected function addRelatedObjects(array $options, array $result) { 1417 $result = parent::addRelatedObjects($options, $result); 1418 1419 $correlationids = array_keys($result); 1420 1421 // Adding formulas and conditions. 1422 if ($options['selectFilter'] !== null) { 1423 $formula_requested = $this->outputIsRequested('formula', $options['selectFilter']); 1424 $eval_formula_requested = $this->outputIsRequested('eval_formula', $options['selectFilter']); 1425 $conditions_requested = $this->outputIsRequested('conditions', $options['selectFilter']); 1426 1427 $filters = []; 1428 1429 if ($options['selectFilter']) { 1430 foreach ($result as $correlation) { 1431 $filters[$correlation['correlationid']] = [ 1432 'evaltype' => $correlation['evaltype'], 1433 'formula' => array_key_exists('formula', $correlation) ? $correlation['formula'] : '', 1434 'conditions' => [] 1435 ]; 1436 } 1437 1438 if ($formula_requested || $eval_formula_requested || $conditions_requested) { 1439 $sql = 'SELECT c.correlationid,c.corr_conditionid,c.type,ct.tag AS ct_tag,'. 1440 'cg.operator AS cg_operator,cg.groupid,ctp.oldtag,ctp.newtag,ctv.tag AS ctv_tag,'. 1441 'ctv.operator AS ctv_operator,ctv.value'. 1442 ' FROM corr_condition c'. 1443 ' LEFT JOIN corr_condition_tag ct ON ct.corr_conditionid = c.corr_conditionid'. 1444 ' LEFT JOIN corr_condition_group cg ON cg.corr_conditionid = c.corr_conditionid'. 1445 ' LEFT JOIN corr_condition_tagpair ctp ON ctp.corr_conditionid = c.corr_conditionid'. 1446 ' LEFT JOIN corr_condition_tagvalue ctv ON ctv.corr_conditionid = c.corr_conditionid'. 1447 ' WHERE '.dbConditionInt('c.correlationid', $correlationids); 1448 1449 $db_corr_conditions = DBselect($sql); 1450 1451 while ($row = DBfetch($db_corr_conditions)) { 1452 $fields = [ 1453 'corr_conditionid' => $row['corr_conditionid'], 1454 'correlationid' => $row['correlationid'], 1455 'type' => $row['type'] 1456 ]; 1457 1458 switch ($row['type']) { 1459 case ZBX_CORR_CONDITION_OLD_EVENT_TAG: 1460 case ZBX_CORR_CONDITION_NEW_EVENT_TAG: 1461 $fields['tag'] = $row['ct_tag']; 1462 break; 1463 1464 case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 1465 $fields['operator'] = $row['cg_operator']; 1466 $fields['groupid'] = $row['groupid']; 1467 break; 1468 1469 case ZBX_CORR_CONDITION_EVENT_TAG_PAIR: 1470 $fields['oldtag'] = $row['oldtag']; 1471 $fields['newtag'] = $row['newtag']; 1472 break; 1473 1474 case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE: 1475 case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 1476 $fields['tag'] = $row['ctv_tag']; 1477 $fields['operator'] = $row['ctv_operator']; 1478 $fields['value'] = $row['value']; 1479 break; 1480 } 1481 1482 $filters[$row['correlationid']]['conditions'][] = $fields; 1483 } 1484 1485 foreach ($filters as &$filter) { 1486 // In case of a custom expression, use the given formula. 1487 if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1488 $formula = $filter['formula']; 1489 } 1490 // In other cases generate the formula automatically. 1491 else { 1492 $conditions = $filter['conditions']; 1493 CArrayHelper::sort($conditions, ['type']); 1494 $conditions_for_formula = []; 1495 1496 foreach ($conditions as $condition) { 1497 $conditions_for_formula[$condition['corr_conditionid']] = $condition['type']; 1498 } 1499 1500 $formula = CConditionHelper::getFormula($conditions_for_formula, $filter['evaltype']); 1501 } 1502 1503 // Generate formulaids from the effective formula. 1504 $formulaids = CConditionHelper::getFormulaIds($formula); 1505 1506 foreach ($filter['conditions'] as &$condition) { 1507 $condition['formulaid'] = $formulaids[$condition['corr_conditionid']]; 1508 } 1509 unset($condition); 1510 1511 // Generated a letter based formula only for actions with custom expressions. 1512 if ($formula_requested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1513 $filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids); 1514 } 1515 1516 if ($eval_formula_requested) { 1517 $filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids); 1518 } 1519 } 1520 unset($filter); 1521 } 1522 } 1523 else { 1524 // In case no fields are actually selected in "filter", return empty array. 1525 foreach ($result as $correlation) { 1526 $filters[$correlation['correlationid']] = []; 1527 } 1528 } 1529 1530 // Add filters to the result. 1531 foreach ($result as &$correlation) { 1532 $correlation['filter'] = $filters[$correlation['correlationid']]; 1533 } 1534 unset($correlation); 1535 } 1536 1537 // Adding operations. 1538 if ($options['selectOperations'] !== null && $options['selectOperations'] != API_OUTPUT_COUNT) { 1539 $operations = API::getApiService()->select('corr_operation', [ 1540 'output' => $this->outputExtend($options['selectOperations'], [ 1541 'correlationid', 'corr_operationid', 'type' 1542 ]), 1543 'filter' => ['correlationid' => $correlationids], 1544 'preservekeys' => true 1545 ]); 1546 $relation_map = $this->createRelationMap($operations, 'correlationid', 'corr_operationid'); 1547 1548 foreach ($operations as &$operation) { 1549 unset($operation['correlationid'], $operation['corr_operationid']); 1550 } 1551 unset($operation); 1552 1553 $result = $relation_map->mapMany($result, $operations, 'operations'); 1554 } 1555 1556 return $result; 1557 } 1558} 1559