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