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 triggers. 24 */ 25class CTrigger extends CTriggerGeneral { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 32 'adddependencies' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 33 'deletedependencies' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN] 34 ]; 35 36 protected $tableName = 'triggers'; 37 protected $tableAlias = 't'; 38 protected $sortColumns = ['triggerid', 'description', 'status', 'priority', 'lastchange', 'hostname']; 39 40 /** 41 * Get Triggers data. 42 * 43 * @param array $options 44 * @param array $options['itemids'] 45 * @param array $options['hostids'] 46 * @param array $options['groupids'] 47 * @param array $options['triggerids'] 48 * @param array $options['status'] 49 * @param bool $options['editable'] 50 * @param array $options['count'] 51 * @param array $options['pattern'] 52 * @param array $options['limit'] 53 * @param array $options['order'] 54 * 55 * @return array|int item data as array or false if error 56 */ 57 public function get(array $options = []) { 58 $result = []; 59 60 $sqlParts = [ 61 'select' => ['triggers' => 't.triggerid'], 62 'from' => ['t' => 'triggers t'], 63 'where' => [], 64 'group' => [], 65 'order' => [], 66 'limit' => null 67 ]; 68 69 $defOptions = [ 70 'groupids' => null, 71 'templateids' => null, 72 'hostids' => null, 73 'triggerids' => null, 74 'itemids' => null, 75 'functions' => null, 76 'inherited' => null, 77 'dependent' => null, 78 'templated' => null, 79 'monitored' => null, 80 'active' => null, 81 'maintenance' => null, 82 'withUnacknowledgedEvents' => null, 83 'withAcknowledgedEvents' => null, 84 'withLastEventUnacknowledged' => null, 85 'skipDependent' => null, 86 'nopermissions' => null, 87 'editable' => false, 88 // timing 89 'lastChangeSince' => null, 90 'lastChangeTill' => null, 91 // filter 92 'group' => null, 93 'host' => null, 94 'only_true' => null, 95 'min_severity' => null, 96 'evaltype' => TAG_EVAL_TYPE_AND_OR, 97 'tags' => null, 98 'filter' => null, 99 'search' => null, 100 'searchByAny' => null, 101 'startSearch' => false, 102 'excludeSearch' => false, 103 'searchWildcardsEnabled' => null, 104 // output 105 'expandDescription' => null, 106 'expandComment' => null, 107 'expandExpression' => null, 108 'output' => API_OUTPUT_EXTEND, 109 'selectGroups' => null, 110 'selectHosts' => null, 111 'selectItems' => null, 112 'selectFunctions' => null, 113 'selectDependencies' => null, 114 'selectDiscoveryRule' => null, 115 'selectLastEvent' => null, 116 'selectTags' => null, 117 'selectTriggerDiscovery' => null, 118 'countOutput' => false, 119 'groupCount' => false, 120 'preservekeys' => false, 121 'sortfield' => '', 122 'sortorder' => '', 123 'limit' => null, 124 'limitSelects' => null 125 ]; 126 $options = zbx_array_merge($defOptions, $options); 127 128 // editable + PERMISSION CHECK 129 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 130 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 131 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 132 133 $sqlParts['where'][] = 'NOT EXISTS ('. 134 'SELECT NULL'. 135 ' FROM functions f,items i,hosts_groups hgg'. 136 ' LEFT JOIN rights r'. 137 ' ON r.id=hgg.groupid'. 138 ' AND '.dbConditionInt('r.groupid', $userGroups). 139 ' WHERE t.triggerid=f.triggerid '. 140 ' AND f.itemid=i.itemid'. 141 ' AND i.hostid=hgg.hostid'. 142 ' GROUP BY i.hostid'. 143 ' HAVING MAX(permission)<'.zbx_dbstr($permission). 144 ' OR MIN(permission) IS NULL'. 145 ' OR MIN(permission)='.PERM_DENY. 146 ')'; 147 } 148 149 // groupids 150 if ($options['groupids'] !== null) { 151 zbx_value2array($options['groupids']); 152 153 sort($options['groupids']); 154 155 $sqlParts['from']['functions'] = 'functions f'; 156 $sqlParts['from']['items'] = 'items i'; 157 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 158 $sqlParts['where']['hgi'] = 'hg.hostid=i.hostid'; 159 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 160 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 161 $sqlParts['where']['groupid'] = dbConditionInt('hg.groupid', $options['groupids']); 162 163 if ($options['groupCount']) { 164 $sqlParts['group']['hg'] = 'hg.groupid'; 165 } 166 } 167 168 // templateids 169 if ($options['templateids'] !== null) { 170 zbx_value2array($options['templateids']); 171 172 if ($options['hostids'] !== null) { 173 zbx_value2array($options['hostids']); 174 $options['hostids'] = array_merge($options['hostids'], $options['templateids']); 175 } 176 else { 177 $options['hostids'] = $options['templateids']; 178 } 179 } 180 181 // hostids 182 if ($options['hostids'] !== null) { 183 zbx_value2array($options['hostids']); 184 185 $sqlParts['from']['functions'] = 'functions f'; 186 $sqlParts['from']['items'] = 'items i'; 187 $sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['hostids']); 188 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 189 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 190 191 if ($options['groupCount']) { 192 $sqlParts['group']['i'] = 'i.hostid'; 193 } 194 } 195 196 // triggerids 197 if ($options['triggerids'] !== null) { 198 zbx_value2array($options['triggerids']); 199 200 $sqlParts['where']['triggerid'] = dbConditionInt('t.triggerid', $options['triggerids']); 201 } 202 203 // itemids 204 if ($options['itemids'] !== null) { 205 zbx_value2array($options['itemids']); 206 207 $sqlParts['from']['functions'] = 'functions f'; 208 $sqlParts['where']['itemid'] = dbConditionInt('f.itemid', $options['itemids']); 209 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 210 211 if ($options['groupCount']) { 212 $sqlParts['group']['f'] = 'f.itemid'; 213 } 214 } 215 216 // functions 217 if ($options['functions'] !== null) { 218 zbx_value2array($options['functions']); 219 220 $sqlParts['from']['functions'] = 'functions f'; 221 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 222 $sqlParts['where'][] = dbConditionString('f.name', $options['functions']); 223 } 224 225 // monitored 226 if ($options['monitored'] !== null) { 227 $sqlParts['where']['monitored'] = 'NOT EXISTS ('. 228 'SELECT NULL'. 229 ' FROM functions f,items i,hosts h'. 230 ' WHERE t.triggerid=f.triggerid'. 231 ' AND f.itemid=i.itemid'. 232 ' AND i.hostid=h.hostid'. 233 ' AND ('. 234 'i.status<>'.ITEM_STATUS_ACTIVE. 235 ' OR h.status<>'.HOST_STATUS_MONITORED. 236 ')'. 237 ')'; 238 $sqlParts['where']['status'] = 't.status='.TRIGGER_STATUS_ENABLED; 239 } 240 241 // active 242 if ($options['active'] !== null) { 243 $sqlParts['where']['active'] = 'NOT EXISTS ('. 244 'SELECT NULL'. 245 ' FROM functions f,items i,hosts h'. 246 ' WHERE t.triggerid=f.triggerid'. 247 ' AND f.itemid=i.itemid'. 248 ' AND i.hostid=h.hostid'. 249 ' AND h.status<>'.HOST_STATUS_MONITORED. 250 ')'; 251 $sqlParts['where']['status'] = 't.status='.TRIGGER_STATUS_ENABLED; 252 } 253 254 // maintenance 255 if ($options['maintenance'] !== null) { 256 $sqlParts['where'][] = ($options['maintenance'] == 0 ? 'NOT ' : ''). 257 'EXISTS ('. 258 'SELECT NULL'. 259 ' FROM functions f,items i,hosts h'. 260 ' WHERE t.triggerid=f.triggerid'. 261 ' AND f.itemid=i.itemid'. 262 ' AND i.hostid=h.hostid'. 263 ' AND h.maintenance_status='.HOST_MAINTENANCE_STATUS_ON. 264 ')'; 265 $sqlParts['where'][] = 't.status='.TRIGGER_STATUS_ENABLED; 266 } 267 268 // lastChangeSince 269 if ($options['lastChangeSince'] !== null) { 270 $sqlParts['where']['lastchangesince'] = 't.lastchange>'.zbx_dbstr($options['lastChangeSince']); 271 } 272 273 // lastChangeTill 274 if ($options['lastChangeTill'] !== null) { 275 $sqlParts['where']['lastchangetill'] = 't.lastchange<'.zbx_dbstr($options['lastChangeTill']); 276 } 277 278 // withUnacknowledgedEvents 279 if ($options['withUnacknowledgedEvents'] !== null) { 280 $sqlParts['where']['unack'] = 'EXISTS ('. 281 'SELECT NULL'. 282 ' FROM events e'. 283 ' WHERE t.triggerid=e.objectid'. 284 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 285 ' AND e.object='.EVENT_OBJECT_TRIGGER. 286 ' AND e.value='.TRIGGER_VALUE_TRUE. 287 ' AND e.acknowledged='.EVENT_NOT_ACKNOWLEDGED. 288 ')'; 289 } 290 291 // withAcknowledgedEvents 292 if ($options['withAcknowledgedEvents'] !== null) { 293 $sqlParts['where']['ack'] = 'NOT EXISTS ('. 294 'SELECT NULL'. 295 ' FROM events e'. 296 ' WHERE e.objectid=t.triggerid'. 297 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 298 ' AND e.object='.EVENT_OBJECT_TRIGGER. 299 ' AND e.value='.TRIGGER_VALUE_TRUE. 300 ' AND e.acknowledged='.EVENT_NOT_ACKNOWLEDGED. 301 ')'; 302 } 303 304 // templated 305 if ($options['templated'] !== null) { 306 $sqlParts['from']['functions'] = 'functions f'; 307 $sqlParts['from']['items'] = 'items i'; 308 $sqlParts['from']['hosts'] = 'hosts h'; 309 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 310 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 311 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 312 313 if ($options['templated']) { 314 $sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE; 315 } 316 else { 317 $sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE; 318 } 319 } 320 321 // inherited 322 if ($options['inherited'] !== null) { 323 if ($options['inherited']) { 324 $sqlParts['where'][] = 't.templateid IS NOT NULL'; 325 } 326 else { 327 $sqlParts['where'][] = 't.templateid IS NULL'; 328 } 329 } 330 331 // dependent 332 if ($options['dependent'] !== null) { 333 if ($options['dependent']) { 334 $sqlParts['where'][] = 'EXISTS ('. 335 'SELECT NULL'. 336 ' FROM trigger_depends td'. 337 ' WHERE td.triggerid_down=t.triggerid'. 338 ')'; 339 } 340 else { 341 $sqlParts['where'][] = 'NOT EXISTS ('. 342 'SELECT NULL'. 343 ' FROM trigger_depends td'. 344 ' WHERE td.triggerid_down=t.triggerid'. 345 ')'; 346 } 347 } 348 349 // search 350 if (is_array($options['search'])) { 351 zbx_db_search('triggers t', $options, $sqlParts); 352 } 353 354 // filter 355 if ($options['filter'] === null) { 356 $options['filter'] = []; 357 } 358 359 if (is_array($options['filter'])) { 360 if (!array_key_exists('flags', $options['filter'])) { 361 $options['filter']['flags'] = [ 362 ZBX_FLAG_DISCOVERY_NORMAL, 363 ZBX_FLAG_DISCOVERY_CREATED 364 ]; 365 } 366 367 $this->dbFilter('triggers t', $options, $sqlParts); 368 369 if (array_key_exists('host', $options['filter']) && $options['filter']['host'] !== null) { 370 zbx_value2array($options['filter']['host']); 371 372 $sqlParts['from']['functions'] = 'functions f'; 373 $sqlParts['from']['items'] = 'items i'; 374 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 375 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 376 $sqlParts['from']['hosts'] = 'hosts h'; 377 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 378 $sqlParts['where']['host'] = dbConditionString('h.host', $options['filter']['host']); 379 } 380 381 if (array_key_exists('hostid', $options['filter']) && $options['filter']['hostid'] !== null) { 382 zbx_value2array($options['filter']['hostid']); 383 384 $sqlParts['from']['functions'] = 'functions f'; 385 $sqlParts['from']['items'] = 'items i'; 386 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 387 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 388 $sqlParts['where']['hostid'] = dbConditionInt('i.hostid', $options['filter']['hostid']); 389 } 390 } 391 392 // group 393 if ($options['group'] !== null) { 394 $sqlParts['from']['functions'] = 'functions f'; 395 $sqlParts['from']['items'] = 'items i'; 396 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 397 $sqlParts['from']['hstgrp'] = 'hstgrp g'; 398 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 399 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 400 $sqlParts['where']['hgi'] = 'hg.hostid=i.hostid'; 401 $sqlParts['where']['ghg'] = 'g.groupid = hg.groupid'; 402 $sqlParts['where']['group'] = ' g.name='.zbx_dbstr($options['group']); 403 } 404 405 // host 406 if ($options['host'] !== null) { 407 $sqlParts['from']['functions'] = 'functions f'; 408 $sqlParts['from']['items'] = 'items i'; 409 $sqlParts['from']['hosts'] = 'hosts h'; 410 $sqlParts['where']['ft'] = 'f.triggerid=t.triggerid'; 411 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 412 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 413 $sqlParts['where']['host'] = ' h.host='.zbx_dbstr($options['host']); 414 } 415 416 // only_true 417 if ($options['only_true'] !== null) { 418 $sqlParts['where']['ot'] = '((t.value='.TRIGGER_VALUE_TRUE.')'. 419 ' OR ((t.value='.TRIGGER_VALUE_FALSE.')'. 420 ' AND (t.lastchange>'. 421 (time() - timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::OK_PERIOD))). 422 '))'. 423 ')'; 424 } 425 426 // min_severity 427 if ($options['min_severity'] !== null) { 428 $sqlParts['where'][] = 't.priority>='.zbx_dbstr($options['min_severity']); 429 } 430 431 // tags 432 if ($options['tags'] !== null && $options['tags']) { 433 $sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 't', 434 'trigger_tag', 'triggerid' 435 ); 436 } 437 438 // limit 439 if (!zbx_ctype_digit($options['limit']) || !$options['limit']) { 440 $options['limit'] = null; 441 } 442 443 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 444 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 445 446 // return count or grouped counts via direct SQL count 447 if ($options['countOutput'] && !$this->requiresPostSqlFiltering($options)) { 448 $dbRes = DBselect(self::createSelectQueryFromParts($sqlParts), $options['limit']); 449 while ($trigger = DBfetch($dbRes)) { 450 if ($options['groupCount']) { 451 $result[] = $trigger; 452 } 453 else { 454 $result = $trigger['rowscount']; 455 } 456 } 457 return $result; 458 } 459 460 $result = zbx_toHash($this->customFetch(self::createSelectQueryFromParts($sqlParts), $options), 'triggerid'); 461 462 // return count for post SQL filtered result sets 463 if ($options['countOutput']) { 464 return (string) count($result); 465 } 466 467 if ($result) { 468 $result = $this->addRelatedObjects($options, $result); 469 } 470 471 // expandDescription 472 if ($options['expandDescription'] !== null && $result && array_key_exists('description', reset($result))) { 473 $result = CMacrosResolverHelper::resolveTriggerNames($result); 474 } 475 476 // expandComment 477 if ($options['expandComment'] !== null && $result && array_key_exists('comments', reset($result))) { 478 $result = CMacrosResolverHelper::resolveTriggerDescriptions($result, ['sources' => ['comments']]); 479 } 480 481 // expand expressions 482 if ($options['expandExpression'] !== null && $result) { 483 $sources = []; 484 if (array_key_exists('expression', reset($result))) { 485 $sources[] = 'expression'; 486 } 487 if (array_key_exists('recovery_expression', reset($result))) { 488 $sources[] = 'recovery_expression'; 489 } 490 491 if ($sources) { 492 $result = CMacrosResolverHelper::resolveTriggerExpressions($result, 493 ['resolve_usermacros' => true, 'resolve_macros' => true, 'sources' => $sources] 494 ); 495 } 496 } 497 498 // removing keys (hash -> array) 499 if (!$options['preservekeys']) { 500 $result = zbx_cleanHashes($result); 501 } 502 503 $result = $this->unsetExtraFields($result, ['state', 'expression'], $options['output']); 504 505 // Triggers share table with trigger prototypes. Therefore remove trigger unrelated fields. 506 if ($this->outputIsRequested('discover', $options['output'])) { 507 foreach ($result as &$row) { 508 unset($row['discover']); 509 } 510 unset($row); 511 } 512 513 return $result; 514 } 515 516 /** 517 * Add triggers. 518 * 519 * Trigger params: expression, description, type, priority, status, comments, url, templateid 520 * 521 * @param array $triggers 522 * 523 * @return array 524 */ 525 public function create(array $triggers) { 526 $this->validateCreate($triggers); 527 $this->createReal($triggers); 528 $this->inherit($triggers); 529 530 // Clear all dependencies on inherited triggers. 531 $this->deleteDependencies($triggers); 532 533 // Add new dependencies. 534 foreach ($triggers as $trigger) { 535 if (!array_key_exists('dependencies', $trigger) || !$trigger['dependencies']) { 536 continue; 537 } 538 539 $new_dependencies = []; 540 foreach ($trigger['dependencies'] as $dependency) { 541 $new_dependencies[] = [ 542 'triggerid' => $trigger['triggerid'], 543 'dependsOnTriggerid' => $dependency['triggerid'] 544 ]; 545 } 546 $this->addDependencies($new_dependencies); 547 } 548 549 return ['triggerids' => zbx_objectValues($triggers, 'triggerid')]; 550 } 551 552 /** 553 * Update triggers. 554 * 555 * If a trigger expression is passed in any of the triggers, it must be in it's exploded form. 556 * 557 * @param array $triggers 558 * 559 * @return array 560 */ 561 public function update(array $triggers) { 562 $this->validateUpdate($triggers, $db_triggers); 563 564 $validate_dependencies = []; 565 foreach ($triggers as $tnum => $trigger) { 566 $db_trigger = $db_triggers[$tnum]; 567 568 $expressions_changed = ($trigger['expression'] !== $db_trigger['expression'] 569 || $trigger['recovery_expression'] !== $db_trigger['recovery_expression']); 570 571 if ($expressions_changed && $db_trigger['dependencies'] && !array_key_exists('dependencies', $trigger)) { 572 $validate_dependencies[] = [ 573 'triggerid' => $trigger['triggerid'], 574 'dependencies' => zbx_objectValues($db_trigger['dependencies'], 'triggerid') 575 ]; 576 } 577 } 578 579 if ($validate_dependencies) { 580 $this->checkDependencies($validate_dependencies); 581 $this->checkDependencyParents($validate_dependencies); 582 } 583 584 $this->updateReal($triggers, $db_triggers); 585 $this->inherit($triggers); 586 587 foreach ($triggers as $trigger) { 588 // Replace dependencies. 589 if (array_key_exists('dependencies', $trigger)) { 590 $this->deleteDependencies($trigger); 591 592 if ($trigger['dependencies']) { 593 $new_dependencies = []; 594 foreach ($trigger['dependencies'] as $dependency) { 595 $new_dependencies[] = [ 596 'triggerid' => $trigger['triggerid'], 597 'dependsOnTriggerid' => $dependency['triggerid'] 598 ]; 599 } 600 $this->addDependencies($new_dependencies); 601 } 602 } 603 } 604 605 return ['triggerids' => zbx_objectValues($triggers, 'triggerid')]; 606 } 607 608 /** 609 * Delete triggers. 610 * 611 * @param array $triggerids 612 * 613 * @return array 614 */ 615 public function delete(array $triggerids) { 616 $this->validateDelete($triggerids, $db_triggers); 617 618 CTriggerManager::delete($triggerids); 619 620 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_TRIGGER, $db_triggers); 621 622 return ['triggerids' => $triggerids]; 623 } 624 625 /** 626 * Validates the input parameters for the delete() method. 627 * 628 * @param array $triggerids [IN/OUT] 629 * @param array $db_triggers [OUT] 630 * 631 * @throws APIException if the input is invalid. 632 */ 633 protected function validateDelete(array &$triggerids, array &$db_triggers = null) { 634 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 635 if (!CApiInputValidator::validate($api_input_rules, $triggerids, '/', $error)) { 636 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 637 } 638 639 $db_triggers = $this->get([ 640 'output' => ['triggerid', 'description', 'expression', 'templateid'], 641 'triggerids' => $triggerids, 642 'editable' => true, 643 'preservekeys' => true 644 ]); 645 646 foreach ($triggerids as $triggerid) { 647 if (!array_key_exists($triggerid, $db_triggers)) { 648 self::exception(ZBX_API_ERROR_PERMISSIONS, 649 _('No permissions to referred object or it does not exist!') 650 ); 651 } 652 653 $db_trigger = $db_triggers[$triggerid]; 654 655 if ($db_trigger['templateid'] != 0) { 656 self::exception(ZBX_API_ERROR_PARAMETERS, 657 _s('Cannot delete templated trigger "%1$s:%2$s".', $db_trigger['description'], 658 CMacrosResolverHelper::resolveTriggerExpression($db_trigger['expression']) 659 ) 660 ); 661 } 662 } 663 } 664 665 /** 666 * Validates the input for the addDependencies() method. 667 * 668 * @param array $triggers_data 669 * @param bool $inherited 670 * 671 * @throws APIException if the given dependencies are invalid. 672 */ 673 protected function validateAddDependencies(array &$triggers_data, $inherited = false) { 674 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['triggerid', 'dependsOnTriggerid']], 'fields' => [ 675 'triggerid' => ['type' => API_ID, 'flags' => API_REQUIRED], 676 'dependsOnTriggerid' => ['type' => API_ID, 'flags' => API_REQUIRED] 677 ]]; 678 if (!CApiInputValidator::validate($api_input_rules, $triggers_data, '/', $error)) { 679 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 680 } 681 682 $triggerids = zbx_objectValues($triggers_data, 'triggerid'); 683 $triggerids = array_keys(array_flip($triggerids)); 684 685 $permission_check = $inherited 686 ? ['nopermissions' => true] 687 : ['editable' => true]; 688 689 $triggers = $this->get([ 690 'output' => ['triggerid', 'description', 'flags'], 691 'triggerids' => $triggerids, 692 'preservekeys' => true 693 ] + $permission_check); 694 695 if (count($triggerids) != count($triggers)) { 696 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 697 } 698 699 foreach ($triggers as $trigger) { 700 if ($trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { 701 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update "%2$s" for a discovered trigger "%1$s".', 702 $trigger['description'], 'dependencies' 703 )); 704 } 705 } 706 707 $dep_triggerids = []; 708 $triggers = []; 709 foreach ($triggers_data as $dep) { 710 $triggerid = $dep['triggerid']; 711 712 if (!array_key_exists($dep['triggerid'], $triggers)) { 713 $triggers[$triggerid] = [ 714 'triggerid' => $triggerid, 715 'dependencies' => [] 716 ]; 717 } 718 $triggers[$triggerid]['dependencies'][] = $dep['dependsOnTriggerid']; 719 $dep_triggerids[$dep['dependsOnTriggerid']] = $dep['dependsOnTriggerid']; 720 } 721 722 if (!$inherited) { 723 $count = $this->get([ 724 'countOutput' => true, 725 'triggerids' => $dep_triggerids 726 ]); 727 728 if ($count != count($dep_triggerids)) { 729 self::exception(ZBX_API_ERROR_PERMISSIONS, 730 _('No permissions to referred object or it does not exist!') 731 ); 732 } 733 } 734 735 $this->checkDependencies($triggers); 736 $this->checkDependencyParents($triggers); 737 $this->checkDependencyDuplicates($triggers); 738 } 739 740 /** 741 * Add the given dependencies and inherit them on all child triggers. 742 * 743 * @param array $triggers_data An array of trigger dependency pairs, each pair in the form of 744 * ['triggerid' => 1, 'dependsOnTriggerid' => 2]. 745 * @param bool $inherited Determines either to check permissions for added dependencies. Permissions are not 746 * validated for inherited triggers. 747 * 748 * @return array 749 */ 750 public function addDependencies(array $triggers_data, $inherited = false) { 751 $this->validateAddDependencies($triggers_data, $inherited); 752 753 foreach ($triggers_data as $dep) { 754 $triggerId = $dep['triggerid']; 755 $depTriggerId = $dep['dependsOnTriggerid']; 756 757 DB::insert('trigger_depends', [[ 758 'triggerid_down' => $triggerId, 759 'triggerid_up' => $depTriggerId 760 ]]); 761 762 // propagate the dependencies to the child triggers 763 $childTriggers = API::getApiService()->select($this->tableName(), [ 764 'output' => ['triggerid'], 765 'filter' => [ 766 'templateid' => $triggerId 767 ] 768 ]); 769 if ($childTriggers) { 770 foreach ($childTriggers as $childTrigger) { 771 $childHostsQuery = get_hosts_by_triggerid($childTrigger['triggerid']); 772 while ($childHost = DBfetch($childHostsQuery)) { 773 $newDep = [$childTrigger['triggerid'] => $depTriggerId]; 774 $newDep = replace_template_dependencies($newDep, $childHost['hostid']); 775 776 $this->addDependencies([[ 777 'triggerid' => $childTrigger['triggerid'], 778 'dependsOnTriggerid' => $newDep[$childTrigger['triggerid']] 779 ]], true); 780 } 781 } 782 } 783 } 784 785 return ['triggerids' => array_unique(zbx_objectValues($triggers_data, 'triggerid'))]; 786 } 787 788 /** 789 * Validates the input for the deleteDependencies() method. 790 * 791 * @param array $triggers 792 * @param bool $inherited 793 * 794 * @throws APIException if the given input is invalid 795 */ 796 protected function validateDeleteDependencies(array $triggers, $inherited) { 797 if (!$triggers) { 798 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 799 } 800 801 foreach ($triggers as $trigger) { 802 if (!check_db_fields(['triggerid' => null], $trigger)) { 803 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 804 } 805 } 806 807 $triggerids = zbx_objectValues($triggers, 'triggerid'); 808 $triggerids = array_keys(array_flip($triggerids)); 809 810 $permission_check = $inherited 811 ? ['nopermissions' => true] 812 : ['editable' => true]; 813 814 $triggers = $this->get([ 815 'output' => ['triggerid', 'description', 'flags'], 816 'triggerids' => $triggerids, 817 'preservekeys' => true 818 ] + $permission_check); 819 820 foreach ($triggerids as $triggerid) { 821 if (!array_key_exists($triggerid, $triggers)) { 822 self::exception(ZBX_API_ERROR_PERMISSIONS, 823 _('No permissions to referred object or it does not exist!') 824 ); 825 } 826 } 827 828 foreach ($triggers as $trigger) { 829 if ($trigger['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { 830 self::exception(ZBX_API_ERROR_PERMISSIONS, _s('Cannot update "%2$s" for a discovered trigger "%1$s".', 831 $trigger['description'], 'dependencies' 832 )); 833 } 834 } 835 } 836 837 /** 838 * Deletes all trigger dependencies from the given triggers and their children. 839 * 840 * @param array $triggers an array of triggers with the 'triggerid' field defined 841 * @param bool $inherited Determines either to check permissions for deleted dependencies. Permissions are not 842 * validated for inherited triggers. 843 * 844 * @return array 845 */ 846 public function deleteDependencies(array $triggers, $inherited = false) { 847 $triggers = zbx_toArray($triggers); 848 849 $this->validateDeleteDependencies($triggers, $inherited); 850 851 $triggerids = zbx_objectValues($triggers, 'triggerid'); 852 853 try { 854 // delete the dependencies from the child triggers 855 $childTriggers = DB::select($this->tableName(), [ 856 'output' => ['triggerid'], 857 'filter' => [ 858 'templateid' => $triggerids 859 ] 860 ]); 861 if ($childTriggers) { 862 $this->deleteDependencies($childTriggers, true); 863 } 864 865 DB::delete('trigger_depends', [ 866 'triggerid_down' => $triggerids 867 ]); 868 } 869 catch (APIException $e) { 870 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot delete dependency')); 871 } 872 873 return ['triggerids' => $triggerids]; 874 } 875 876 /** 877 * Synchronizes the templated trigger dependencies on the given hosts inherited from the given 878 * templates. 879 * Update dependencies, do it after all triggers that can be dependent were created/updated on 880 * all child hosts/templates. Starting from highest level template triggers select triggers from 881 * one level lower, then for each lower trigger look if it's parent has dependencies, if so 882 * find this dependency trigger child on dependent trigger host and add new dependency. 883 * 884 * @param array $data 885 */ 886 public function syncTemplateDependencies(array $data) { 887 $templateIds = zbx_toArray($data['templateids']); 888 $hostIds = zbx_toArray($data['hostids']); 889 890 $parentTriggers = $this->get([ 891 'output' => ['triggerid'], 892 'hostids' => $templateIds, 893 'preservekeys' => true, 894 'selectDependencies' => ['triggerid'] 895 ]); 896 897 if ($parentTriggers) { 898 $childTriggers = $this->get([ 899 'output' => ['triggerid', 'templateid'], 900 'hostids' => ($hostIds) ? $hostIds : null, 901 'filter' => ['templateid' => array_keys($parentTriggers)], 902 'nopermissions' => true, 903 'preservekeys' => true, 904 'selectHosts' => ['hostid'] 905 ]); 906 907 if ($childTriggers) { 908 $newDependencies = []; 909 foreach ($childTriggers as $childTrigger) { 910 $parentDependencies = $parentTriggers[$childTrigger['templateid']]['dependencies']; 911 if ($parentDependencies) { 912 $dependencies = []; 913 foreach ($parentDependencies as $depTrigger) { 914 $dependencies[] = $depTrigger['triggerid']; 915 } 916 $host = reset($childTrigger['hosts']); 917 $dependencies = replace_template_dependencies($dependencies, $host['hostid']); 918 foreach ($dependencies as $depTriggerId) { 919 $newDependencies[] = [ 920 'triggerid' => $childTrigger['triggerid'], 921 'dependsOnTriggerid' => $depTriggerId 922 ]; 923 } 924 } 925 } 926 $this->deleteDependencies($childTriggers); 927 928 if ($newDependencies) { 929 $this->addDependencies($newDependencies); 930 } 931 } 932 } 933 } 934 935 /** 936 * Validates the dependencies of the given triggers. 937 * 938 * @param array $triggers list of triggers and corresponding dependencies 939 * @param int $triggers[]['triggerid'] trigger id 940 * @param array $triggers[]['dependencies'] list of trigger ids on which depends given trigger 941 * 942 * @trows APIException if any of the dependencies is invalid 943 */ 944 protected function checkDependencies(array $triggers) { 945 foreach ($triggers as $trigger) { 946 if (empty($trigger['dependencies'])) { 947 continue; 948 } 949 950 // trigger templates 951 $triggerTemplates = API::Template()->get([ 952 'output' => ['status', 'hostid'], 953 'triggerids' => $trigger['triggerid'], 954 'nopermissions' => true 955 ]); 956 957 // forbid dependencies from hosts to templates 958 if (!$triggerTemplates) { 959 $triggerDependencyTemplates = API::Template()->get([ 960 'output' => ['templateid'], 961 'triggerids' => $trigger['dependencies'], 962 'nopermissions' => true, 963 'limit' => 1 964 ]); 965 if ($triggerDependencyTemplates) { 966 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot add dependency from a host to a template.')); 967 } 968 } 969 970 // the trigger can't depend on itself 971 if (in_array($trigger['triggerid'], $trigger['dependencies'])) { 972 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot create dependency on trigger itself.')); 973 } 974 975 // check circular dependency 976 $downTriggerIds = [$trigger['triggerid']]; 977 do { 978 // triggerid_down depends on triggerid_up 979 $res = DBselect( 980 'SELECT td.triggerid_up'. 981 ' FROM trigger_depends td'. 982 ' WHERE '.dbConditionInt('td.triggerid_down', $downTriggerIds) 983 ); 984 985 // combine db dependencies with those to be added 986 $upTriggersIds = []; 987 while ($row = DBfetch($res)) { 988 $upTriggersIds[] = $row['triggerid_up']; 989 } 990 foreach ($downTriggerIds as $id) { 991 if (isset($triggers[$id]) && isset($triggers[$id]['dependencies'])) { 992 $upTriggersIds = array_merge($upTriggersIds, $triggers[$id]['dependencies']); 993 } 994 } 995 996 // if found trigger id is in dependent triggerids, there is a dependency loop 997 $downTriggerIds = []; 998 foreach ($upTriggersIds as $id) { 999 if (bccomp($id, $trigger['triggerid']) == 0) { 1000 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot create circular dependencies.')); 1001 } 1002 $downTriggerIds[] = $id; 1003 } 1004 } while (!empty($downTriggerIds)); 1005 1006 // fetch all templates that are used in dependencies 1007 $triggerDependencyTemplates = API::Template()->get([ 1008 'output' => ['templateid'], 1009 'triggerids' => $trigger['dependencies'], 1010 'nopermissions' => true 1011 ]); 1012 $depTemplateIds = zbx_toHash(zbx_objectValues($triggerDependencyTemplates, 'templateid')); 1013 1014 // run the check only if a templated trigger has dependencies on other templates 1015 $triggerTemplateIds = zbx_toHash(zbx_objectValues($triggerTemplates, 'templateid')); 1016 $tdiff = array_diff($depTemplateIds, $triggerTemplateIds); 1017 if (!empty($triggerTemplateIds) && !empty($depTemplateIds) && !empty($tdiff)) { 1018 $affectedTemplateIds = zbx_array_merge($triggerTemplateIds, $depTemplateIds); 1019 1020 // create a list of all hosts, that are children of the affected templates 1021 $dbLowlvltpl = DBselect( 1022 'SELECT DISTINCT ht.templateid,ht.hostid,h.host'. 1023 ' FROM hosts_templates ht,hosts h'. 1024 ' WHERE h.hostid=ht.hostid'. 1025 ' AND '.dbConditionInt('ht.templateid', $affectedTemplateIds) 1026 ); 1027 $map = []; 1028 while ($lowlvltpl = DBfetch($dbLowlvltpl)) { 1029 if (!isset($map[$lowlvltpl['hostid']])) { 1030 $map[$lowlvltpl['hostid']] = []; 1031 } 1032 $map[$lowlvltpl['hostid']][$lowlvltpl['templateid']] = $lowlvltpl['host']; 1033 } 1034 1035 // check that if some host is linked to the template, that the trigger belongs to, 1036 // the host must also be linked to all of the templates, that trigger dependencies point to 1037 foreach ($map as $templates) { 1038 foreach ($triggerTemplateIds as $triggerTemplateId) { 1039 // is the host linked to one of the trigger templates? 1040 if (isset($templates[$triggerTemplateId])) { 1041 // then make sure all of the dependency templates are also linked 1042 foreach ($depTemplateIds as $depTemplateId) { 1043 if (!isset($templates[$depTemplateId])) { 1044 self::exception(ZBX_API_ERROR_PARAMETERS, 1045 _s('Not all templates are linked to "%1$s".', reset($templates)) 1046 ); 1047 } 1048 } 1049 break; 1050 } 1051 } 1052 } 1053 } 1054 } 1055 } 1056 1057 /** 1058 * Check that none of the triggers have dependencies on their children. Checks only one level of inheritance, but 1059 * since it is called on each inheritance step, also works for multiple inheritance levels. 1060 * 1061 * @throws APIException if at least one trigger is dependent on its child 1062 * 1063 * @param array $triggers 1064 */ 1065 protected function checkDependencyParents(array $triggers) { 1066 // fetch all templated dependency trigger parents 1067 $depTriggerIds = []; 1068 foreach ($triggers as $trigger) { 1069 foreach ($trigger['dependencies'] as $depTriggerId) { 1070 $depTriggerIds[$depTriggerId] = $depTriggerId; 1071 } 1072 } 1073 $parentDepTriggers = DBfetchArray(DBSelect( 1074 'SELECT templateid,triggerid'. 1075 ' FROM triggers'. 1076 ' WHERE templateid>0'. 1077 ' AND '.dbConditionInt('triggerid', $depTriggerIds) 1078 )); 1079 if ($parentDepTriggers) { 1080 $parentDepTriggers = zbx_toHash($parentDepTriggers, 'triggerid'); 1081 foreach ($triggers as $trigger) { 1082 foreach ($trigger['dependencies'] as $depTriggerId) { 1083 // check if the current trigger is the parent of the dependency trigger 1084 if (isset($parentDepTriggers[$depTriggerId]) 1085 && $parentDepTriggers[$depTriggerId]['templateid'] == $trigger['triggerid']) { 1086 1087 self::exception(ZBX_API_ERROR_PARAMETERS, 1088 _s('Trigger cannot be dependent on a trigger that is inherited from it.') 1089 ); 1090 } 1091 } 1092 } 1093 } 1094 } 1095 1096 /** 1097 * Checks if the given dependencies contain duplicates. 1098 * 1099 * @throws APIException if the given dependencies contain duplicates 1100 * 1101 * @param array $triggers 1102 */ 1103 protected function checkDependencyDuplicates(array $triggers) { 1104 // check duplicates in array 1105 $uniqueTriggers = []; 1106 $duplicateTriggerId = null; 1107 foreach ($triggers as $trigger) { 1108 foreach ($trigger['dependencies'] as $dep) { 1109 if (isset($uniqueTriggers[$trigger['triggerid']][$dep])) { 1110 $duplicateTriggerId = $trigger['triggerid']; 1111 break 2; 1112 } 1113 else { 1114 $uniqueTriggers[$trigger['triggerid']][$dep] = 1; 1115 } 1116 } 1117 } 1118 1119 if ($duplicateTriggerId === null) { 1120 // check if dependency already exists in DB 1121 foreach ($triggers as $trigger) { 1122 $dbUpTriggers = DBselect( 1123 'SELECT td.triggerid_up'. 1124 ' FROM trigger_depends td'. 1125 ' WHERE '.dbConditionInt('td.triggerid_up', $trigger['dependencies']). 1126 ' AND td.triggerid_down='.zbx_dbstr($trigger['triggerid']) 1127 , 1); 1128 if (DBfetch($dbUpTriggers)) { 1129 $duplicateTriggerId = $trigger['triggerid']; 1130 break; 1131 } 1132 } 1133 } 1134 1135 if ($duplicateTriggerId) { 1136 $dplTrigger = DBfetch(DBselect( 1137 'SELECT t.description'. 1138 ' FROM triggers t'. 1139 ' WHERE t.triggerid='.zbx_dbstr($duplicateTriggerId) 1140 )); 1141 self::exception(ZBX_API_ERROR_PARAMETERS, 1142 _s('Duplicate dependencies in trigger "%1$s".', $dplTrigger['description']) 1143 ); 1144 } 1145 } 1146 1147 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 1148 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 1149 1150 if (!$options['countOutput'] && $options['expandDescription'] !== null) { 1151 $sqlParts = $this->addQuerySelect($this->fieldId('expression'), $sqlParts); 1152 } 1153 1154 return $sqlParts; 1155 } 1156 1157 protected function addRelatedObjects(array $options, array $result) { 1158 $result = parent::addRelatedObjects($options, $result); 1159 1160 if (!$result) { 1161 return $result; 1162 } 1163 1164 $triggerids = array_keys($result); 1165 1166 // adding trigger dependencies 1167 if ($options['selectDependencies'] !== null && $options['selectDependencies'] != API_OUTPUT_COUNT) { 1168 $dependencies = []; 1169 $relationMap = new CRelationMap(); 1170 $res = DBselect( 1171 'SELECT td.triggerid_up,td.triggerid_down'. 1172 ' FROM trigger_depends td'. 1173 ' WHERE '.dbConditionInt('td.triggerid_down', $triggerids) 1174 ); 1175 while ($relation = DBfetch($res)) { 1176 $relationMap->addRelation($relation['triggerid_down'], $relation['triggerid_up']); 1177 } 1178 1179 $related_ids = $relationMap->getRelatedIds(); 1180 1181 if ($related_ids) { 1182 $dependencies = $this->get([ 1183 'output' => $options['selectDependencies'], 1184 'triggerids' => $related_ids, 1185 'preservekeys' => true 1186 ]); 1187 } 1188 1189 $result = $relationMap->mapMany($result, $dependencies, 'dependencies'); 1190 } 1191 1192 // adding items 1193 if ($options['selectItems'] !== null && $options['selectItems'] != API_OUTPUT_COUNT) { 1194 $relationMap = $this->createRelationMap($result, 'triggerid', 'itemid', 'functions'); 1195 $items = API::Item()->get([ 1196 'output' => $options['selectItems'], 1197 'itemids' => $relationMap->getRelatedIds(), 1198 'webitems' => true, 1199 'nopermissions' => true, 1200 'preservekeys' => true 1201 ]); 1202 $result = $relationMap->mapMany($result, $items, 'items'); 1203 } 1204 1205 // adding discoveryrule 1206 if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) { 1207 $discoveryRules = []; 1208 $relationMap = new CRelationMap(); 1209 $dbRules = DBselect( 1210 'SELECT id.parent_itemid,td.triggerid'. 1211 ' FROM trigger_discovery td,item_discovery id,functions f'. 1212 ' WHERE '.dbConditionInt('td.triggerid', $triggerids). 1213 ' AND td.parent_triggerid=f.triggerid'. 1214 ' AND f.itemid=id.itemid' 1215 ); 1216 while ($rule = DBfetch($dbRules)) { 1217 $relationMap->addRelation($rule['triggerid'], $rule['parent_itemid']); 1218 } 1219 1220 $related_ids = $relationMap->getRelatedIds(); 1221 1222 if ($related_ids) { 1223 $discoveryRules = API::DiscoveryRule()->get([ 1224 'output' => $options['selectDiscoveryRule'], 1225 'itemids' => $related_ids, 1226 'nopermissions' => true, 1227 'preservekeys' => true 1228 ]); 1229 } 1230 1231 $result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule'); 1232 } 1233 1234 // adding last event 1235 if ($options['selectLastEvent'] !== null) { 1236 foreach ($result as $triggerId => $trigger) { 1237 $result[$triggerId]['lastEvent'] = []; 1238 } 1239 1240 if (is_array($options['selectLastEvent'])) { 1241 $pkFieldId = $this->pk('events'); 1242 $outputFields = [ 1243 'objectid' => $this->fieldId('objectid', 'e'), 1244 'ns' => $this->fieldId('ns', 'e'), 1245 $pkFieldId => $this->fieldId($pkFieldId, 'e') 1246 ]; 1247 1248 foreach ($options['selectLastEvent'] as $field) { 1249 if ($this->hasField($field, 'events')) { 1250 $outputFields[$field] = $this->fieldId($field, 'e'); 1251 } 1252 } 1253 1254 $outputFields = implode(',', $outputFields); 1255 } 1256 else { 1257 $outputFields = 'e.*'; 1258 } 1259 1260 // Due to performance issues, avoid using 'ORDER BY' for outer SELECT. 1261 $dbEvents = DBselect( 1262 'SELECT '.$outputFields. 1263 ' FROM events e'. 1264 ' JOIN ('. 1265 'SELECT e2.source,e2.object,e2.objectid,MAX(clock) AS clock'. 1266 ' FROM events e2'. 1267 ' WHERE e2.source='.EVENT_SOURCE_TRIGGERS. 1268 ' AND e2.object='.EVENT_OBJECT_TRIGGER. 1269 ' AND '.dbConditionInt('e2.objectid', $triggerids). 1270 ' GROUP BY e2.source,e2.object,e2.objectid'. 1271 ') e3 ON e3.source=e.source'. 1272 ' AND e3.object=e.object'. 1273 ' AND e3.objectid=e.objectid'. 1274 ' AND e3.clock=e.clock' 1275 ); 1276 1277 // in case there are multiple records with same 'clock' for one trigger, we'll get different 'ns' 1278 $lastEvents = []; 1279 1280 while ($dbEvent = DBfetch($dbEvents)) { 1281 $triggerId = $dbEvent['objectid']; 1282 $ns = $dbEvent['ns']; 1283 1284 // unset fields, that were not requested 1285 if (is_array($options['selectLastEvent'])) { 1286 if (!in_array('objectid', $options['selectLastEvent'])) { 1287 unset($dbEvent['objectid']); 1288 } 1289 if (!in_array('ns', $options['selectLastEvent'])) { 1290 unset($dbEvent['ns']); 1291 } 1292 } 1293 1294 $lastEvents[$triggerId][$ns] = $dbEvent; 1295 } 1296 1297 foreach ($lastEvents as $triggerId => $events) { 1298 // find max 'ns' for each trigger and that will be the 'lastEvent' 1299 $maxNs = max(array_keys($events)); 1300 $result[$triggerId]['lastEvent'] = $events[$maxNs]; 1301 } 1302 } 1303 1304 // adding trigger discovery 1305 if ($options['selectTriggerDiscovery'] !== null && $options['selectTriggerDiscovery'] !== API_OUTPUT_COUNT) { 1306 foreach ($result as &$trigger) { 1307 $trigger['triggerDiscovery'] = []; 1308 } 1309 unset($trigger); 1310 1311 $sql_select = ['triggerid']; 1312 foreach (['parent_triggerid', 'ts_delete'] as $field) { 1313 if ($this->outputIsRequested($field, $options['selectTriggerDiscovery'])) { 1314 $sql_select[] = $field; 1315 } 1316 } 1317 1318 $trigger_discoveries = DBselect( 1319 'SELECT '.implode(',', $sql_select). 1320 ' FROM trigger_discovery'. 1321 ' WHERE '.dbConditionInt('triggerid', $triggerids) 1322 ); 1323 1324 while ($trigger_discovery = DBfetch($trigger_discoveries)) { 1325 $triggerid = $trigger_discovery['triggerid']; 1326 unset($trigger_discovery['triggerid']); 1327 1328 $result[$triggerid]['triggerDiscovery'] = $trigger_discovery; 1329 } 1330 } 1331 1332 return $result; 1333 } 1334 1335 protected function applyQuerySortField($sortfield, $sortorder, $alias, array $sqlParts) { 1336 if ($sortfield === 'hostname') { 1337 $sqlParts['select']['hostname'] = 'h.name AS hostname'; 1338 $sqlParts['from']['functions'] = 'functions f'; 1339 $sqlParts['from']['items'] = 'items i'; 1340 $sqlParts['from']['hosts'] = 'hosts h'; 1341 $sqlParts['where'][] = 't.triggerid = f.triggerid'; 1342 $sqlParts['where'][] = 'f.itemid = i.itemid'; 1343 $sqlParts['where'][] = 'i.hostid = h.hostid'; 1344 $sqlParts['order'][] = 'h.name '.$sortorder; 1345 } 1346 else { 1347 $sqlParts = parent::applyQuerySortField($sortfield, $sortorder, $alias, $sqlParts); 1348 } 1349 1350 return $sqlParts; 1351 } 1352 1353 protected function requiresPostSqlFiltering(array $options) { 1354 return $options['skipDependent'] !== null || $options['withLastEventUnacknowledged'] !== null; 1355 } 1356 1357 protected function applyPostSqlFiltering(array $triggers, array $options) { 1358 $triggers = zbx_toHash($triggers, 'triggerid'); 1359 1360 // unset triggers which depend on at least one problem trigger upstream into dependency tree 1361 if ($options['skipDependent'] !== null) { 1362 // Result trigger IDs of all triggers in results. 1363 $resultTriggerIds = zbx_objectValues($triggers, 'triggerid'); 1364 1365 // Will contain IDs of all triggers on which some other trigger depends. 1366 $allUpTriggerIds = []; 1367 1368 // Trigger dependency map. 1369 $downToUpTriggerIds = []; 1370 1371 // Values (state) of each "up" trigger ID is stored in here. 1372 $upTriggerValues = []; 1373 1374 // Will contain IDs of all triggers either disabled directly, or by having disabled item or disabled host. 1375 $disabledTriggerIds = []; 1376 1377 // First loop uses result trigger IDs. 1378 $triggerIds = $resultTriggerIds; 1379 do { 1380 // Fetch all dependency records where "down" trigger IDs are in current iteration trigger IDs. 1381 $dbResult = DBselect( 1382 'SELECT d.triggerid_down,d.triggerid_up,t.value'. 1383 ' FROM trigger_depends d,triggers t'. 1384 ' WHERE d.triggerid_up=t.triggerid'. 1385 ' AND '.dbConditionInt('d.triggerid_down', $triggerIds) 1386 ); 1387 1388 // Add trigger IDs as keys and empty arrays as values. 1389 $downToUpTriggerIds = $downToUpTriggerIds + array_fill_keys($triggerIds, []); 1390 1391 $triggerIds = []; 1392 while ($dependency = DBfetch($dbResult)) { 1393 // Trigger ID for "down" trigger, which has dependencies. 1394 $downTriggerId = $dependency['triggerid_down']; 1395 1396 // Trigger ID for "up" trigger, on which the other ("up") trigger depends. 1397 $upTriggerId = $dependency['triggerid_up']; 1398 1399 // Add "up" trigger ID to mapping. We also index by $upTrigger because later these arrays 1400 // are combined with + and this way indexes and values do not break. 1401 $downToUpTriggerIds[$downTriggerId][$upTriggerId] = $upTriggerId; 1402 1403 // Add ID of this "up" trigger to all known "up" triggers. 1404 $allUpTriggerIds[] = $upTriggerId; 1405 1406 // Remember value of this "up" trigger. 1407 $upTriggerValues[$upTriggerId] = $dependency['value']; 1408 1409 // Add ID of this "up" trigger to the list of trigger IDs which should be mapped. 1410 $triggerIds[] = $upTriggerId; 1411 } 1412 } while ($triggerIds); 1413 1414 // Fetch trigger IDs for triggers that are disabled, have disabled items or disabled item hosts. 1415 $dbResult = DBSelect( 1416 'SELECT t.triggerid'. 1417 ' FROM triggers t,functions f,items i,hosts h'. 1418 ' WHERE t.triggerid=f.triggerid'. 1419 ' AND f.itemid=i.itemid'. 1420 ' AND i.hostid=h.hostid'. 1421 ' AND ('. 1422 'i.status='.ITEM_STATUS_DISABLED. 1423 ' OR h.status='.HOST_STATUS_NOT_MONITORED. 1424 ' OR t.status='.TRIGGER_STATUS_DISABLED. 1425 ')'. 1426 ' AND '.dbConditionInt('t.triggerid', $allUpTriggerIds) 1427 ); 1428 while ($row = DBfetch($dbResult)) { 1429 $resultTriggerId = $row['triggerid']; 1430 $disabledTriggerIds[$resultTriggerId] = $resultTriggerId; 1431 } 1432 1433 // Now process all mapped dependencies and unset any disabled "up" triggers so they do not participate in 1434 // decisions regarding nesting resolution in next step. 1435 foreach ($downToUpTriggerIds as $downTriggerId => $upTriggerIds) { 1436 $upTriggerIdsToUnset = []; 1437 foreach ($upTriggerIds as $upTriggerId) { 1438 if (isset($disabledTriggerIds[$upTriggerId])) { 1439 unset($downToUpTriggerIds[$downTriggerId][$upTriggerId]); 1440 } 1441 } 1442 } 1443 1444 // Resolve dependencies for all result set triggers. 1445 foreach ($resultTriggerIds as $resultTriggerId) { 1446 // We start with result trigger. 1447 $triggerIds = [$resultTriggerId]; 1448 1449 // This also is unrolled recursive function and is repeated until there are no more trigger IDs to 1450 // check, add and resolve. 1451 do { 1452 $nextTriggerIds = []; 1453 foreach ($triggerIds as $triggerId) { 1454 // Loop through all "up" triggers. 1455 foreach ($downToUpTriggerIds[$triggerId] as $upTriggerId) { 1456 if ($downToUpTriggerIds[$upTriggerId]) { 1457 // If there this "up" trigger has "up" triggers of it's own, merge them and proceed with recursion. 1458 $downToUpTriggerIds[$resultTriggerId] += $downToUpTriggerIds[$upTriggerId]; 1459 1460 // Add trigger ID to be processed in next loop iteration. 1461 $nextTriggerIds[] = $upTriggerId; 1462 } 1463 } 1464 } 1465 $triggerIds = $nextTriggerIds; 1466 } while ($triggerIds); 1467 } 1468 1469 // Clean result set. 1470 foreach ($resultTriggerIds as $resultTriggerId) { 1471 foreach ($downToUpTriggerIds[$resultTriggerId] as $upTriggerId) { 1472 // If "up" trigger is in problem state, dependent trigger should not be returned and is removed 1473 // from results. 1474 if ($upTriggerValues[$upTriggerId] == TRIGGER_VALUE_TRUE) { 1475 unset($triggers[$resultTriggerId]); 1476 } 1477 } 1478 1479 // Check if result trigger is disabled and if so, remove from results. 1480 if (isset($disabledTriggerIds[$resultTriggerId])) { 1481 unset($triggers[$resultTriggerId]); 1482 } 1483 } 1484 } 1485 1486 // withLastEventUnacknowledged 1487 if ($options['withLastEventUnacknowledged'] !== null) { 1488 $triggerIds = zbx_objectValues($triggers, 'triggerid'); 1489 $eventIds = []; 1490 $eventsDb = DBselect( 1491 'SELECT MAX(e.eventid) AS eventid,e.objectid'. 1492 ' FROM events e'. 1493 ' WHERE e.object='.EVENT_OBJECT_TRIGGER. 1494 ' AND e.source='.EVENT_SOURCE_TRIGGERS. 1495 ' AND '.dbConditionInt('e.objectid', $triggerIds). 1496 ' AND '.dbConditionInt('e.value', [TRIGGER_VALUE_TRUE]). 1497 ' GROUP BY e.objectid' 1498 ); 1499 while ($event = DBfetch($eventsDb)) { 1500 $eventIds[] = $event['eventid']; 1501 } 1502 1503 $correctTriggerIds = DBfetchArrayAssoc(DBselect( 1504 'SELECT e.objectid'. 1505 ' FROM events e '. 1506 ' WHERE '.dbConditionInt('e.eventid', $eventIds). 1507 ' AND e.acknowledged=0' 1508 ), 'objectid'); 1509 1510 foreach ($triggers as $triggerId => $trigger) { 1511 if (!isset($correctTriggerIds[$triggerId])) { 1512 unset($triggers[$triggerId]); 1513 } 1514 } 1515 } 1516 1517 return $triggers; 1518 } 1519} 1520