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 common methods for operations with triggers. 24 */ 25abstract class CTriggerGeneral extends CApiService { 26 27 /** 28 * @abstract 29 * 30 * @param array $options 31 * 32 * @return array 33 */ 34 abstract public function get(array $options = []); 35 36 /** 37 * Prepares and returns an array of child triggers, inherited from triggers $tpl_triggers on the given hosts. 38 * 39 * @param array $tpl_triggers 40 * @param string $tpl_triggers[<tnum>]['triggerid'] 41 */ 42 private function prepareInheritedTriggers(array $tpl_triggers, array $hostids = null, array &$ins_triggers = null, 43 array &$upd_triggers = null, array &$db_triggers = null) { 44 $ins_triggers = []; 45 $upd_triggers = []; 46 $db_triggers = []; 47 48 $result = DBselect( 49 'SELECT DISTINCT t.triggerid,h.hostid'. 50 ' FROM triggers t,functions f,items i,hosts h'. 51 ' WHERE t.triggerid=f.triggerid'. 52 ' AND f.itemid=i.itemid'. 53 ' AND i.hostid=h.hostid'. 54 ' AND '.dbConditionInt('t.triggerid', zbx_objectValues($tpl_triggers, 'triggerid')). 55 ' AND '.dbConditionInt('h.status', [HOST_STATUS_TEMPLATE]) 56 ); 57 58 $tpl_hostids_by_triggerid = []; 59 $tpl_hostids = []; 60 61 while ($row = DBfetch($result)) { 62 $tpl_hostids_by_triggerid[$row['triggerid']][] = $row['hostid']; 63 $tpl_hostids[$row['hostid']] = true; 64 } 65 66 // Unset host-level triggers. 67 foreach ($tpl_triggers as $tnum => $tpl_trigger) { 68 if (!array_key_exists($tpl_trigger['triggerid'], $tpl_hostids_by_triggerid)) { 69 unset($tpl_triggers[$tnum]); 70 } 71 } 72 73 if (!$tpl_triggers) { 74 // Nothing to inherit, just exit. 75 return; 76 } 77 78 $hosts_by_tpl_hostid = self::getLinkedHosts(array_keys($tpl_hostids), $hostids); 79 $chd_triggers_tpl = self::getHostTriggersByTemplateId(array_keys($tpl_hostids_by_triggerid), $hostids); 80 $tpl_triggers_by_description = []; 81 82 // Preparing list of missing triggers on linked hosts. 83 foreach ($tpl_triggers as $tpl_trigger) { 84 $hostids = []; 85 86 foreach ($tpl_hostids_by_triggerid[$tpl_trigger['triggerid']] as $tpl_hostid) { 87 if (array_key_exists($tpl_hostid, $hosts_by_tpl_hostid)) { 88 foreach ($hosts_by_tpl_hostid[$tpl_hostid] as $host) { 89 if (array_key_exists($host['hostid'], $chd_triggers_tpl) 90 && array_key_exists($tpl_trigger['triggerid'], $chd_triggers_tpl[$host['hostid']])) { 91 continue; 92 } 93 94 $hostids[$host['hostid']] = true; 95 } 96 } 97 } 98 99 if ($hostids) { 100 $tpl_triggers_by_description[$tpl_trigger['description']][] = [ 101 'triggerid' => $tpl_trigger['triggerid'], 102 'expression' => $tpl_trigger['expression'], 103 'recovery_mode' => $tpl_trigger['recovery_mode'], 104 'recovery_expression' => $tpl_trigger['recovery_expression'], 105 'hostids' => $hostids 106 ]; 107 } 108 } 109 110 $chd_triggers_all = array_replace_recursive($chd_triggers_tpl, 111 $this->getHostTriggersByDescription($tpl_triggers_by_description) 112 ); 113 114 $expression_data = new CTriggerExpression(['lldmacros' => $this instanceof CTriggerPrototype]); 115 $recovery_expression_data = new CTriggerExpression(['lldmacros' => $this instanceof CTriggerPrototype]); 116 117 // List of triggers to check for duplicates. Grouped by description. 118 $descriptions = []; 119 $triggerids = []; 120 121 $db_tpl_triggers = DB::select('triggers', [ 122 'output' => ['url', 'status', 'priority', 'comments', 'type', 'correlation_mode', 'correlation_tag', 123 'manual_close' 124 ], 125 'triggerids' => array_keys($tpl_hostids_by_triggerid), 126 'preservekeys' => true 127 ]); 128 129 foreach ($tpl_triggers as $tpl_trigger) { 130 $db_tpl_trigger = $db_tpl_triggers[$tpl_trigger['triggerid']]; 131 132 $tpl_hostid = $tpl_hostids_by_triggerid[$tpl_trigger['triggerid']][0]; 133 134 // expression: {template:item.func()} => {host:item.func()} 135 if (!$expression_data->parse($tpl_trigger['expression'])) { 136 self::exception(ZBX_API_ERROR_PARAMETERS, $expression_data->error); 137 } 138 139 // recovery_expression: {template:item.func()} => {host:item.func()} 140 if ($tpl_trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 141 if (!$recovery_expression_data->parse($tpl_trigger['recovery_expression'])) { 142 self::exception(ZBX_API_ERROR_PARAMETERS, $recovery_expression_data->error); 143 } 144 } 145 146 $new_trigger = $tpl_trigger; 147 unset($new_trigger['triggerid'], $new_trigger['templateid']); 148 149 if (array_key_exists($tpl_hostid, $hosts_by_tpl_hostid)) { 150 foreach ($hosts_by_tpl_hostid[$tpl_hostid] as $host) { 151 $new_trigger['expression'] = $tpl_trigger['expression']; 152 $expr_part = end($expression_data->expressions); 153 do { 154 $new_trigger['expression'] = substr_replace($new_trigger['expression'], 155 '{'.$host['host'].':'.$expr_part['item'].'.'.$expr_part['function'].'}', 156 $expr_part['pos'], strlen($expr_part['expression']) 157 ); 158 } 159 while ($expr_part = prev($expression_data->expressions)); 160 161 if ($tpl_trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 162 $new_trigger['recovery_expression'] = $tpl_trigger['recovery_expression']; 163 $expr_part = end($recovery_expression_data->expressions); 164 do { 165 $new_trigger['recovery_expression'] = substr_replace($new_trigger['recovery_expression'], 166 '{'.$host['host'].':'.$expr_part['item'].'.'.$expr_part['function'].'}', 167 $expr_part['pos'], strlen($expr_part['expression']) 168 ); 169 } 170 while ($expr_part = prev($recovery_expression_data->expressions)); 171 } 172 173 if (array_key_exists($host['hostid'], $chd_triggers_all) 174 && array_key_exists($tpl_trigger['triggerid'], $chd_triggers_all[$host['hostid']])) { 175 $chd_trigger = $chd_triggers_all[$host['hostid']][$tpl_trigger['triggerid']]; 176 177 $upd_triggers[] = $new_trigger + [ 178 'triggerid' => $chd_trigger['triggerid'], 179 'templateid' => $tpl_trigger['triggerid'] 180 ]; 181 $db_triggers[] = $chd_trigger; 182 $triggerids[] = $chd_trigger['triggerid']; 183 184 $check_duplicates = ($chd_trigger['description'] !== $new_trigger['description'] 185 || $chd_trigger['expression'] !== $new_trigger['expression'] 186 || $chd_trigger['recovery_expression'] !== $new_trigger['recovery_expression']); 187 } 188 else { 189 $ins_triggers[] = $new_trigger + $db_tpl_trigger + ['templateid' => $tpl_trigger['triggerid']]; 190 $check_duplicates = true; 191 } 192 193 if ($check_duplicates) { 194 $descriptions[$new_trigger['description']][] = [ 195 'expression' => $new_trigger['expression'], 196 'recovery_expression' => $new_trigger['recovery_expression'], 197 'hostid' => $host['hostid'] 198 ]; 199 } 200 } 201 } 202 } 203 204 if ($triggerids) { 205 // Add trigger tags. 206 $result = DBselect( 207 'SELECT tt.triggertagid,tt.triggerid,tt.tag,tt.value'. 208 ' FROM trigger_tag tt'. 209 ' WHERE '.dbConditionInt('tt.triggerid', $triggerids) 210 ); 211 212 $trigger_tags = []; 213 214 while ($row = DBfetch($result)) { 215 $trigger_tags[$row['triggerid']][] = [ 216 'triggertagid' => $row['triggertagid'], 217 'tag' => $row['tag'], 218 'value' => $row['value'] 219 ]; 220 } 221 222 foreach ($db_triggers as $tnum => $db_trigger) { 223 $db_triggers[$tnum]['tags'] = array_key_exists($db_trigger['triggerid'], $trigger_tags) 224 ? $trigger_tags[$db_trigger['triggerid']] 225 : []; 226 } 227 228 // Add discovery rule IDs. 229 if ($this instanceof CTriggerPrototype) { 230 $result = DBselect( 231 'SELECT id.parent_itemid,f.triggerid'. 232 ' FROM item_discovery id,functions f'. 233 ' WHERE '.dbConditionInt('f.triggerid', $triggerids). 234 ' AND f.itemid=id.itemid' 235 ); 236 237 $drule_by_triggerid = []; 238 239 while ($row = DBfetch($result)) { 240 $drule_by_triggerid[$row['triggerid']] = $row['parent_itemid']; 241 } 242 243 foreach ($db_triggers as $tnum => $db_trigger) { 244 $db_triggers[$tnum]['discoveryRule']['itemid'] = $drule_by_triggerid[$db_trigger['triggerid']]; 245 } 246 } 247 } 248 249 $this->checkDuplicates($descriptions); 250 } 251 252 /** 253 * Returns list of linked hosts. 254 * 255 * Output format: 256 * [ 257 * <tpl_hostid> => [ 258 * [ 259 * 'hostid' => <hostid>, 260 * 'host' => <host> 261 * ], 262 * ... 263 * ], 264 * ... 265 * ] 266 * 267 * @param array $tpl_hostids 268 * @param array $hostids The function will return a list of all linked hosts if no hostids are specified. 269 * 270 * @return array 271 */ 272 private static function getLinkedHosts(array $tpl_hostids, array $hostids = null) { 273 // Fetch all child hosts and templates 274 $sql = 'SELECT ht.hostid,ht.templateid,h.host'. 275 ' FROM hosts_templates ht,hosts h'. 276 ' WHERE ht.hostid=h.hostid'. 277 ' AND '.dbConditionInt('ht.templateid', $tpl_hostids). 278 ' AND '.dbConditionInt('h.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]); 279 if ($hostids !== null) { 280 $sql .= ' AND '.dbConditionInt('ht.hostid', $hostids); 281 } 282 $result = DBselect($sql); 283 284 $hosts_by_tpl_hostid = []; 285 286 while ($row = DBfetch($result)) { 287 $hosts_by_tpl_hostid[$row['templateid']][] = [ 288 'hostid' => $row['hostid'], 289 'host' => $row['host'] 290 ]; 291 } 292 293 return $hosts_by_tpl_hostid; 294 } 295 296 /** 297 * Returns list of already linked triggers. 298 * 299 * Output format: 300 * [ 301 * <hostid> => [ 302 * <tpl_triggerid> => ['triggerid' => <triggerid>], 303 * ... 304 * ], 305 * ... 306 * ] 307 * 308 * @param array $tpl_triggerids 309 * @param array $hostids The function will return a list of all linked triggers if no hosts are specified. 310 * 311 * @return array 312 */ 313 private static function getHostTriggersByTemplateId(array $tpl_triggerids, array $hostids = null) { 314 // Preparing list of triggers by templateid. 315 $sql = 'SELECT DISTINCT t.triggerid,t.expression,t.description,t.url,t.status,t.priority,t.comments,t.type,'. 316 't.recovery_mode,t.recovery_expression,t.correlation_mode,t.correlation_tag,t.manual_close,'. 317 't.templateid,i.hostid'. 318 ' FROM triggers t,functions f,items i'. 319 ' WHERE t.triggerid=f.triggerid'. 320 ' AND f.itemid=i.itemid'. 321 ' AND '.dbConditionInt('t.templateid', $tpl_triggerids); 322 if ($hostids !== null) { 323 $sql .= ' AND '.dbConditionInt('i.hostid', $hostids); 324 } 325 326 $chd_triggers = DBfetchArray(DBselect($sql)); 327 $chd_triggers = CMacrosResolverHelper::resolveTriggerExpressions($chd_triggers, 328 ['sources' => ['expression', 'recovery_expression']] 329 ); 330 331 $chd_triggers_tpl = []; 332 333 foreach ($chd_triggers as $chd_trigger) { 334 $hostid = $chd_trigger['hostid']; 335 unset($chd_trigger['hostid']); 336 337 $chd_triggers_tpl[$hostid][$chd_trigger['templateid']] = $chd_trigger; 338 } 339 340 return $chd_triggers_tpl; 341 } 342 343 /** 344 * Returns list of not inherited triggers with same name and expression. 345 * 346 * Output format: 347 * [ 348 * <hostid> => [ 349 * <tpl_triggerid> => ['triggerid' => <triggerid>], 350 * ... 351 * ], 352 * ... 353 * ] 354 * 355 * @param array $tpl_triggers_by_description The list of hostids, grouped by trigger description and expression. 356 * 357 * @return array 358 */ 359 private function getHostTriggersByDescription(array $tpl_triggers_by_description) { 360 $chd_triggers_description = []; 361 362 $expression_data = new CTriggerExpression(['lldmacros' => $this instanceof CTriggerPrototype]); 363 $recovery_expression_data = new CTriggerExpression(['lldmacros' => $this instanceof CTriggerPrototype]); 364 365 foreach ($tpl_triggers_by_description as $description => $tpl_triggers) { 366 $hostids = []; 367 368 foreach ($tpl_triggers as $tpl_trigger) { 369 $hostids += $tpl_trigger['hostids']; 370 } 371 372 $chd_triggers = DBfetchArray(DBselect( 373 'SELECT DISTINCT t.triggerid,t.expression,t.description,t.url,t.status,t.priority,t.comments,t.type,'. 374 't.recovery_mode,t.recovery_expression,t.correlation_mode,t.correlation_tag,t.manual_close,'. 375 'i.hostid,h.host'. 376 ' FROM triggers t,functions f,items i,hosts h'. 377 ' WHERE t.triggerid=f.triggerid'. 378 ' AND f.itemid=i.itemid'. 379 ' AND i.hostid=h.hostid'. 380 ' AND '.dbConditionString('t.description', [$description]). 381 ' AND '.dbConditionInt('i.hostid', array_keys($hostids)) 382 )); 383 384 $chd_triggers = CMacrosResolverHelper::resolveTriggerExpressions($chd_triggers, 385 ['sources' => ['expression', 'recovery_expression']] 386 ); 387 388 foreach ($tpl_triggers as $tpl_trigger) { 389 // expression: {template:item.func()} => {host:item.func()} 390 if (!$expression_data->parse($tpl_trigger['expression'])) { 391 self::exception(ZBX_API_ERROR_PARAMETERS, $expression_data->error); 392 } 393 394 // recovery_expression: {template:item.func()} => {host:item.func()} 395 if ($tpl_trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 396 if (!$recovery_expression_data->parse($tpl_trigger['recovery_expression'])) { 397 self::exception(ZBX_API_ERROR_PARAMETERS, $recovery_expression_data->error); 398 } 399 } 400 401 foreach ($chd_triggers as $chd_trigger) { 402 if (!array_key_exists($chd_trigger['hostid'], $tpl_trigger['hostids'])) { 403 continue; 404 } 405 406 if ($chd_trigger['recovery_mode'] != $tpl_trigger['recovery_mode']) { 407 continue; 408 } 409 410 $expression = $tpl_trigger['expression']; 411 $expr_part = end($expression_data->expressions); 412 do { 413 $expression = substr_replace($expression, 414 '{'.$chd_trigger['host'].':'.$expr_part['item'].'.'.$expr_part['function'].'}', 415 $expr_part['pos'], strlen($expr_part['expression']) 416 ); 417 } 418 while ($expr_part = prev($expression_data->expressions)); 419 420 if ($chd_trigger['expression'] !== $expression) { 421 continue; 422 } 423 424 if ($tpl_trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 425 $recovery_expression = $tpl_trigger['recovery_expression']; 426 $expr_part = end($recovery_expression_data->expressions); 427 do { 428 $recovery_expression = substr_replace($recovery_expression, 429 '{'.$chd_trigger['host'].':'.$expr_part['item'].'.'.$expr_part['function'].'}', 430 $expr_part['pos'], strlen($expr_part['expression']) 431 ); 432 } 433 while ($expr_part = prev($recovery_expression_data->expressions)); 434 435 if ($chd_trigger['recovery_expression'] !== $recovery_expression) { 436 continue; 437 } 438 } 439 440 $hostid = $chd_trigger['hostid']; 441 unset($chd_trigger['hostid'], $chd_trigger['host']); 442 $chd_triggers_description[$hostid][$tpl_trigger['triggerid']] = $chd_trigger + ['templateid' => 0]; 443 } 444 } 445 } 446 447 return $chd_triggers_description; 448 } 449 450 /** 451 * Updates the children of the triggers on the given hosts and propagates the inheritance to all child hosts. 452 * If the given triggers was assigned to a different template or a host, all of the child triggers, that became 453 * obsolete will be deleted. 454 * 455 * @param array $triggers 456 * @param string $triggers[]['triggerid'] 457 * @param string $triggers[]['description'] 458 * @param string $triggers[]['expression'] 459 * @param int $triggers[]['recovery mode'] 460 * @param string $triggers[]['recovery_expression'] 461 * @param array $hostids 462 */ 463 protected function inherit(array $triggers, array $hostids = null) { 464 $this->prepareInheritedTriggers($triggers, $hostids, $ins_triggers, $upd_triggers, $db_triggers); 465 466 if ($ins_triggers) { 467 $this->createReal($ins_triggers, true); 468 } 469 470 if ($upd_triggers) { 471 $this->updateReal($upd_triggers, $db_triggers, true); 472 } 473 474 if ($ins_triggers || $upd_triggers) { 475 $this->inherit(array_merge($ins_triggers + $upd_triggers)); 476 } 477 } 478 479 /** 480 * Populate an array by "hostid" keys. 481 * 482 * @param array $descriptions 483 * @param string $descriptions[<description>][]['expression'] 484 * 485 * @throws APIException If host or template does not exists. 486 * 487 * @return array 488 */ 489 protected function populateHostIds($descriptions) { 490 $expression_data = new CTriggerExpression(['lldmacros' => $this instanceof CTriggerPrototype]); 491 492 $hosts = []; 493 494 foreach ($descriptions as $description => $triggers) { 495 foreach ($triggers as $index => $trigger) { 496 $expression_data->parse($trigger['expression']); 497 $hosts[$expression_data->getHosts()[0]][$description][] = $index; 498 } 499 } 500 501 $db_hosts = DBselect( 502 'SELECT h.hostid,h.host'. 503 ' FROM hosts h'. 504 ' WHERE '.dbConditionInt('h.host', array_keys($hosts)). 505 ' AND '.dbConditionInt('h.status', 506 [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED, HOST_STATUS_TEMPLATE] 507 ) 508 ); 509 510 while ($db_host = DBfetch($db_hosts)) { 511 foreach ($hosts[$db_host['host']] as $description => $indexes) { 512 foreach ($indexes as $index) { 513 $descriptions[$description][$index]['hostid'] = $db_host['hostid']; 514 } 515 } 516 unset($hosts[$db_host['host']]); 517 } 518 519 if ($hosts) { 520 $error_wrong_host = ($this instanceof CTrigger) 521 ? _('Incorrect trigger expression. Host "%1$s" does not exist or you have no access to this host.') 522 : _('Incorrect trigger prototype expression. Host "%1$s" does not exist or you have no access to this host.'); 523 self::exception(ZBX_API_ERROR_PARAMETERS, _params($error_wrong_host, [key($hosts)])); 524 } 525 526 return $descriptions; 527 } 528 529 /** 530 * Checks triggers for duplicates. 531 * 532 * @param array $descriptions 533 * @param string $descriptions[<description>][]['expression'] 534 * @param string $descriptions[<description>][]['recovery_expression'] 535 * @param string $descriptions[<description>][]['hostid'] 536 * 537 * @throws APIException if at least one trigger exists 538 */ 539 protected function checkDuplicates(array $descriptions) { 540 foreach ($descriptions as $description => $triggers) { 541 $hostids = []; 542 $expressions = []; 543 544 foreach ($triggers as $trigger) { 545 $hostids[$trigger['hostid']] = true; 546 $expressions[$trigger['expression']][$trigger['recovery_expression']] = $trigger['hostid']; 547 } 548 549 $db_triggers = DBfetchArray(DBselect( 550 'SELECT DISTINCT t.expression,t.recovery_expression'. 551 ' FROM triggers t,functions f,items i,hosts h'. 552 ' WHERE t.triggerid=f.triggerid'. 553 ' AND f.itemid=i.itemid'. 554 ' AND i.hostid=h.hostid'. 555 ' AND '.dbConditionString('t.description', [$description]). 556 ' AND '.dbConditionInt('i.hostid', array_keys($hostids)) 557 )); 558 559 $db_triggers = CMacrosResolverHelper::resolveTriggerExpressions($db_triggers, 560 ['sources' => ['expression', 'recovery_expression']] 561 ); 562 563 foreach ($db_triggers as $db_trigger) { 564 $expression = $db_trigger['expression']; 565 $recovery_expression = $db_trigger['recovery_expression']; 566 567 if (array_key_exists($expression, $expressions) 568 && array_key_exists($recovery_expression, $expressions[$expression])) { 569 $error_already_exists = ($this instanceof CTrigger) 570 ? _('Trigger "%1$s" already exists on "%2$s".') 571 : _('Trigger prototype "%1$s" already exists on "%2$s".'); 572 573 $db_hosts = DB::select('hosts', [ 574 'output' => ['name'], 575 'hostids' => $expressions[$expression][$recovery_expression] 576 ]); 577 578 self::exception(ZBX_API_ERROR_PARAMETERS, 579 _params($error_already_exists, [$description, $db_hosts[0]['name']]) 580 ); 581 } 582 } 583 } 584 } 585 586 protected function addRelatedObjects(array $options, array $result) { 587 $result = parent::addRelatedObjects($options, $result); 588 589 $triggerids = array_keys($result); 590 591 // adding groups 592 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 593 $res = DBselect( 594 'SELECT f.triggerid,hg.groupid'. 595 ' FROM functions f,items i,hosts_groups hg'. 596 ' WHERE '.dbConditionInt('f.triggerid', $triggerids). 597 ' AND f.itemid=i.itemid'. 598 ' AND i.hostid=hg.hostid' 599 ); 600 $relationMap = new CRelationMap(); 601 while ($relation = DBfetch($res)) { 602 $relationMap->addRelation($relation['triggerid'], $relation['groupid']); 603 } 604 605 $groups = API::HostGroup()->get([ 606 'output' => $options['selectGroups'], 607 'groupids' => $relationMap->getRelatedIds(), 608 'preservekeys' => true 609 ]); 610 $result = $relationMap->mapMany($result, $groups, 'groups'); 611 } 612 613 // adding hosts 614 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 615 $res = DBselect( 616 'SELECT f.triggerid,i.hostid'. 617 ' FROM functions f,items i'. 618 ' WHERE '.dbConditionInt('f.triggerid', $triggerids). 619 ' AND f.itemid=i.itemid' 620 ); 621 $relationMap = new CRelationMap(); 622 while ($relation = DBfetch($res)) { 623 $relationMap->addRelation($relation['triggerid'], $relation['hostid']); 624 } 625 626 $hosts = API::Host()->get([ 627 'output' => $options['selectHosts'], 628 'hostids' => $relationMap->getRelatedIds(), 629 'templated_hosts' => true, 630 'nopermissions' => true, 631 'preservekeys' => true 632 ]); 633 if (!is_null($options['limitSelects'])) { 634 order_result($hosts, 'host'); 635 } 636 $result = $relationMap->mapMany($result, $hosts, 'hosts', $options['limitSelects']); 637 } 638 639 // adding functions 640 if ($options['selectFunctions'] !== null && $options['selectFunctions'] != API_OUTPUT_COUNT) { 641 $functions = API::getApiService()->select('functions', [ 642 'output' => $this->outputExtend($options['selectFunctions'], ['triggerid', 'functionid']), 643 'filter' => ['triggerid' => $triggerids], 644 'preservekeys' => true 645 ]); 646 647 // Rename column 'name' to 'function'. 648 $function = reset($functions); 649 if ($function && array_key_exists('name', $function)) { 650 $functions = CArrayHelper::renameObjectsKeys($functions, ['name' => 'function']); 651 } 652 653 $relationMap = $this->createRelationMap($functions, 'triggerid', 'functionid'); 654 655 $functions = $this->unsetExtraFields($functions, ['triggerid', 'functionid'], $options['selectFunctions']); 656 $result = $relationMap->mapMany($result, $functions, 'functions'); 657 } 658 659 // Adding trigger tags. 660 if ($options['selectTags'] !== null && $options['selectTags'] != API_OUTPUT_COUNT) { 661 $tags = API::getApiService()->select('trigger_tag', [ 662 'output' => $this->outputExtend($options['selectTags'], ['triggerid']), 663 'filter' => ['triggerid' => $triggerids], 664 'preservekeys' => true 665 ]); 666 667 $relationMap = $this->createRelationMap($tags, 'triggerid', 'triggertagid'); 668 $tags = $this->unsetExtraFields($tags, ['triggertagid', 'triggerid'], []); 669 $result = $relationMap->mapMany($result, $tags, 'tags'); 670 } 671 672 return $result; 673 } 674 675 /** 676 * Validate integrity of trigger recovery properties. 677 * 678 * @static 679 * 680 * @param array $trigger 681 * @param int $trigger['recovery_mode'] 682 * @param string $trigger['recovery_expression'] 683 * 684 * @throws APIException if validation failed. 685 */ 686 private static function checkTriggerRecoveryMode(array $trigger) { 687 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 688 if ($trigger['recovery_expression'] === '') { 689 self::exception(ZBX_API_ERROR_PARAMETERS, 690 _s('Incorrect value for field "%1$s": %2$s.', 'recovery_expression', _('cannot be empty')) 691 ); 692 } 693 } 694 elseif ($trigger['recovery_expression'] !== '') { 695 self::exception(ZBX_API_ERROR_PARAMETERS, 696 _s('Incorrect value for field "%1$s": %2$s.', 'recovery_expression', _('should be empty')) 697 ); 698 } 699 } 700 701 /** 702 * Validate trigger correlation mode and related properties. 703 * 704 * @static 705 * 706 * @param array $trigger 707 * @param int $trigger['correlation_mode'] 708 * @param string $trigger['correlation_tag'] 709 * @param int $trigger['recovery_mode'] 710 * 711 * @throws APIException if validation failed. 712 */ 713 private static function checkTriggerCorrelationMode(array $trigger) { 714 if ($trigger['correlation_mode'] == ZBX_TRIGGER_CORRELATION_TAG) { 715 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_NONE) { 716 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 717 'correlation_mode', _s('unexpected value "%1$s"', $trigger['correlation_mode']) 718 )); 719 } 720 721 if ($trigger['correlation_tag'] === '') { 722 self::exception(ZBX_API_ERROR_PARAMETERS, 723 _s('Incorrect value for field "%1$s": %2$s.', 'correlation_tag', _('cannot be empty')) 724 ); 725 } 726 } 727 elseif ($trigger['correlation_tag'] !== '') { 728 self::exception(ZBX_API_ERROR_PARAMETERS, 729 _s('Incorrect value for field "%1$s": %2$s.', 'correlation_tag', _('should be empty')) 730 ); 731 } 732 } 733 734 /** 735 * Validate trigger to be created. 736 * 737 * @param array $triggers [IN/OUT] 738 * @param array $triggers[]['description'] [IN] 739 * @param string $triggers[]['expression'] [IN] 740 * @param string $triggers[]['comments'] [IN] (optional) 741 * @param int $triggers[]['priority'] [IN] (optional) 742 * @param int $triggers[]['status'] [IN] (optional) 743 * @param int $triggers[]['type'] [IN] (optional) 744 * @param string $triggers[]['url'] [IN] (optional) 745 * @param int $triggers[]['recovery_mode'] [IN/OUT] (optional) 746 * @param string $triggers[]['recovery_expression'] [IN/OUT] (optional) 747 * @param int $triggers[]['correlation_mode'] [IN/OUT] (optional) 748 * @param string $triggers[]['correlation_tag'] [IN/OUT] (optional) 749 * @param int $triggers[]['manual_close'] [IN] (optional) 750 * @param array $triggers[]['tags'] [IN] (optional) 751 * @param string $triggers[]['tags'][]['tag'] [IN] 752 * @param string $triggers[]['tags'][]['value'] [IN/OUT] (optional) 753 * @param array $triggers[]['dependencies'] [IN] (optional) 754 * @param string $triggers[]['dependencies'][]['triggerid'] [IN] 755 * 756 * @throws APIException if validation failed. 757 */ 758 protected function validateCreate(array &$triggers) { 759 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['description', 'expression']], 'fields' => [ 760 'description' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('triggers', 'description')], 761 'expression' => ['type' => API_TRIGGER_EXPRESSION, 'flags' => API_REQUIRED | API_NOT_EMPTY | API_ALLOW_LLD_MACRO], 762 'comments' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('triggers', 'comments')], 763 'priority' => ['type' => API_INT32, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1))], 764 'status' => ['type' => API_INT32, 'in' => implode(',', [TRIGGER_STATUS_ENABLED, TRIGGER_STATUS_DISABLED])], 765 'type' => ['type' => API_INT32, 'in' => implode(',', [TRIGGER_MULT_EVENT_DISABLED, TRIGGER_MULT_EVENT_ENABLED])], 766 'url' => ['type' => API_URL, 'flags' => API_ALLOW_USER_MACRO, 'length' => DB::getFieldLength('triggers', 'url')], 767 'recovery_mode' => ['type' => API_INT32, 'in' => implode(',', [ZBX_RECOVERY_MODE_EXPRESSION, ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION, ZBX_RECOVERY_MODE_NONE]), 'default' => DB::getDefault('triggers', 'recovery_mode')], 768 'recovery_expression' => ['type' => API_TRIGGER_EXPRESSION, 'flags' => API_ALLOW_LLD_MACRO, 'default' => DB::getDefault('triggers', 'recovery_expression')], 769 'correlation_mode' => ['type' => API_INT32, 'in' => implode(',', [ZBX_TRIGGER_CORRELATION_NONE, ZBX_TRIGGER_CORRELATION_TAG]), 'default' => DB::getDefault('triggers', 'correlation_mode')], 770 'correlation_tag' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('triggers', 'correlation_tag'), 'default' => DB::getDefault('triggers', 'correlation_tag')], 771 'manual_close' => ['type' => API_INT32, 'in' => implode(',', [ZBX_TRIGGER_MANUAL_CLOSE_NOT_ALLOWED, ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED])], 772 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 773 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('trigger_tag', 'tag')], 774 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('trigger_tag', 'value'), 'default' => DB::getDefault('trigger_tag', 'value')] 775 ]], 776 'dependencies' => ['type' => API_OBJECTS, 'uniq' => [['triggerid']], 'fields'=> [ 777 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED] 778 ]] 779 ]]; 780 if (!$this instanceof CTriggerPrototype) { 781 $api_input_rules['fields']['expression']['flags'] &= ~API_ALLOW_LLD_MACRO; 782 $api_input_rules['fields']['recovery_expression']['flags'] &= ~API_ALLOW_LLD_MACRO; 783 } 784 if (!CApiInputValidator::validate($api_input_rules, $triggers, '/', $error)) { 785 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 786 } 787 788 $descriptions = []; 789 foreach ($triggers as $trigger) { 790 self::checkTriggerRecoveryMode($trigger); 791 self::checkTriggerCorrelationMode($trigger); 792 793 $descriptions[$trigger['description']][] = [ 794 'expression' => $trigger['expression'], 795 'recovery_expression' => $trigger['recovery_expression'] 796 ]; 797 } 798 $descriptions = $this->populateHostIds($descriptions); 799 $this->checkDuplicates($descriptions); 800 } 801 802 /** 803 * Validate trigger to be updated. 804 * 805 * @param array $triggers [IN/OUT] 806 * @param array $triggers[]['triggerid'] [IN] 807 * @param array $triggers[]['description'] [IN/OUT] (optional) 808 * @param string $triggers[]['expression'] [IN/OUT] (optional) 809 * @param string $triggers[]['comments'] [IN] (optional) 810 * @param int $triggers[]['priority'] [IN] (optional) 811 * @param int $triggers[]['status'] [IN] (optional) 812 * @param int $triggers[]['type'] [IN] (optional) 813 * @param string $triggers[]['url'] [IN] (optional) 814 * @param int $triggers[]['recovery_mode'] [IN/OUT] (optional) 815 * @param string $triggers[]['recovery_expression'] [IN/OUT] (optional) 816 * @param int $triggers[]['correlation_mode'] [IN/OUT] (optional) 817 * @param string $triggers[]['correlation_tag'] [IN/OUT] (optional) 818 * @param int $triggers[]['manual_close'] [IN] (optional) 819 * @param array $triggers[]['tags'] [IN] (optional) 820 * @param string $triggers[]['tags'][]['tag'] [IN] 821 * @param string $triggers[]['tags'][]['value'] [IN/OUT] (optional) 822 * @param array $triggers[]['dependencies'] [IN] (optional) 823 * @param string $triggers[]['dependencies'][]['triggerid'] [IN] 824 * @param array $db_triggers [OUT] 825 * @param array $db_triggers[<tnum>]['triggerid'] [OUT] 826 * @param array $db_triggers[<tnum>]['description'] [OUT] 827 * @param string $db_triggers[<tnum>]['expression'] [OUT] 828 * @param int $db_triggers[<tnum>]['recovery_mode'] [OUT] 829 * @param string $db_triggers[<tnum>]['recovery_expression'] [OUT] 830 * @param string $db_triggers[<tnum>]['url'] [OUT] 831 * @param int $db_triggers[<tnum>]['status'] [OUT] 832 * @param int $db_triggers[<tnum>]['priority'] [OUT] 833 * @param string $db_triggers[<tnum>]['comments'] [OUT] 834 * @param int $db_triggers[<tnum>]['type'] [OUT] 835 * @param string $db_triggers[<tnum>]['templateid'] [OUT] 836 * @param int $db_triggers[<tnum>]['correlation_mode'] [OUT] 837 * @param string $db_triggers[<tnum>]['correlation_tag'] [OUT] 838 * 839 * @throws APIException if validation failed. 840 */ 841 protected function validateUpdate(array &$triggers, array &$db_triggers = null) { 842 $db_triggers = []; 843 844 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['description', 'expression']], 'fields' => [ 845 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED], 846 'description' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('triggers', 'description')], 847 'expression' => ['type' => API_TRIGGER_EXPRESSION, 'flags' => API_NOT_EMPTY | API_ALLOW_LLD_MACRO], 848 'comments' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('triggers', 'comments')], 849 'priority' => ['type' => API_INT32, 'in' => implode(',', range(TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_COUNT - 1))], 850 'status' => ['type' => API_INT32, 'in' => implode(',', [TRIGGER_STATUS_ENABLED, TRIGGER_STATUS_DISABLED])], 851 'type' => ['type' => API_INT32, 'in' => implode(',', [TRIGGER_MULT_EVENT_DISABLED, TRIGGER_MULT_EVENT_ENABLED])], 852 'url' => ['type' => API_URL, 'flags' => API_ALLOW_USER_MACRO, 'length' => DB::getFieldLength('triggers', 'url')], 853 'recovery_mode' => ['type' => API_INT32, 'in' => implode(',', [ZBX_RECOVERY_MODE_EXPRESSION, ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION, ZBX_RECOVERY_MODE_NONE])], 854 'recovery_expression' => ['type' => API_TRIGGER_EXPRESSION, 'flags' => API_ALLOW_LLD_MACRO], 855 'correlation_mode' => ['type' => API_INT32, 'in' => implode(',', [ZBX_TRIGGER_CORRELATION_NONE, ZBX_TRIGGER_CORRELATION_TAG])], 856 'correlation_tag' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('triggers', 'correlation_tag')], 857 'manual_close' => ['type' => API_INT32, 'in' => implode(',', [ZBX_TRIGGER_MANUAL_CLOSE_NOT_ALLOWED, ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED])], 858 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 859 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('trigger_tag', 'tag')], 860 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('trigger_tag', 'value'), 'default' => DB::getDefault('trigger_tag', 'value')] 861 ]], 862 'dependencies' => ['type' => API_OBJECTS, 'uniq' => [['triggerid']], 'fields'=> [ 863 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED] 864 ]] 865 ]]; 866 if (!$this instanceof CTriggerPrototype) { 867 $api_input_rules['fields']['expression']['flags'] &= ~API_ALLOW_LLD_MACRO; 868 $api_input_rules['fields']['recovery_expression']['flags'] &= ~API_ALLOW_LLD_MACRO; 869 } 870 if (!CApiInputValidator::validate($api_input_rules, $triggers, '/', $error)) { 871 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 872 } 873 874 $options = [ 875 'output' => ['triggerid', 'description', 'expression', 'url', 'status', 'priority', 'comments', 'type', 876 'templateid', 'recovery_mode', 'recovery_expression', 'correlation_mode', 'correlation_tag', 877 'manual_close' 878 ], 879 'selectDependencies' => ['triggerid'], 880 'triggerids' => zbx_objectValues($triggers, 'triggerid'), 881 'editable' => true, 882 'preservekeys' => true 883 ]; 884 885 $class = get_class($this); 886 887 switch ($class) { 888 case 'CTrigger': 889 $error_cannot_update = _('Cannot update "%1$s" for templated trigger "%2$s".'); 890 $options['output'][] = 'flags'; 891 892 // Discovered fields, except status, cannot be updated. 893 $update_discovered_validator = new CUpdateDiscoveredValidator([ 894 'allowed' => ['triggerid', 'status'], 895 'messageAllowedField' => _('Cannot update "%2$s" for a discovered trigger "%1$s".') 896 ]); 897 break; 898 899 case 'CTriggerPrototype': 900 $error_cannot_update = _('Cannot update "%1$s" for templated trigger prototype "%2$s".'); 901 $options['selectDiscoveryRule'] = ['itemid']; 902 break; 903 904 default: 905 self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.')); 906 } 907 908 $_db_triggers = CMacrosResolverHelper::resolveTriggerExpressions($this->get($options), 909 ['sources' => ['expression', 'recovery_expression']] 910 ); 911 912 $db_trigger_tags = API::getApiService()->select('trigger_tag', [ 913 'output' => ['triggertagid', 'triggerid', 'tag', 'value'], 914 'filter' => ['triggerid' => array_keys($_db_triggers)], 915 'preservekeys' => true 916 ]); 917 $_db_triggers = $this->createRelationMap($db_trigger_tags, 'triggerid', 'triggertagid') 918 ->mapMany($_db_triggers, $db_trigger_tags, 'tags'); 919 920 $read_only_fields = ['description', 'expression', 'recovery_mode', 'recovery_expression', 'correlation_mode', 921 'correlation_tag', 'manual_close' 922 ]; 923 924 $descriptions = []; 925 926 foreach ($triggers as $key => &$trigger) { 927 if (!array_key_exists($trigger['triggerid'], $_db_triggers)) { 928 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 929 } 930 931 $db_trigger = $_db_triggers[$trigger['triggerid']]; 932 $description = array_key_exists('description', $trigger) 933 ? $trigger['description'] 934 : $db_trigger['description']; 935 936 if ($class === 'CTrigger') { 937 $update_discovered_validator->setObjectName($description); 938 $this->checkPartialValidator($trigger, $update_discovered_validator, $db_trigger); 939 } 940 941 if ($db_trigger['templateid'] != 0) { 942 $this->checkNoParameters($trigger, $read_only_fields, $error_cannot_update, $description); 943 } 944 945 $field_names = ['description', 'expression', 'recovery_mode', 'manual_close']; 946 foreach ($field_names as $field_name) { 947 if (!array_key_exists($field_name, $trigger)) { 948 $trigger[$field_name] = $db_trigger[$field_name]; 949 } 950 } 951 952 if (!array_key_exists('recovery_expression', $trigger)) { 953 $trigger['recovery_expression'] = ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) 954 ? $db_trigger['recovery_expression'] 955 : ''; 956 } 957 if (!array_key_exists('correlation_mode', $trigger)) { 958 $trigger['correlation_mode'] = ($trigger['recovery_mode'] != ZBX_RECOVERY_MODE_NONE) 959 ? $db_trigger['correlation_mode'] 960 : ZBX_TRIGGER_CORRELATION_NONE; 961 } 962 if (!array_key_exists('correlation_tag', $trigger)) { 963 $trigger['correlation_tag'] = ($trigger['correlation_mode'] == ZBX_TRIGGER_CORRELATION_TAG) 964 ? $db_trigger['correlation_tag'] 965 : ''; 966 } 967 968 self::checkTriggerRecoveryMode($trigger); 969 self::checkTriggerCorrelationMode($trigger); 970 971 if ($trigger['expression'] !== $db_trigger['expression'] 972 || $trigger['recovery_expression'] !== $db_trigger['recovery_expression'] 973 || $trigger['description'] !== $db_trigger['description']) { 974 $descriptions[$trigger['description']][] = [ 975 'expression' => $trigger['expression'], 976 'recovery_expression' => $trigger['recovery_expression'] 977 ]; 978 } 979 980 $db_triggers[$key] = $db_trigger; 981 } 982 unset($trigger); 983 984 if ($descriptions) { 985 $descriptions = $this->populateHostIds($descriptions); 986 $this->checkDuplicates($descriptions); 987 } 988 } 989 990 /** 991 * Inserts trigger or trigger prototypes records into the database. 992 * 993 * @param array $triggers [IN/OUT] 994 * @param array $triggers[]['triggerid'] [OUT] 995 * @param array $triggers[]['description'] [IN] 996 * @param string $triggers[]['expression'] [IN] 997 * @param int $triggers[]['recovery_mode'] [IN] 998 * @param string $triggers[]['recovery_expression'] [IN] 999 * @param string $triggers[]['url'] [IN] (optional) 1000 * @param int $triggers[]['status'] [IN] (optional) 1001 * @param int $triggers[]['priority'] [IN] (optional) 1002 * @param string $triggers[]['comments'] [IN] (optional) 1003 * @param int $triggers[]['type'] [IN] (optional) 1004 * @param string $triggers[]['templateid'] [IN] (optional) 1005 * @param array $triggers[]['tags'] [IN] (optional) 1006 * @param string $triggers[]['tags'][]['tag'] [IN] 1007 * @param string $triggers[]['tags'][]['value'] [IN] 1008 * @param int $triggers[]['correlation_mode'] [IN] (optional) 1009 * @param string $triggers[]['correlation_tag'] [IN] (optional) 1010 * @param bool $inherited [IN] (optional) If set to true, trigger will be created for 1011 * non-editable host/template. 1012 * 1013 * @throws APIException 1014 */ 1015 protected function createReal(array &$triggers, $inherited = false) { 1016 $class = get_class($this); 1017 1018 switch ($class) { 1019 case 'CTrigger': 1020 $resource = AUDIT_RESOURCE_TRIGGER; 1021 break; 1022 1023 case 'CTriggerPrototype': 1024 $resource = AUDIT_RESOURCE_TRIGGER_PROTOTYPE; 1025 break; 1026 1027 default: 1028 self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.')); 1029 } 1030 1031 $new_triggers = $triggers; 1032 $new_functions = []; 1033 $triggers_functions = []; 1034 $new_tags = []; 1035 $this->implode_expressions($new_triggers, null, $triggers_functions, $inherited); 1036 1037 $triggerid = DB::reserveIds('triggers', count($new_triggers)); 1038 1039 foreach ($new_triggers as $tnum => &$new_trigger) { 1040 $new_trigger['triggerid'] = $triggerid; 1041 $triggers[$tnum]['triggerid'] = $triggerid; 1042 1043 foreach ($triggers_functions[$tnum] as $trigger_function) { 1044 $trigger_function['triggerid'] = $triggerid; 1045 $new_functions[] = $trigger_function; 1046 } 1047 1048 if ($class === 'CTriggerPrototype') { 1049 $new_trigger['flags'] = ZBX_FLAG_DISCOVERY_PROTOTYPE; 1050 } 1051 1052 if (array_key_exists('tags', $new_trigger)) { 1053 foreach ($new_trigger['tags'] as $tag) { 1054 $tag['triggerid'] = $triggerid; 1055 $new_tags[] = $tag; 1056 } 1057 } 1058 1059 $triggerid = bcadd($triggerid, 1, 0); 1060 } 1061 unset($new_trigger); 1062 1063 DB::insert('triggers', $new_triggers, false); 1064 DB::insertBatch('functions', $new_functions, false); 1065 1066 if ($new_tags) { 1067 DB::insert('trigger_tag', $new_tags); 1068 } 1069 1070 if (!$inherited) { 1071 $this->addAuditBulk(AUDIT_ACTION_ADD, $resource, $triggers); 1072 } 1073 } 1074 1075 /** 1076 * Update trigger or trigger prototypes records in the database. 1077 * 1078 * @param array $triggers [IN] list of triggers to be updated 1079 * @param array $triggers[<tnum>]['triggerid'] [IN] 1080 * @param array $triggers[<tnum>]['description'] [IN] 1081 * @param string $triggers[<tnum>]['expression'] [IN] 1082 * @param int $triggers[<tnum>]['recovery_mode'] [IN] 1083 * @param string $triggers[<tnum>]['recovery_expression'] [IN] 1084 * @param string $triggers[<tnum>]['url'] [IN] (optional) 1085 * @param int $triggers[<tnum>]['status'] [IN] (optional) 1086 * @param int $triggers[<tnum>]['priority'] [IN] (optional) 1087 * @param string $triggers[<tnum>]['comments'] [IN] (optional) 1088 * @param int $triggers[<tnum>]['type'] [IN] (optional) 1089 * @param string $triggers[<tnum>]['templateid'] [IN] (optional) 1090 * @param array $triggers[<tnum>]['tags'] [IN] 1091 * @param string $triggers[<tnum>]['tags'][]['tag'] [IN] 1092 * @param string $triggers[<tnum>]['tags'][]['value'] [IN] 1093 * @param int $triggers[<tnum>]['correlation_mode'] [IN] 1094 * @param string $triggers[<tnum>]['correlation_tag'] [IN] 1095 * @param array $db_triggers [IN] 1096 * @param array $db_triggers[<tnum>]['triggerid'] [IN] 1097 * @param array $db_triggers[<tnum>]['description'] [IN] 1098 * @param string $db_triggers[<tnum>]['expression'] [IN] 1099 * @param int $db_triggers[<tnum>]['recovery_mode'] [IN] 1100 * @param string $db_triggers[<tnum>]['recovery_expression'] [IN] 1101 * @param string $db_triggers[<tnum>]['url'] [IN] 1102 * @param int $db_triggers[<tnum>]['status'] [IN] 1103 * @param int $db_triggers[<tnum>]['priority'] [IN] 1104 * @param string $db_triggers[<tnum>]['comments'] [IN] 1105 * @param int $db_triggers[<tnum>]['type'] [IN] 1106 * @param string $db_triggers[<tnum>]['templateid'] [IN] 1107 * @param array $db_triggers[<tnum>]['discoveryRule'] [IN] For trigger prototypes only. 1108 * @param string $db_triggers[<tnum>]['discoveryRule']['itemid'] [IN] 1109 * @param array $db_triggers[<tnum>]['tags'] [IN] 1110 * @param string $db_triggers[<tnum>]['tags'][]['tag'] [IN] 1111 * @param string $db_triggers[<tnum>]['tags'][]['value'] [IN] 1112 * @param int $db_triggers[<tnum>]['correlation_mode'] [IN] 1113 * @param string $db_triggers[<tnum>]['correlation_tag'] [IN] 1114 * @param bool $inherited [IN] (optional) If set to true, trigger will be 1115 * created for non-editable 1116 * host/template. 1117 * 1118 * @throws APIException 1119 */ 1120 protected function updateReal(array $triggers, array $db_triggers, $inherited = false) { 1121 $class = get_class($this); 1122 1123 switch ($class) { 1124 case 'CTrigger': 1125 $resource = AUDIT_RESOURCE_TRIGGER; 1126 break; 1127 1128 case 'CTriggerPrototype': 1129 $resource = AUDIT_RESOURCE_TRIGGER_PROTOTYPE; 1130 break; 1131 1132 default: 1133 self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.')); 1134 } 1135 1136 $upd_triggers = []; 1137 $new_functions = []; 1138 $del_functions_triggerids = []; 1139 $triggers_functions = []; 1140 $new_tags = []; 1141 $del_triggertagids = []; 1142 $save_triggers = $triggers; 1143 $this->implode_expressions($triggers, $db_triggers, $triggers_functions, $inherited); 1144 1145 if ($class === 'CTrigger') { 1146 // The list of the triggers with changed priority. 1147 $changed_priority_triggerids = []; 1148 } 1149 1150 foreach ($triggers as $tnum => $trigger) { 1151 $db_trigger = $db_triggers[$tnum]; 1152 $upd_trigger = ['values' => [], 'where' => ['triggerid' => $trigger['triggerid']]]; 1153 1154 if (array_key_exists($tnum, $triggers_functions)) { 1155 $del_functions_triggerids[] = $trigger['triggerid']; 1156 1157 foreach ($triggers_functions[$tnum] as $trigger_function) { 1158 $trigger_function['triggerid'] = $trigger['triggerid']; 1159 $new_functions[] = $trigger_function; 1160 } 1161 1162 $upd_trigger['values']['expression'] = $trigger['expression']; 1163 $upd_trigger['values']['recovery_expression'] = $trigger['recovery_expression']; 1164 } 1165 1166 if ($trigger['description'] !== $db_trigger['description']) { 1167 $upd_trigger['values']['description'] = $trigger['description']; 1168 } 1169 if ($trigger['recovery_mode'] != $db_trigger['recovery_mode']) { 1170 $upd_trigger['values']['recovery_mode'] = $trigger['recovery_mode']; 1171 } 1172 if (array_key_exists('url', $trigger) && $trigger['url'] !== $db_trigger['url']) { 1173 $upd_trigger['values']['url'] = $trigger['url']; 1174 } 1175 if (array_key_exists('status', $trigger) && $trigger['status'] != $db_trigger['status']) { 1176 $upd_trigger['values']['status'] = $trigger['status']; 1177 } 1178 if (array_key_exists('priority', $trigger) && $trigger['priority'] != $db_trigger['priority']) { 1179 $upd_trigger['values']['priority'] = $trigger['priority']; 1180 1181 if ($class === 'CTrigger') { 1182 $changed_priority_triggerids[] = $trigger['triggerid']; 1183 } 1184 } 1185 if (array_key_exists('comments', $trigger) && $trigger['comments'] !== $db_trigger['comments']) { 1186 $upd_trigger['values']['comments'] = $trigger['comments']; 1187 } 1188 if (array_key_exists('type', $trigger) && $trigger['type'] != $db_trigger['type']) { 1189 $upd_trigger['values']['type'] = $trigger['type']; 1190 } 1191 if (array_key_exists('templateid', $trigger) && $trigger['templateid'] != $db_trigger['templateid']) { 1192 $upd_trigger['values']['templateid'] = $trigger['templateid']; 1193 } 1194 if ($trigger['correlation_mode'] != $db_trigger['correlation_mode']) { 1195 $upd_trigger['values']['correlation_mode'] = $trigger['correlation_mode']; 1196 } 1197 if ($trigger['correlation_tag'] !== $db_trigger['correlation_tag']) { 1198 $upd_trigger['values']['correlation_tag'] = $trigger['correlation_tag']; 1199 } 1200 if ($trigger['manual_close'] != $db_trigger['manual_close']) { 1201 $upd_trigger['values']['manual_close'] = $trigger['manual_close']; 1202 } 1203 1204 if ($upd_trigger['values']) { 1205 $upd_triggers[] = $upd_trigger; 1206 } 1207 1208 if (array_key_exists('tags', $trigger)) { 1209 // Add new trigger tags and replace changed ones. 1210 1211 CArrayHelper::sort($db_trigger['tags'], ['tag', 'value']); 1212 CArrayHelper::sort($trigger['tags'], ['tag', 'value']); 1213 1214 $tags_delete = $db_trigger['tags']; 1215 $tags_add = $trigger['tags']; 1216 1217 foreach ($tags_delete as $dt_key => $tag_delete) { 1218 foreach ($tags_add as $nt_key => $tag_add) { 1219 if ($tag_delete['tag'] === $tag_add['tag'] && $tag_delete['value'] === $tag_add['value']) { 1220 unset($tags_delete[$dt_key], $tags_add[$nt_key]); 1221 continue 2; 1222 } 1223 } 1224 } 1225 1226 foreach ($tags_delete as $tag_delete) { 1227 $del_triggertagids[] = $tag_delete['triggertagid']; 1228 } 1229 1230 foreach ($tags_add as $tag_add) { 1231 $tag_add['triggerid'] = $trigger['triggerid']; 1232 $new_tags[] = $tag_add; 1233 } 1234 } 1235 } 1236 1237 if ($upd_triggers) { 1238 DB::update('triggers', $upd_triggers); 1239 } 1240 if ($del_functions_triggerids) { 1241 DB::delete('functions', ['triggerid' => $del_functions_triggerids]); 1242 } 1243 if ($new_functions) { 1244 DB::insertBatch('functions', $new_functions, false); 1245 } 1246 if ($del_triggertagids) { 1247 DB::delete('trigger_tag', ['triggertagid' => $del_triggertagids]); 1248 } 1249 if ($new_tags) { 1250 DB::insert('trigger_tag', $new_tags); 1251 } 1252 1253 if ($class === 'CTrigger' && $changed_priority_triggerids 1254 && CTriggerManager::usedInItServices($changed_priority_triggerids)) { 1255 updateItServices(); 1256 } 1257 1258 if (!$inherited) { 1259 $this->addAuditBulk(AUDIT_ACTION_UPDATE, $resource, $save_triggers, zbx_toHash($db_triggers, 'triggerid')); 1260 } 1261 } 1262 1263 /** 1264 * Implodes expression and recovery_expression for each trigger. Also returns array of functions and 1265 * array of hostnames for each trigger. 1266 * 1267 * For example: {localhost:system.cpu.load.last(0)}>10 will be translated to {12}>10 and 1268 * created database representation. 1269 * 1270 * Note: All expressions must be already validated and exploded. 1271 * 1272 * @param array $triggers [IN] 1273 * @param string $triggers[<tnum>]['description'] [IN] 1274 * @param string $triggers[<tnum>]['expression'] [IN/OUT] 1275 * @param int $triggers[<tnum>]['recovery_mode'] [IN] 1276 * @param string $triggers[<tnum>]['recovery_expression'] [IN/OUT] 1277 * @param array|null $db_triggers [IN] 1278 * @param string $db_triggers[<tnum>]['triggerid'] [IN] 1279 * @param string $db_triggers[<tnum>]['expression'] [IN] 1280 * @param string $db_triggers[<tnum>]['recovery_expression'] [IN] 1281 * @param array $triggers_functions [OUT] array of the new functions which must be 1282 * inserted into DB 1283 * @param string $triggers_functions[<tnum>][]['functionid'] [OUT] 1284 * @param null $triggers_functions[<tnum>][]['triggerid'] [OUT] must be initialized before insertion into DB 1285 * @param string $triggers_functions[<tnum>][]['itemid'] [OUT] 1286 * @param string $triggers_functions[<tnum>][]['name'] [OUT] 1287 * @param string $triggers_functions[<tnum>][]['parameter'] [OUT] 1288 * @param bool $inherited [IN] (optional) If set to true, triggers will be 1289 * created for non-editable 1290 * hosts/templates. 1291 * 1292 * @throws APIException if error occurred 1293 */ 1294 private function implode_expressions(array &$triggers, array $db_triggers = null, array &$triggers_functions, 1295 $inherited = false) { 1296 $class = get_class($this); 1297 1298 switch ($class) { 1299 case 'CTrigger': 1300 $expressionData = new CTriggerExpression(['lldmacros' => false]); 1301 $error_wrong_host = _('Incorrect trigger expression. Host "%1$s" does not exist or you have no access to this host.'); 1302 $error_host_and_template = _('Incorrect trigger expression. Trigger expression elements should not belong to a template and a host simultaneously.'); 1303 $triggerFunctionValidator = new CFunctionValidator(['lldmacros' => false]); 1304 break; 1305 1306 case 'CTriggerPrototype': 1307 $expressionData = new CTriggerExpression(); 1308 $error_wrong_host = _('Incorrect trigger prototype expression. Host "%1$s" does not exist or you have no access to this host.'); 1309 $error_host_and_template = _('Incorrect trigger prototype expression. Trigger prototype expression elements should not belong to a template and a host simultaneously.'); 1310 $triggerFunctionValidator = new CFunctionValidator(); 1311 break; 1312 1313 default: 1314 self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.')); 1315 } 1316 1317 /* 1318 * [ 1319 * <host> => [ 1320 * 'hostid' => <hostid>, 1321 * 'host' => <host>, 1322 * 'status' => <status>, 1323 * 'keys' => [ 1324 * <key> => [ 1325 * 'itemid' => <itemid>, 1326 * 'key' => <key>, 1327 * 'value_type' => <value_type>, 1328 * 'flags' => <flags>, 1329 * 'lld_ruleid' => <itemid> (CTriggerProrotype only) 1330 * ] 1331 * ] 1332 * ] 1333 * ] 1334 */ 1335 $hosts_keys = []; 1336 $functions_num = 0; 1337 1338 foreach ($triggers as $tnum => $trigger) { 1339 $expressions_changed = ($db_triggers === null 1340 || ($trigger['expression'] !== $db_triggers[$tnum]['expression'] 1341 || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression'])); 1342 1343 if (!$expressions_changed) { 1344 continue; 1345 } 1346 1347 $expressionData->parse($trigger['expression']); 1348 $expressions = $expressionData->expressions; 1349 1350 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 1351 $expressionData->parse($trigger['recovery_expression']); 1352 $expressions = array_merge($expressions, $expressionData->expressions); 1353 } 1354 1355 foreach ($expressions as $exprPart) { 1356 if (!array_key_exists($exprPart['host'], $hosts_keys)) { 1357 $hosts_keys[$exprPart['host']] = [ 1358 'hostid' => null, 1359 'host' => $exprPart['host'], 1360 'status' => null, 1361 'keys' => [] 1362 ]; 1363 } 1364 1365 $hosts_keys[$exprPart['host']]['keys'][$exprPart['item']] = [ 1366 'itemid' => null, 1367 'key' => $exprPart['item'], 1368 'value_type' => null, 1369 'flags' => null 1370 ]; 1371 } 1372 } 1373 1374 if (!$hosts_keys) { 1375 return; 1376 } 1377 1378 $permission_check = $inherited 1379 ? ['nopermissions' => true] 1380 : ['editable' => true]; 1381 1382 $_db_hosts = API::Host()->get([ 1383 'output' => ['hostid', 'host', 'status'], 1384 'filter' => ['host' => array_keys($hosts_keys)] 1385 ] + $permission_check); 1386 1387 if (count($hosts_keys) != count($_db_hosts)) { 1388 $_db_templates = API::Template()->get([ 1389 'output' => ['templateid', 'host', 'status'], 1390 'filter' => ['host' => array_keys($hosts_keys)] 1391 ] + $permission_check); 1392 1393 foreach ($_db_templates as &$_db_template) { 1394 $_db_template['hostid'] = $_db_template['templateid']; 1395 unset($_db_template['templateid']); 1396 } 1397 unset($_db_template); 1398 1399 $_db_hosts = array_merge($_db_hosts, $_db_templates); 1400 } 1401 1402 foreach ($_db_hosts as $_db_host) { 1403 $host_keys = &$hosts_keys[$_db_host['host']]; 1404 1405 $host_keys['hostid'] = $_db_host['hostid']; 1406 $host_keys['status'] = $_db_host['status']; 1407 1408 if ($class === 'CTriggerPrototype') { 1409 $sql = 'SELECT i.itemid,i.key_,i.value_type,i.flags,id.parent_itemid'. 1410 ' FROM items i'. 1411 ' LEFT JOIN item_discovery id ON i.itemid=id.itemid'. 1412 ' WHERE i.hostid='.$host_keys['hostid']. 1413 ' AND '.dbConditionString('i.key_', array_keys($host_keys['keys'])). 1414 ' AND '.dbConditionInt('i.flags', 1415 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_PROTOTYPE, ZBX_FLAG_DISCOVERY_CREATED] 1416 ); 1417 } 1418 else { 1419 $sql = 'SELECT i.itemid,i.key_,i.value_type,i.flags'. 1420 ' FROM items i'. 1421 ' WHERE i.hostid='.$host_keys['hostid']. 1422 ' AND '.dbConditionString('i.key_', array_keys($host_keys['keys'])). 1423 ' AND '.dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]); 1424 } 1425 1426 $_db_items = DBselect($sql); 1427 1428 while ($_db_item = DBfetch($_db_items)) { 1429 $host_keys['keys'][$_db_item['key_']]['itemid'] = $_db_item['itemid']; 1430 $host_keys['keys'][$_db_item['key_']]['value_type'] = $_db_item['value_type']; 1431 $host_keys['keys'][$_db_item['key_']]['flags'] = $_db_item['flags']; 1432 1433 if ($class === 'CTriggerPrototype' && $_db_item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) { 1434 $host_keys['keys'][$_db_item['key_']]['lld_ruleid'] = $_db_item['parent_itemid']; 1435 } 1436 } 1437 1438 unset($host_keys); 1439 } 1440 1441 /* 1442 * The list of triggers with multiple templates. 1443 * 1444 * [ 1445 * [ 1446 * 'description' => <description>, 1447 * 'templateids' => [<templateid>, ...] 1448 * ], 1449 * ... 1450 * ] 1451 */ 1452 $mt_triggers = []; 1453 1454 if ($class === 'CTrigger') { 1455 /* 1456 * The list of triggers which are moved from one host or template to another. 1457 * 1458 * [ 1459 * <triggerid> => [ 1460 * 'description' => <description> 1461 * ], 1462 * ... 1463 * ] 1464 */ 1465 $moved_triggers = []; 1466 } 1467 1468 foreach ($triggers as $tnum => &$trigger) { 1469 $expressions_changed = $db_triggers === null 1470 || ($trigger['expression'] !== $db_triggers[$tnum]['expression'] 1471 || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression']); 1472 1473 if (!$expressions_changed) { 1474 continue; 1475 } 1476 1477 $expressionData->parse($trigger['expression']); 1478 $expressions1 = $expressionData->expressions; 1479 $expressions2 = []; 1480 1481 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 1482 $expressionData->parse($trigger['recovery_expression']); 1483 $expressions2 = $expressionData->expressions; 1484 } 1485 1486 $triggers_functions[$tnum] = []; 1487 if ($class === 'CTriggerPrototype') { 1488 $lld_ruleids = []; 1489 } 1490 1491 /* 1492 * 0x01 - with templates 1493 * 0x02 - with hosts 1494 */ 1495 $status_mask = 0x00; 1496 // The lists of hostids and hosts which are used in the current trigger. 1497 $hostids = []; 1498 $hosts = []; 1499 1500 // Common checks. 1501 foreach (array_merge($expressions1, $expressions2) as $exprPart) { 1502 $host_keys = $hosts_keys[$exprPart['host']]; 1503 $key = $host_keys['keys'][$exprPart['item']]; 1504 1505 if ($host_keys['hostid'] === null) { 1506 self::exception(ZBX_API_ERROR_PARAMETERS, _params($error_wrong_host, [$host_keys['host']])); 1507 } 1508 1509 if ($key['itemid'] === null) { 1510 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1511 'Incorrect item key "%1$s" provided for trigger expression on "%2$s".', $key['key'], 1512 $host_keys['host'] 1513 )); 1514 } 1515 1516 if (!$triggerFunctionValidator->validate([ 1517 'function' => $exprPart['function'], 1518 'functionName' => $exprPart['functionName'], 1519 'functionParamList' => $exprPart['functionParamList'], 1520 'valueType' => $key['value_type']])) { 1521 self::exception(ZBX_API_ERROR_PARAMETERS, $triggerFunctionValidator->getError()); 1522 } 1523 1524 if (!array_key_exists($exprPart['expression'], $triggers_functions[$tnum])) { 1525 $triggers_functions[$tnum][$exprPart['expression']] = [ 1526 'functionid' => null, 1527 'triggerid' => null, 1528 'itemid' => $key['itemid'], 1529 'name' => $exprPart['functionName'], 1530 'parameter' => $exprPart['functionParam'] 1531 ]; 1532 $functions_num++; 1533 } 1534 1535 if ($class === 'CTriggerPrototype' && $key['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) { 1536 $lld_ruleids[$key['lld_ruleid']] = true; 1537 } 1538 1539 $status_mask |= ($host_keys['status'] == HOST_STATUS_TEMPLATE ? 0x01 : 0x02); 1540 $hostids[$host_keys['hostid']] = true; 1541 $hosts[$exprPart['host']] = true; 1542 } 1543 1544 // When both templates and hosts are referenced in expressions. 1545 if ($status_mask == 0x03) { 1546 self::exception(ZBX_API_ERROR_PARAMETERS, $error_host_and_template); 1547 } 1548 1549 // Triggers with children cannot be moved from one template to another host or template. 1550 if ($class === 'CTrigger' && $db_triggers !== null && $expressions_changed) { 1551 $expressionData->parse($db_triggers[$tnum]['expression']); 1552 $old_hosts1 = $expressionData->getHosts(); 1553 $old_hosts2 = []; 1554 1555 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 1556 $expressionData->parse($db_triggers[$tnum]['recovery_expression']); 1557 $old_hosts2 = $expressionData->getHosts(); 1558 } 1559 1560 $is_moved = true; 1561 foreach (array_merge($old_hosts1, $old_hosts2) as $old_host) { 1562 if (array_key_exists($old_host, $hosts)) { 1563 $is_moved = false; 1564 break; 1565 } 1566 } 1567 1568 if ($is_moved) { 1569 $moved_triggers[$db_triggers[$tnum]['triggerid']] = ['description' => $trigger['description']]; 1570 } 1571 } 1572 1573 // The trigger with multiple templates. 1574 if ($status_mask == 0x01 && count($hostids) > 1) { 1575 $mt_triggers[] = [ 1576 'description' => $trigger['description'], 1577 'templateids' => array_keys($hostids) 1578 ]; 1579 } 1580 1581 if ($class === 'CTriggerPrototype') { 1582 $lld_ruleids = array_keys($lld_ruleids); 1583 1584 if (!$lld_ruleids) { 1585 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1586 'Trigger prototype "%1$s" must contain at least one item prototype.', $trigger['description'] 1587 )); 1588 } 1589 elseif (count($lld_ruleids) > 1) { 1590 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1591 'Trigger prototype "%1$s" contains item prototypes from multiple discovery rules.', 1592 $trigger['description'] 1593 )); 1594 } 1595 elseif ($db_triggers !== null 1596 && !idcmp($lld_ruleids[0], $db_triggers[$tnum]['discoveryRule']['itemid'])) { 1597 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot update trigger prototype "%1$s": %2$s.', 1598 $trigger['description'], _('trigger prototype cannot be moved to another template or host') 1599 )); 1600 } 1601 } 1602 } 1603 unset($trigger); 1604 1605 if ($mt_triggers) { 1606 $this->validateTriggersWithMultipleTemplates($mt_triggers); 1607 } 1608 1609 if ($class === 'CTrigger' && $moved_triggers) { 1610 $this->validateMovedTriggers($moved_triggers); 1611 } 1612 1613 $functionid = DB::reserveIds('functions', $functions_num); 1614 1615 $expression_max_length = DB::getFieldLength('triggers', 'expression'); 1616 $recovery_expression_max_length = DB::getFieldLength('triggers', 'recovery_expression'); 1617 1618 // Replace {host:item.func()} macros with {<functionid>}. 1619 foreach ($triggers as $tnum => &$trigger) { 1620 $expressions_changed = $db_triggers === null 1621 || ($trigger['expression'] !== $db_triggers[$tnum]['expression'] 1622 || $trigger['recovery_expression'] !== $db_triggers[$tnum]['recovery_expression']); 1623 1624 if (!$expressions_changed) { 1625 continue; 1626 } 1627 1628 foreach ($triggers_functions[$tnum] as &$trigger_function) { 1629 $trigger_function['functionid'] = $functionid; 1630 $functionid = bcadd($functionid, 1, 0); 1631 } 1632 unset($function); 1633 1634 $expressionData->parse($trigger['expression']); 1635 $exprPart = end($expressionData->expressions); 1636 do { 1637 $trigger['expression'] = substr_replace($trigger['expression'], 1638 '{'.$triggers_functions[$tnum][$exprPart['expression']]['functionid'].'}', 1639 $exprPart['pos'], strlen($exprPart['expression']) 1640 ); 1641 } 1642 while ($exprPart = prev($expressionData->expressions)); 1643 1644 if (mb_strlen($trigger['expression']) > $expression_max_length) { 1645 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1646 'Invalid parameter "%1$s": %2$s.', '/'.($tnum + 1).'/expression', _('value is too long') 1647 )); 1648 } 1649 1650 if ($trigger['recovery_mode'] == ZBX_RECOVERY_MODE_RECOVERY_EXPRESSION) { 1651 $expressionData->parse($trigger['recovery_expression']); 1652 $exprPart = end($expressionData->expressions); 1653 do { 1654 $trigger['recovery_expression'] = substr_replace($trigger['recovery_expression'], 1655 '{'.$triggers_functions[$tnum][$exprPart['expression']]['functionid'].'}', 1656 $exprPart['pos'], strlen($exprPart['expression']) 1657 ); 1658 } 1659 while ($exprPart = prev($expressionData->expressions)); 1660 1661 if (mb_strlen($trigger['recovery_expression']) > $recovery_expression_max_length) { 1662 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 1663 '/'.($tnum + 1).'/recovery_expression', _('value is too long') 1664 )); 1665 } 1666 } 1667 } 1668 unset($trigger); 1669 } 1670 1671 /** 1672 * Check if all templates trigger belongs to are linked to same hosts. 1673 * 1674 * @param array $mt_triggers 1675 * @param string $mt_triggers[]['description'] 1676 * @param array $mt_triggers[]['templateids'] 1677 * 1678 * @throws APIException 1679 */ 1680 protected function validateTriggersWithMultipleTemplates(array $mt_triggers) { 1681 switch (get_class($this)) { 1682 case 'CTrigger': 1683 $error_different_linkages = _('Trigger "%1$s" belongs to templates with different linkages.'); 1684 break; 1685 1686 case 'CTriggerPrototype': 1687 $error_different_linkages = _('Trigger prototype "%1$s" belongs to templates with different linkages.'); 1688 break; 1689 1690 default: 1691 self::exception(ZBX_API_ERROR_INTERNAL, _('Internal error.')); 1692 } 1693 1694 $templateids = []; 1695 1696 foreach ($mt_triggers as $mt_trigger) { 1697 foreach ($mt_trigger['templateids'] as $templateid) { 1698 $templateids[$templateid] = true; 1699 } 1700 } 1701 1702 $templates = API::Template()->get([ 1703 'output' => [], 1704 'selectHosts' => ['hostid'], 1705 'selectTemplates' => ['templateid'], 1706 'templateids' => array_keys($templateids), 1707 'nopermissions' => true, 1708 'preservekeys' => true 1709 ]); 1710 1711 foreach ($templates as &$template) { 1712 $template = array_merge( 1713 zbx_objectValues($template['hosts'], 'hostid'), 1714 zbx_objectValues($template['templates'], 'templateid') 1715 ); 1716 } 1717 unset($template); 1718 1719 foreach ($mt_triggers as $mt_trigger) { 1720 $compare_links = null; 1721 1722 foreach ($mt_trigger['templateids'] as $templateid) { 1723 if ($compare_links === null) { 1724 $compare_links = $templates[$templateid]; 1725 continue; 1726 } 1727 1728 $linked_to = $templates[$templateid]; 1729 1730 if (array_diff($compare_links, $linked_to) || array_diff($linked_to, $compare_links)) { 1731 self::exception(ZBX_API_ERROR_PARAMETERS, 1732 _params($error_different_linkages, [$mt_trigger['description']]) 1733 ); 1734 } 1735 } 1736 } 1737 } 1738 1739 /** 1740 * Check if moved triggers does not have children. 1741 * 1742 * @param array $moved_triggers 1743 * @param string $moved_triggers[<triggerid>]['description'] 1744 * 1745 * @throws APIException 1746 */ 1747 protected function validateMovedTriggers(array $moved_triggers) { 1748 $_db_triggers = DBselect( 1749 'SELECT t.templateid'. 1750 ' FROM triggers t'. 1751 ' WHERE '.dbConditionInt('t.templateid', array_keys($moved_triggers)), 1752 1 1753 ); 1754 1755 if ($_db_trigger = DBfetch($_db_triggers)) { 1756 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot update trigger "%1$s": %2$s.', 1757 $moved_triggers[$_db_trigger['templateid']]['description'], 1758 _('trigger with linkages cannot be moved to another template or host') 1759 )); 1760 } 1761 } 1762 1763 /** 1764 * Adds triggers and trigger prototypes from template to hosts. 1765 * 1766 * @param array $data 1767 */ 1768 public function syncTemplates(array $data) { 1769 $data['templateids'] = zbx_toArray($data['templateids']); 1770 $data['hostids'] = zbx_toArray($data['hostids']); 1771 1772 $triggers = $this->get([ 1773 'output' => [ 1774 'triggerid', 'description', 'expression', 'recovery_mode', 'recovery_expression', 'url', 'status', 1775 'priority', 'comments', 'type', 'correlation_mode', 'correlation_tag', 'manual_close' 1776 ], 1777 'selectTags' => ['tag', 'value'], 1778 'hostids' => $data['templateids'], 1779 'preservekeys' => true 1780 ]); 1781 1782 $triggers = CMacrosResolverHelper::resolveTriggerExpressions($triggers, 1783 ['sources' => ['expression', 'recovery_expression']] 1784 ); 1785 1786 $this->inherit($triggers, $data['hostids']); 1787 } 1788} 1789