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 problems. 24 */ 25class CProblem extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER] 29 ]; 30 31 protected $tableName = 'problem'; 32 protected $tableAlias = 'p'; 33 protected $sortColumns = ['eventid']; 34 35 /** 36 * Get problem data. 37 * 38 * @param array $options 39 * 40 * @return array|int item data as array or false if error 41 */ 42 public function get($options = []) { 43 $result = []; 44 $userType = self::$userData['type']; 45 46 $sqlParts = [ 47 'select' => [$this->fieldId('eventid')], 48 'from' => ['p' => 'problem p'], 49 'where' => [], 50 'order' => [], 51 'group' => [], 52 'limit' => null 53 ]; 54 55 $defOptions = [ 56 'eventids' => null, 57 'groupids' => null, 58 'hostids' => null, 59 'objectids' => null, 60 61 'editable' => false, 62 'source' => EVENT_SOURCE_TRIGGERS, 63 'object' => EVENT_OBJECT_TRIGGER, 64 'severities' => null, 65 'nopermissions' => null, 66 // filter 67 'time_from' => null, 68 'time_till' => null, 69 'eventid_from' => null, 70 'eventid_till' => null, 71 'acknowledged' => null, 72 'suppressed' => null, 73 'recent' => null, 74 'any' => null, // (internal) true if need not filtred by r_eventid 75 'evaltype' => TAG_EVAL_TYPE_AND_OR, 76 'tags' => null, 77 'filter' => null, 78 'search' => null, 79 'searchByAny' => null, 80 'startSearch' => false, 81 'excludeSearch' => false, 82 'searchWildcardsEnabled' => null, 83 // output 84 'output' => API_OUTPUT_EXTEND, 85 'selectAcknowledges' => null, 86 'selectSuppressionData' => null, 87 'selectTags' => null, 88 'countOutput' => false, 89 'preservekeys' => false, 90 'sortfield' => '', 91 'sortorder' => '', 92 'limit' => null 93 ]; 94 $options = zbx_array_merge($defOptions, $options); 95 96 $this->validateGet($options); 97 98 // source and object 99 $sqlParts['where'][] = 'p.source='.zbx_dbstr($options['source']); 100 $sqlParts['where'][] = 'p.object='.zbx_dbstr($options['object']); 101 102 // editable + PERMISSION CHECK 103 if ($userType != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 104 // triggers 105 if ($options['object'] == EVENT_OBJECT_TRIGGER) { 106 $user_groups = getUserGroupsByUserId(self::$userData['userid']); 107 108 // specific triggers 109 if ($options['objectids'] !== null) { 110 $options['objectids'] = array_keys(API::Trigger()->get([ 111 'output' => [], 112 'triggerids' => $options['objectids'], 113 'editable' => $options['editable'], 114 'preservekeys' => true 115 ])); 116 } 117 // all triggers 118 else { 119 $sqlParts['where'][] = 'NOT EXISTS ('. 120 'SELECT NULL'. 121 ' FROM functions f,items i,hosts_groups hgg'. 122 ' LEFT JOIN rights r'. 123 ' ON r.id=hgg.groupid'. 124 ' AND '.dbConditionInt('r.groupid', $user_groups). 125 ' WHERE p.objectid=f.triggerid'. 126 ' AND f.itemid=i.itemid'. 127 ' AND i.hostid=hgg.hostid'. 128 ' GROUP BY i.hostid'. 129 ' HAVING MAX(permission)<'.($options['editable'] ? PERM_READ_WRITE : PERM_READ). 130 ' OR MIN(permission) IS NULL'. 131 ' OR MIN(permission)='.PERM_DENY. 132 ')'; 133 } 134 135 if ($options['source'] == EVENT_SOURCE_TRIGGERS) { 136 $sqlParts = self::addTagFilterSqlParts($user_groups, $sqlParts); 137 } 138 } 139 elseif ($options['object'] == EVENT_OBJECT_ITEM || $options['object'] == EVENT_OBJECT_LLDRULE) { 140 // specific items or lld rules 141 if ($options['objectids'] !== null) { 142 if ($options['object'] == EVENT_OBJECT_ITEM) { 143 $items = API::Item()->get([ 144 'output' => [], 145 'itemids' => $options['objectids'], 146 'editable' => $options['editable'], 147 'preservekeys' => true 148 ]); 149 $options['objectids'] = array_keys($items); 150 } 151 elseif ($options['object'] == EVENT_OBJECT_LLDRULE) { 152 $items = API::DiscoveryRule()->get([ 153 'output' => [], 154 'itemids' => $options['objectids'], 155 'editable' => $options['editable'], 156 'preservekeys' => true 157 ]); 158 $options['objectids'] = array_keys($items); 159 } 160 } 161 // all items or lld rules 162 else { 163 $user_groups = getUserGroupsByUserId(self::$userData['userid']); 164 165 $sqlParts['where'][] = 'EXISTS ('. 166 'SELECT NULL'. 167 ' FROM items i,hosts_groups hgg'. 168 ' JOIN rights r'. 169 ' ON r.id=hgg.groupid'. 170 ' AND '.dbConditionInt('r.groupid', $user_groups). 171 ' WHERE p.objectid=i.itemid'. 172 ' AND i.hostid=hgg.hostid'. 173 ' GROUP BY hgg.hostid'. 174 ' HAVING MIN(r.permission)>'.PERM_DENY. 175 ' AND MAX(r.permission)>='.($options['editable'] ? PERM_READ_WRITE : PERM_READ). 176 ')'; 177 } 178 } 179 } 180 181 // eventids 182 if ($options['eventids'] !== null) { 183 zbx_value2array($options['eventids']); 184 $sqlParts['where'][] = dbConditionInt('p.eventid', $options['eventids']); 185 } 186 187 // objectids 188 if ($options['objectids'] !== null) { 189 zbx_value2array($options['objectids']); 190 $sqlParts['where'][] = dbConditionInt('p.objectid', $options['objectids']); 191 } 192 193 // groupids 194 if ($options['groupids'] !== null) { 195 zbx_value2array($options['groupids']); 196 197 // triggers 198 if ($options['object'] == EVENT_OBJECT_TRIGGER) { 199 $sqlParts['from']['f'] = 'functions f'; 200 $sqlParts['from']['i'] = 'items i'; 201 $sqlParts['from']['hg'] = 'hosts_groups hg'; 202 $sqlParts['where']['p-f'] = 'p.objectid=f.triggerid'; 203 $sqlParts['where']['f-i'] = 'f.itemid=i.itemid'; 204 $sqlParts['where']['i-hg'] = 'i.hostid=hg.hostid'; 205 $sqlParts['where']['hg'] = dbConditionInt('hg.groupid', $options['groupids']); 206 } 207 // lld rules and items 208 elseif ($options['object'] == EVENT_OBJECT_LLDRULE || $options['object'] == EVENT_OBJECT_ITEM) { 209 $sqlParts['from']['i'] = 'items i'; 210 $sqlParts['from']['hg'] = 'hosts_groups hg'; 211 $sqlParts['where']['p-i'] = 'p.objectid=i.itemid'; 212 $sqlParts['where']['i-hg'] = 'i.hostid=hg.hostid'; 213 $sqlParts['where']['hg'] = dbConditionInt('hg.groupid', $options['groupids']); 214 } 215 } 216 217 // hostids 218 if ($options['hostids'] !== null) { 219 zbx_value2array($options['hostids']); 220 221 // triggers 222 if ($options['object'] == EVENT_OBJECT_TRIGGER) { 223 $sqlParts['from']['f'] = 'functions f'; 224 $sqlParts['from']['i'] = 'items i'; 225 $sqlParts['where']['p-f'] = 'p.objectid=f.triggerid'; 226 $sqlParts['where']['f-i'] = 'f.itemid=i.itemid'; 227 $sqlParts['where']['i'] = dbConditionInt('i.hostid', $options['hostids']); 228 } 229 // lld rules and items 230 elseif ($options['object'] == EVENT_OBJECT_LLDRULE || $options['object'] == EVENT_OBJECT_ITEM) { 231 $sqlParts['from']['i'] = 'items i'; 232 $sqlParts['where']['p-i'] = 'p.objectid=i.itemid'; 233 $sqlParts['where']['i'] = dbConditionInt('i.hostid', $options['hostids']); 234 } 235 } 236 237 // severities 238 if ($options['severities'] !== null) { 239 // triggers 240 if ($options['object'] == EVENT_OBJECT_TRIGGER) { 241 zbx_value2array($options['severities']); 242 $sqlParts['where'][] = dbConditionInt('p.severity', $options['severities']); 243 } 244 // ignore this filter for items and lld rules 245 } 246 247 // acknowledged 248 if ($options['acknowledged'] !== null) { 249 $acknowledged = $options['acknowledged'] ? EVENT_ACKNOWLEDGED : EVENT_NOT_ACKNOWLEDGED; 250 $sqlParts['where'][] = 'p.acknowledged='.$acknowledged; 251 } 252 253 // suppressed 254 if ($options['suppressed'] !== null) { 255 $sqlParts['where'][] = (!$options['suppressed'] ? 'NOT ' : ''). 256 'EXISTS ('. 257 'SELECT NULL'. 258 ' FROM event_suppress es'. 259 ' WHERE es.eventid=p.eventid'. 260 ')'; 261 } 262 263 // tags 264 if ($options['tags'] !== null && $options['tags']) { 265 $sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'p', 266 'problem_tag', 'eventid' 267 ); 268 } 269 270 // recent 271 if ($options['recent'] !== null && $options['recent']) { 272 $ok_events_from = time() - timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::OK_PERIOD)); 273 274 $sqlParts['where'][] = '(p.r_eventid IS NULL OR p.r_clock>'.$ok_events_from.')'; 275 } 276 else { 277 $sqlParts['where'][] = 'p.r_eventid IS NULL'; 278 } 279 280 // time_from 281 if ($options['time_from'] !== null) { 282 $sqlParts['where'][] = 'p.clock>='.zbx_dbstr($options['time_from']); 283 } 284 285 // time_till 286 if ($options['time_till'] !== null) { 287 $sqlParts['where'][] = 'p.clock<='.zbx_dbstr($options['time_till']); 288 } 289 290 // eventid_from 291 if ($options['eventid_from'] !== null) { 292 $sqlParts['where'][] = 'p.eventid>='.zbx_dbstr($options['eventid_from']); 293 } 294 295 // eventid_till 296 if ($options['eventid_till'] !== null) { 297 $sqlParts['where'][] = 'p.eventid<='.zbx_dbstr($options['eventid_till']); 298 } 299 300 // search 301 if (is_array($options['search'])) { 302 zbx_db_search('problem p', $options, $sqlParts); 303 } 304 305 // filter 306 if (is_array($options['filter'])) { 307 $this->dbFilter('problem p', $options, $sqlParts); 308 } 309 310 // limit 311 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 312 $sqlParts['limit'] = $options['limit']; 313 } 314 315 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 316 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 317 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 318 while ($event = DBfetch($res)) { 319 if ($options['countOutput']) { 320 $result = $event['rowscount']; 321 } 322 else { 323 $result[$event['eventid']] = $event; 324 } 325 } 326 327 if ($options['countOutput']) { 328 return $result; 329 } 330 331 if ($result) { 332 $result = $this->addRelatedObjects($options, $result); 333 $result = $this->unsetExtraFields($result, ['object', 'objectid'], $options['output']); 334 } 335 336 // removing keys (hash -> array) 337 if (!$options['preservekeys']) { 338 $result = zbx_cleanHashes($result); 339 } 340 341 return $result; 342 } 343 344 /** 345 * Validates the input parameters for the get() method. 346 * 347 * @throws APIException if the input is invalid 348 * 349 * @param array $options 350 */ 351 protected function validateGet(array $options) { 352 $sourceValidator = new CLimitedSetValidator([ 353 'values' => [EVENT_SOURCE_TRIGGERS, EVENT_SOURCE_INTERNAL] 354 ]); 355 if (!$sourceValidator->validate($options['source'])) { 356 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect source value.')); 357 } 358 359 $objectValidator = new CLimitedSetValidator([ 360 'values' => [EVENT_OBJECT_TRIGGER, EVENT_OBJECT_ITEM, EVENT_OBJECT_LLDRULE] 361 ]); 362 if (!$objectValidator->validate($options['object'])) { 363 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect object value.')); 364 } 365 366 $sourceObjectValidator = new CEventSourceObjectValidator(); 367 if (!$sourceObjectValidator->validate(['source' => $options['source'], 'object' => $options['object']])) { 368 self::exception(ZBX_API_ERROR_PARAMETERS, $sourceObjectValidator->getError()); 369 } 370 371 $evaltype_validator = new CLimitedSetValidator([ 372 'values' => [TAG_EVAL_TYPE_AND_OR, TAG_EVAL_TYPE_OR] 373 ]); 374 if (!$evaltype_validator->validate($options['evaltype'])) { 375 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect evaltype value.')); 376 } 377 } 378 379 protected function addRelatedObjects(array $options, array $result) { 380 $result = parent::addRelatedObjects($options, $result); 381 382 $eventids = array_keys($result); 383 384 // Adding operational data. 385 if ($this->outputIsRequested('opdata', $options['output'])) { 386 $problems = DBFetchArrayAssoc(DBselect( 387 'SELECT p.eventid,p.clock,p.ns,t.triggerid,t.expression,t.opdata'. 388 ' FROM problem p'. 389 ' JOIN triggers t ON t.triggerid=p.objectid'. 390 ' WHERE '.dbConditionInt('p.eventid', $eventids) 391 ), 'eventid'); 392 393 foreach ($result as $eventid => $problem) { 394 $result[$eventid]['opdata'] = 395 (array_key_exists($eventid, $problems) && $problems[$eventid]['opdata'] !== '') 396 ? CMacrosResolverHelper::resolveTriggerOpdata($problems[$eventid], ['events' => true]) 397 : ''; 398 } 399 } 400 401 // adding acknowledges 402 if ($options['selectAcknowledges'] !== null) { 403 if ($options['selectAcknowledges'] != API_OUTPUT_COUNT) { 404 // create the base query 405 $acknowledges = API::getApiService()->select('acknowledges', [ 406 'output' => $this->outputExtend($options['selectAcknowledges'], 407 ['acknowledgeid', 'eventid'] 408 ), 409 'filter' => ['eventid' => $eventids], 410 'preservekeys' => true 411 ]); 412 413 $relationMap = $this->createRelationMap($acknowledges, 'eventid', 'acknowledgeid'); 414 $acknowledges = $this->unsetExtraFields($acknowledges, ['eventid', 'acknowledgeid'], 415 $options['selectAcknowledges'] 416 ); 417 $result = $relationMap->mapMany($result, $acknowledges, 'acknowledges'); 418 } 419 else { 420 $acknowledges = DBFetchArrayAssoc(DBselect( 421 'SELECT a.eventid,COUNT(a.acknowledgeid) AS rowscount'. 422 ' FROM acknowledges a'. 423 ' WHERE '.dbConditionInt('a.eventid', $eventids). 424 ' GROUP BY a.eventid' 425 ), 'eventid'); 426 427 foreach ($result as $eventid => $event) { 428 $result[$eventid]['acknowledges'] = array_key_exists($eventid, $acknowledges) 429 ? $acknowledges[$eventid]['rowscount'] 430 : '0'; 431 } 432 } 433 } 434 435 // Adding suppression data. 436 if ($options['selectSuppressionData'] !== null && $options['selectSuppressionData'] != API_OUTPUT_COUNT) { 437 $suppression_data = API::getApiService()->select('event_suppress', [ 438 'output' => $this->outputExtend($options['selectSuppressionData'], ['eventid', 'maintenanceid']), 439 'filter' => ['eventid' => $eventids], 440 'preservekeys' => true 441 ]); 442 $relation_map = $this->createRelationMap($suppression_data, 'eventid', 'event_suppressid'); 443 $suppression_data = $this->unsetExtraFields($suppression_data, ['event_suppressid', 'eventid'], []); 444 $result = $relation_map->mapMany($result, $suppression_data, 'suppression_data'); 445 } 446 447 // Adding suppressed value. 448 if ($this->outputIsRequested('suppressed', $options['output'])) { 449 $suppressed_eventids = []; 450 foreach ($result as &$problem) { 451 if (array_key_exists('suppression_data', $problem)) { 452 $problem['suppressed'] = $problem['suppression_data'] 453 ? (string) ZBX_PROBLEM_SUPPRESSED_TRUE 454 : (string) ZBX_PROBLEM_SUPPRESSED_FALSE; 455 } 456 else { 457 $suppressed_eventids[] = $problem['eventid']; 458 } 459 } 460 unset($problem); 461 462 if ($suppressed_eventids) { 463 $suppressed_events = API::getApiService()->select('event_suppress', [ 464 'output' => ['eventid'], 465 'filter' => ['eventid' => $suppressed_eventids] 466 ]); 467 $suppressed_eventids = array_flip(zbx_objectValues($suppressed_events, 'eventid')); 468 foreach ($result as &$problem) { 469 $problem['suppressed'] = array_key_exists($problem['eventid'], $suppressed_eventids) 470 ? (string) ZBX_PROBLEM_SUPPRESSED_TRUE 471 : (string) ZBX_PROBLEM_SUPPRESSED_FALSE; 472 } 473 unset($problem); 474 } 475 } 476 477 // Remove "maintenanceid" field if it's not requested. 478 if ($options['selectSuppressionData'] !== null && $options['selectSuppressionData'] != API_OUTPUT_COUNT 479 && !$this->outputIsRequested('maintenanceid', $options['selectSuppressionData'])) { 480 foreach ($result as &$row) { 481 $row['suppression_data'] = $this->unsetExtraFields($row['suppression_data'], ['maintenanceid'], []); 482 } 483 unset($row); 484 } 485 486 // Resolve webhook urls. 487 if ($this->outputIsRequested('urls', $options['output'])) { 488 $tags_options = [ 489 'output' => ['eventid', 'tag', 'value'], 490 'filter' => ['eventid' => $eventids] 491 ]; 492 $tags = DBselect(DB::makeSql('problem_tag', $tags_options)); 493 494 $events = []; 495 496 foreach ($result as $event) { 497 $events[$event['eventid']]['tags'] = []; 498 } 499 500 while ($tag = DBfetch($tags)) { 501 $events[$tag['eventid']]['tags'][] = [ 502 'tag' => $tag['tag'], 503 'value' => $tag['value'] 504 ]; 505 } 506 507 $urls = DB::select('media_type', [ 508 'output' => ['event_menu_url', 'event_menu_name'], 509 'filter' => [ 510 'type' => MEDIA_TYPE_WEBHOOK, 511 'status' => MEDIA_TYPE_STATUS_ACTIVE, 512 'show_event_menu' => ZBX_EVENT_MENU_SHOW 513 ] 514 ]); 515 516 $events = CMacrosResolverHelper::resolveMediaTypeUrls($events, $urls); 517 518 foreach ($events as $eventid => $event) { 519 $result[$eventid]['urls'] = $event['urls']; 520 } 521 } 522 523 // Adding event tags. 524 if ($options['selectTags'] !== null && $options['selectTags'] != API_OUTPUT_COUNT) { 525 if ($options['selectTags'] === API_OUTPUT_EXTEND) { 526 $options['selectTags'] = ['tag', 'value']; 527 } 528 529 $tags_options = [ 530 'output' => $this->outputExtend($options['selectTags'], ['eventid']), 531 'filter' => ['eventid' => $eventids] 532 ]; 533 $tags = DBselect(DB::makeSql('problem_tag', $tags_options)); 534 535 foreach ($result as &$event) { 536 $event['tags'] = []; 537 } 538 unset($event); 539 540 while ($tag = DBfetch($tags)) { 541 $event = &$result[$tag['eventid']]; 542 543 unset($tag['problemtagid'], $tag['eventid']); 544 $event['tags'][] = $tag; 545 } 546 unset($event); 547 } 548 549 return $result; 550 } 551 552 /** 553 * Add sql parts related to tag-based permissions. 554 * 555 * @param array $usrgrpids 556 * @param array $sqlParts 557 * 558 * @return array 559 */ 560 protected static function addTagFilterSqlParts(array $usrgrpids, array $sqlParts) { 561 $tag_filters = CEvent::getTagFilters($usrgrpids); 562 563 if (!$tag_filters) { 564 return $sqlParts; 565 } 566 567 $sqlParts['from']['f'] = 'functions f'; 568 $sqlParts['from']['i'] = 'items i'; 569 $sqlParts['from']['hg'] = 'hosts_groups hg'; 570 $sqlParts['where']['p-f'] = 'p.objectid=f.triggerid'; 571 $sqlParts['where']['f-i'] = 'f.itemid=i.itemid'; 572 $sqlParts['where']['i-hg'] = 'i.hostid=hg.hostid'; 573 574 $tag_conditions = []; 575 $full_access_groupids = []; 576 577 foreach ($tag_filters as $groupid => $filters) { 578 $tags = []; 579 $tag_values = []; 580 581 foreach ($filters as $filter) { 582 if ($filter['tag'] === '') { 583 $full_access_groupids[] = $groupid; 584 continue 2; 585 } 586 elseif ($filter['value'] === '') { 587 $tags[] = $filter['tag']; 588 } 589 else { 590 $tag_values[$filter['tag']][] = $filter['value']; 591 } 592 } 593 594 $conditions = []; 595 596 if ($tags) { 597 $conditions[] = dbConditionString('pt.tag', $tags); 598 } 599 $parenthesis = $tags || count($tag_values) > 1; 600 601 foreach ($tag_values as $tag => $values) { 602 $condition = 'pt.tag='.zbx_dbstr($tag).' AND '.dbConditionString('pt.value', $values); 603 $conditions[] = $parenthesis ? '('.$condition.')' : $condition; 604 } 605 606 $conditions = (count($conditions) > 1) ? '('.implode(' OR ', $conditions).')' : $conditions[0]; 607 608 $tag_conditions[] = 'hg.groupid='.zbx_dbstr($groupid).' AND '.$conditions; 609 } 610 611 if ($tag_conditions) { 612 $sqlParts['from']['pt'] = 'problem_tag pt'; 613 $sqlParts['where']['p-pt'] = 'p.eventid=pt.eventid'; 614 615 if ($full_access_groupids || count($tag_conditions) > 1) { 616 foreach ($tag_conditions as &$tag_condition) { 617 $tag_condition = '('.$tag_condition.')'; 618 } 619 unset($tag_condition); 620 } 621 } 622 623 if ($full_access_groupids) { 624 $tag_conditions[] = dbConditionInt('hg.groupid', $full_access_groupids); 625 } 626 627 $sqlParts['where'][] = (count($tag_conditions) > 1) 628 ? '('.implode(' OR ', $tag_conditions).')' 629 : $tag_conditions[0]; 630 631 return $sqlParts; 632 } 633} 634