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 (($condition['operator'] == CONDITION_OPERATOR_LIKE 1148 || $condition['operator'] == CONDITION_OPERATOR_NOT_LIKE) 1149 && (!array_key_exists('value', $condition) || $condition['value'] === '')) { 1150 self::exception(ZBX_API_ERROR_PARAMETERS, 1151 _s('Incorrect value for field "%1$s": %2$s.', 'value', _('cannot be empty')) 1152 ); 1153 } 1154 elseif (!$filter_condition_tagval_operator_validator->validate($condition['operator'])) { 1155 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1156 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1157 $condition['operator'], 1158 'operator', 1159 $correlation['name'] 1160 )); 1161 } 1162 } 1163 break; 1164 } 1165 1166 if ($correlation['filter']['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1167 if (array_key_exists($condition['formulaid'], $formulaIds)) { 1168 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1169 'Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'formulaid', $condition['formulaid'], 1170 $correlation['name'] 1171 )); 1172 } 1173 else { 1174 $formulaIds[$condition['formulaid']] = true; 1175 } 1176 } 1177 1178 unset($condition['formulaid']); 1179 $conditions[] = $condition; 1180 } 1181 1182 if (count($conditions) != count(array_unique($conditions, SORT_REGULAR))) { 1183 self::exception(ZBX_API_ERROR_PARAMETERS, 1184 _s('Conditions duplicates for correlation "%1$s".', $correlation['name']) 1185 ); 1186 } 1187 1188 return $groupids; 1189 } 1190 1191 /** 1192 * Validate correlation filter "formula" field. 1193 * 1194 * @param array $correlation One correlation containing the filter, formula and name. 1195 * @param string $correlation['name'] Correlation name for error messages. 1196 * @param array $correlation['filter'] Correlation filter array containing the formula. 1197 * @param string $correlation['filter']['formula'] User-defined expression to be used for evaluating 1198 * conditions of filters with a custom expression. 1199 * @param CConditionFormula $parser Condition formula parser. 1200 * 1201 * @throws APIException if the input is invalid. 1202 */ 1203 protected function validateFormula(array $correlation, CConditionFormula $parser) { 1204 if (is_array($correlation['filter']['formula'])) { 1205 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1206 } 1207 1208 if (!$parser->parse($correlation['filter']['formula'])) { 1209 self::exception(ZBX_API_ERROR_PARAMETERS, 1210 _s('Incorrect custom expression "%2$s" for correlation "%1$s": %3$s.', 1211 $correlation['name'], $correlation['filter']['formula'], $parser->error 1212 ) 1213 ); 1214 } 1215 } 1216 1217 /** 1218 * Validate correlation condition formula IDs. Check the "formulaid" field and that formula matches the conditions. 1219 * 1220 * @param array $correlation One correlation containing array of 1221 * conditions and name. 1222 * @param string $correlation['name'] Correlation name for error messages. 1223 * @param array $correlation['filter'] Correlation filter array containing 1224 * the conditions. 1225 * @param array $correlation['filter']['conditions'] An array of correlation conditions. 1226 * @param string $correlation['filter']['conditions'][]['formulaid'] Condition formula ID. 1227 * @param CConditionFormula $parser Condition formula parser. 1228 * 1229 * @throws APIException if the input is invalid. 1230 */ 1231 protected function validateConditionFormulaIDs(array $correlation, CConditionFormula $parser) { 1232 foreach ($correlation['filter']['conditions'] as $condition) { 1233 if (!array_key_exists('formulaid', $condition)) { 1234 self::exception(ZBX_API_ERROR_PARAMETERS, 1235 _s('No "%1$s" given for correlation "%2$s".', 'formulaid', $correlation['name']) 1236 ); 1237 } 1238 elseif (is_array($condition['formulaid'])) { 1239 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1240 } 1241 elseif (!preg_match('/[A-Z]+/', $condition['formulaid'])) { 1242 self::exception(ZBX_API_ERROR_PARAMETERS, 1243 _s('Incorrect filter condition formula ID given for correlation "%1$s".', $correlation['name']) 1244 ); 1245 } 1246 } 1247 1248 $conditions = zbx_toHash($correlation['filter']['conditions'], 'formulaid'); 1249 $constants = array_unique(zbx_objectValues($parser->constants, 'value')); 1250 1251 foreach ($constants as $constant) { 1252 if (!array_key_exists($constant, $conditions)) { 1253 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1254 'Condition "%2$s" used in formula "%3$s" for correlation "%1$s" is not defined.', 1255 $correlation['name'], $constant, $correlation['filter']['formula'] 1256 )); 1257 } 1258 1259 unset($conditions[$constant]); 1260 } 1261 1262 // Check that the "conditions" array has no unused conditions. 1263 if ($conditions) { 1264 $condition = reset($conditions); 1265 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1266 'Condition "%2$s" is not used in formula "%3$s" for correlation "%1$s".', $correlation['name'], 1267 $condition['formulaid'], $correlation['filter']['formula'] 1268 )); 1269 } 1270 } 1271 1272 /** 1273 * Validate correlation operations. Check if "operations" is valid, if "type" is valid and there are no duplicate 1274 * operations in correlation. 1275 * 1276 * @param array $correlation One correlation containing array of operations and name. 1277 * @param string $correlation['name'] Correlation name for error messages. 1278 * @param array $correlation['operations'] An array of correlation operations. 1279 * @param int $correlation['operations']['type'] Correlation operation type. 1280 * Possible values are: 1281 * 0 - ZBX_CORR_OPERATION_CLOSE_OLD; 1282 * 1 - ZBX_CORR_OPERATION_CLOSE_NEW. 1283 * @param CLimitedSetValidator $filter_operations_validator Operations validator. 1284 * 1285 * @throws APIException if the input is invalid. 1286 */ 1287 protected function validateOperations(array $correlation, CLimitedSetValidator $filter_operations_validator) { 1288 if (!is_array($correlation['operations'])) { 1289 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1290 } 1291 elseif (!$correlation['operations']) { 1292 self::exception(ZBX_API_ERROR_PARAMETERS, 1293 _s('No "%1$s" given for correlation "%2$s".', 'operations', $correlation['name']) 1294 ); 1295 } 1296 1297 foreach ($correlation['operations'] as $operation) { 1298 if (!array_key_exists('type', $operation)) { 1299 self::exception(ZBX_API_ERROR_PARAMETERS, 1300 _s('No operation type given for correlation "%1$s".', $correlation['name']) 1301 ); 1302 } 1303 elseif (is_array($operation['type'])) { 1304 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1305 } 1306 1307 if (!$filter_operations_validator->validate($operation['type'])) { 1308 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1309 'Incorrect value "%1$s" in field "%2$s" for correlation "%3$s".', 1310 $operation['type'], 1311 'type', 1312 $correlation['name'] 1313 )); 1314 } 1315 } 1316 1317 // Check that same operation types do not repeat. 1318 $duplicate = CArrayHelper::findDuplicate($correlation['operations'], 'type'); 1319 if ($duplicate) { 1320 self::exception(ZBX_API_ERROR_PARAMETERS, 1321 _s('Duplicate "%1$s" value "%2$s" for correlation "%3$s".', 'type', $duplicate['type'], 1322 $correlation['name'] 1323 ) 1324 ); 1325 } 1326 } 1327 1328 /** 1329 * Insert correlation condition values to their corresponding DB tables. 1330 * 1331 * @param array $conditions An array of conditions to create. 1332 * 1333 * @return array 1334 */ 1335 protected function addConditions(array $conditions) { 1336 $conditions = DB::save('corr_condition', $conditions); 1337 1338 $corr_condition_tags_to_create = []; 1339 $corr_condition_hostgroups_to_create = []; 1340 $corr_condition_tag_pairs_to_create = []; 1341 $corr_condition_tag_values_to_create = []; 1342 1343 foreach ($conditions as $condition) { 1344 switch ($condition['type']) { 1345 case ZBX_CORR_CONDITION_OLD_EVENT_TAG: 1346 case ZBX_CORR_CONDITION_NEW_EVENT_TAG: 1347 $corr_condition_tags_to_create[] = $condition; 1348 break; 1349 1350 case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 1351 $corr_condition_hostgroups_to_create[] = $condition; 1352 break; 1353 1354 case ZBX_CORR_CONDITION_EVENT_TAG_PAIR: 1355 $corr_condition_tag_pairs_to_create[] = $condition; 1356 break; 1357 1358 case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE: 1359 case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 1360 $corr_condition_tag_values_to_create[] = $condition; 1361 break; 1362 } 1363 } 1364 1365 if ($corr_condition_tags_to_create) { 1366 DB::insert('corr_condition_tag', $corr_condition_tags_to_create, false); 1367 } 1368 1369 if ($corr_condition_hostgroups_to_create) { 1370 DB::insert('corr_condition_group', $corr_condition_hostgroups_to_create, false); 1371 } 1372 1373 if ($corr_condition_tag_pairs_to_create) { 1374 DB::insert('corr_condition_tagpair', $corr_condition_tag_pairs_to_create, false); 1375 } 1376 1377 if ($corr_condition_tag_values_to_create) { 1378 DB::insert('corr_condition_tagvalue', $corr_condition_tag_values_to_create, false); 1379 } 1380 1381 return $conditions; 1382 } 1383 1384 /** 1385 * Apply query output options. 1386 * 1387 * @param type $table_name 1388 * @param type $table_alias 1389 * @param array $options 1390 * @param array $sql_parts 1391 * 1392 * @return array 1393 */ 1394 protected function applyQueryOutputOptions($table_name, $table_alias, array $options, array $sql_parts) { 1395 $sql_parts = parent::applyQueryOutputOptions($table_name, $table_alias, $options, $sql_parts); 1396 1397 if (!$options['countOutput']) { 1398 // Add filter fields. 1399 if ($this->outputIsRequested('formula', $options['selectFilter']) 1400 || $this->outputIsRequested('eval_formula', $options['selectFilter']) 1401 || $this->outputIsRequested('conditions', $options['selectFilter'])) { 1402 1403 $sql_parts = $this->addQuerySelect('c.formula', $sql_parts); 1404 $sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts); 1405 } 1406 1407 if ($this->outputIsRequested('evaltype', $options['selectFilter'])) { 1408 $sql_parts = $this->addQuerySelect('c.evaltype', $sql_parts); 1409 } 1410 } 1411 1412 return $sql_parts; 1413 } 1414 1415 /** 1416 * Extend result with requested objects. 1417 * 1418 * @param array $options 1419 * @param array $result 1420 * 1421 * @return array 1422 */ 1423 protected function addRelatedObjects(array $options, array $result) { 1424 $result = parent::addRelatedObjects($options, $result); 1425 1426 $correlationids = array_keys($result); 1427 1428 // Adding formulas and conditions. 1429 if ($options['selectFilter'] !== null) { 1430 $formula_requested = $this->outputIsRequested('formula', $options['selectFilter']); 1431 $eval_formula_requested = $this->outputIsRequested('eval_formula', $options['selectFilter']); 1432 $conditions_requested = $this->outputIsRequested('conditions', $options['selectFilter']); 1433 1434 $filters = []; 1435 1436 if ($options['selectFilter']) { 1437 foreach ($result as $correlation) { 1438 $filters[$correlation['correlationid']] = [ 1439 'evaltype' => $correlation['evaltype'], 1440 'formula' => array_key_exists('formula', $correlation) ? $correlation['formula'] : '', 1441 'conditions' => [] 1442 ]; 1443 } 1444 1445 if ($formula_requested || $eval_formula_requested || $conditions_requested) { 1446 $sql = 'SELECT c.correlationid,c.corr_conditionid,c.type,ct.tag AS ct_tag,'. 1447 'cg.operator AS cg_operator,cg.groupid,ctp.oldtag,ctp.newtag,ctv.tag AS ctv_tag,'. 1448 'ctv.operator AS ctv_operator,ctv.value'. 1449 ' FROM corr_condition c'. 1450 ' LEFT JOIN corr_condition_tag ct ON ct.corr_conditionid = c.corr_conditionid'. 1451 ' LEFT JOIN corr_condition_group cg ON cg.corr_conditionid = c.corr_conditionid'. 1452 ' LEFT JOIN corr_condition_tagpair ctp ON ctp.corr_conditionid = c.corr_conditionid'. 1453 ' LEFT JOIN corr_condition_tagvalue ctv ON ctv.corr_conditionid = c.corr_conditionid'. 1454 ' WHERE '.dbConditionInt('c.correlationid', $correlationids); 1455 1456 $db_corr_conditions = DBselect($sql); 1457 1458 while ($row = DBfetch($db_corr_conditions)) { 1459 $fields = [ 1460 'corr_conditionid' => $row['corr_conditionid'], 1461 'correlationid' => $row['correlationid'], 1462 'type' => $row['type'] 1463 ]; 1464 1465 switch ($row['type']) { 1466 case ZBX_CORR_CONDITION_OLD_EVENT_TAG: 1467 case ZBX_CORR_CONDITION_NEW_EVENT_TAG: 1468 $fields['tag'] = $row['ct_tag']; 1469 break; 1470 1471 case ZBX_CORR_CONDITION_NEW_EVENT_HOSTGROUP: 1472 $fields['operator'] = $row['cg_operator']; 1473 $fields['groupid'] = $row['groupid']; 1474 break; 1475 1476 case ZBX_CORR_CONDITION_EVENT_TAG_PAIR: 1477 $fields['oldtag'] = $row['oldtag']; 1478 $fields['newtag'] = $row['newtag']; 1479 break; 1480 1481 case ZBX_CORR_CONDITION_OLD_EVENT_TAG_VALUE: 1482 case ZBX_CORR_CONDITION_NEW_EVENT_TAG_VALUE: 1483 $fields['tag'] = $row['ctv_tag']; 1484 $fields['operator'] = $row['ctv_operator']; 1485 $fields['value'] = $row['value']; 1486 break; 1487 } 1488 1489 $filters[$row['correlationid']]['conditions'][] = $fields; 1490 } 1491 1492 foreach ($filters as &$filter) { 1493 // In case of a custom expression, use the given formula. 1494 if ($filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1495 $formula = $filter['formula']; 1496 } 1497 // In other cases generate the formula automatically. 1498 else { 1499 $conditions = $filter['conditions']; 1500 CArrayHelper::sort($conditions, ['type']); 1501 $conditions_for_formula = []; 1502 1503 foreach ($conditions as $condition) { 1504 $conditions_for_formula[$condition['corr_conditionid']] = $condition['type']; 1505 } 1506 1507 $formula = CConditionHelper::getFormula($conditions_for_formula, $filter['evaltype']); 1508 } 1509 1510 // Generate formulaids from the effective formula. 1511 $formulaids = CConditionHelper::getFormulaIds($formula); 1512 1513 foreach ($filter['conditions'] as &$condition) { 1514 $condition['formulaid'] = $formulaids[$condition['corr_conditionid']]; 1515 } 1516 unset($condition); 1517 1518 // Generated a letter based formula only for actions with custom expressions. 1519 if ($formula_requested && $filter['evaltype'] == CONDITION_EVAL_TYPE_EXPRESSION) { 1520 $filter['formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids); 1521 } 1522 1523 if ($eval_formula_requested) { 1524 $filter['eval_formula'] = CConditionHelper::replaceNumericIds($formula, $formulaids); 1525 } 1526 } 1527 unset($filter); 1528 } 1529 } 1530 else { 1531 // In case no fields are actually selected in "filter", return empty array. 1532 foreach ($result as $correlation) { 1533 $filters[$correlation['correlationid']] = []; 1534 } 1535 } 1536 1537 // Add filters to the result. 1538 foreach ($result as &$correlation) { 1539 $correlation['filter'] = $filters[$correlation['correlationid']]; 1540 } 1541 unset($correlation); 1542 } 1543 1544 // Adding operations. 1545 if ($options['selectOperations'] !== null && $options['selectOperations'] != API_OUTPUT_COUNT) { 1546 $operations = API::getApiService()->select('corr_operation', [ 1547 'output' => $this->outputExtend($options['selectOperations'], [ 1548 'correlationid', 'corr_operationid', 'type' 1549 ]), 1550 'filter' => ['correlationid' => $correlationids], 1551 'preservekeys' => true 1552 ]); 1553 $relation_map = $this->createRelationMap($operations, 'correlationid', 'corr_operationid'); 1554 1555 foreach ($operations as &$operation) { 1556 unset($operation['correlationid'], $operation['corr_operationid']); 1557 } 1558 unset($operation); 1559 1560 $result = $relation_map->mapMany($result, $operations, 'operations'); 1561 } 1562 1563 return $result; 1564 } 1565} 1566