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