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 host groups. 24 */ 25class CHostGroup extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 32 'massadd' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 33 'massupdate' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN], 34 'massremove' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN] 35 ]; 36 37 protected $tableName = 'hstgrp'; 38 protected $tableAlias = 'g'; 39 protected $sortColumns = ['groupid', 'name']; 40 41 /** 42 * Get host groups. 43 * 44 * @param array $params 45 * 46 * @return array 47 */ 48 public function get($params) { 49 $result = []; 50 51 $sqlParts = [ 52 'select' => ['hstgrp' => 'g.groupid'], 53 'from' => ['hstgrp' => 'hstgrp g'], 54 'where' => [], 55 'order' => [], 56 'limit' => null 57 ]; 58 59 $defOptions = [ 60 'groupids' => null, 61 'hostids' => null, 62 'templateids' => null, 63 'graphids' => null, 64 'triggerids' => null, 65 'maintenanceids' => null, 66 'monitored_hosts' => null, 67 'templated_hosts' => null, 68 'real_hosts' => null, 69 'with_hosts_and_templates' => null, 70 'with_items' => null, 71 'with_item_prototypes' => null, 72 'with_simple_graph_items' => null, 73 'with_simple_graph_item_prototypes' => null, 74 'with_monitored_items' => null, 75 'with_triggers' => null, 76 'with_monitored_triggers' => null, 77 'with_httptests' => null, 78 'with_monitored_httptests' => null, 79 'with_graphs' => null, 80 'with_graph_prototypes' => null, 81 'editable' => false, 82 'nopermissions' => null, 83 // filter 84 'filter' => null, 85 'search' => null, 86 'searchByAny' => null, 87 'startSearch' => false, 88 'excludeSearch' => false, 89 'searchWildcardsEnabled' => null, 90 // output 91 'output' => API_OUTPUT_EXTEND, 92 'selectHosts' => null, 93 'selectTemplates' => null, 94 'selectGroupDiscovery' => null, 95 'selectDiscoveryRule' => null, 96 'countOutput' => false, 97 'groupCount' => false, 98 'preservekeys' => false, 99 'sortfield' => '', 100 'sortorder' => '', 101 'limit' => null, 102 'limitSelects' => null 103 ]; 104 $options = zbx_array_merge($defOptions, $params); 105 106 // editable + PERMISSION CHECK 107 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 108 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 109 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 110 111 $sqlParts['where'][] = 'EXISTS ('. 112 'SELECT NULL'. 113 ' FROM rights r'. 114 ' WHERE g.groupid=r.id'. 115 ' AND '.dbConditionInt('r.groupid', $userGroups). 116 ' GROUP BY r.id'. 117 ' HAVING MIN(r.permission)>'.PERM_DENY. 118 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 119 ')'; 120 } 121 122 // groupids 123 if (!is_null($options['groupids'])) { 124 zbx_value2array($options['groupids']); 125 $sqlParts['where']['groupid'] = dbConditionInt('g.groupid', $options['groupids']); 126 } 127 128 // templateids 129 if (!is_null($options['templateids'])) { 130 zbx_value2array($options['templateids']); 131 132 if (!is_null($options['hostids'])) { 133 zbx_value2array($options['hostids']); 134 $options['hostids'] = array_merge($options['hostids'], $options['templateids']); 135 } 136 else { 137 $options['hostids'] = $options['templateids']; 138 } 139 } 140 141 // hostids 142 if (!is_null($options['hostids'])) { 143 zbx_value2array($options['hostids']); 144 145 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 146 $sqlParts['where'][] = dbConditionInt('hg.hostid', $options['hostids']); 147 $sqlParts['where']['hgg'] = 'hg.groupid=g.groupid'; 148 } 149 150 // triggerids 151 if (!is_null($options['triggerids'])) { 152 zbx_value2array($options['triggerids']); 153 154 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 155 $sqlParts['from']['functions'] = 'functions f'; 156 $sqlParts['from']['items'] = 'items i'; 157 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 158 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 159 $sqlParts['where']['hgi'] = 'hg.hostid=i.hostid'; 160 $sqlParts['where']['hgg'] = 'hg.groupid=g.groupid'; 161 } 162 163 // graphids 164 if (!is_null($options['graphids'])) { 165 zbx_value2array($options['graphids']); 166 167 $sqlParts['from']['gi'] = 'graphs_items gi'; 168 $sqlParts['from']['i'] = 'items i'; 169 $sqlParts['from']['hg'] = 'hosts_groups hg'; 170 $sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']); 171 $sqlParts['where']['hgg'] = 'hg.groupid=g.groupid'; 172 $sqlParts['where']['igi'] = 'i.itemid=gi.itemid'; 173 $sqlParts['where']['hgi'] = 'hg.hostid=i.hostid'; 174 } 175 176 // maintenanceids 177 if (!is_null($options['maintenanceids'])) { 178 zbx_value2array($options['maintenanceids']); 179 180 $sqlParts['from']['maintenances_groups'] = 'maintenances_groups mg'; 181 $sqlParts['where'][] = dbConditionInt('mg.maintenanceid', $options['maintenanceids']); 182 $sqlParts['where']['hmh'] = 'g.groupid=mg.groupid'; 183 } 184 185 $sub_sql_common = []; 186 187 // monitored_hosts, real_hosts, templated_hosts, with_hosts_and_templates 188 if ($options['monitored_hosts'] !== null) { 189 $sub_sql_common['from']['h'] = 'hosts h'; 190 $sub_sql_common['where']['hg-h'] = 'hg.hostid=h.hostid'; 191 $sub_sql_common['where'][] = dbConditionInt('h.status', [HOST_STATUS_MONITORED]); 192 } 193 elseif ($options['real_hosts'] !== null) { 194 $sub_sql_common['from']['h'] = 'hosts h'; 195 $sub_sql_common['where']['hg-h'] = 'hg.hostid=h.hostid'; 196 $sub_sql_common['where'][] = dbConditionInt('h.status', [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]); 197 } 198 elseif ($options['templated_hosts'] !== null) { 199 $sub_sql_common['from']['h'] = 'hosts h'; 200 $sub_sql_common['where']['hg-h'] = 'hg.hostid=h.hostid'; 201 $sub_sql_common['where'][] = dbConditionInt('h.status', [HOST_STATUS_TEMPLATE]); 202 } 203 elseif ($options['with_hosts_and_templates'] !== null) { 204 $sub_sql_common['from']['h'] = 'hosts h'; 205 $sub_sql_common['where']['hg-h'] = 'hg.hostid=h.hostid'; 206 $sub_sql_common['where'][] = dbConditionInt('h.status', 207 [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED, HOST_STATUS_TEMPLATE] 208 ); 209 } 210 211 $sub_sql_parts = $sub_sql_common; 212 213 // with_items, with_monitored_items, with_simple_graph_items 214 if ($options['with_items'] !== null) { 215 $sub_sql_parts['from']['i'] = 'items i'; 216 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 217 $sub_sql_parts['where'][] = dbConditionInt('i.flags', 218 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 219 ); 220 } 221 elseif ($options['with_monitored_items'] !== null) { 222 $sub_sql_parts['from']['i'] = 'items i'; 223 $sub_sql_parts['from']['h'] = 'hosts h'; 224 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 225 $sub_sql_parts['where']['hg-h'] = 'hg.hostid=h.hostid'; 226 $sub_sql_parts['where'][] = dbConditionInt('h.status', [HOST_STATUS_MONITORED]); 227 $sub_sql_parts['where'][] = dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]); 228 $sub_sql_parts['where'][] = dbConditionInt('i.flags', 229 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 230 ); 231 } 232 elseif ($options['with_simple_graph_items'] !== null) { 233 $sub_sql_parts['from']['i'] = 'items i'; 234 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 235 $sub_sql_parts['where'][] = dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]); 236 $sub_sql_parts['where'][] = dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]); 237 $sub_sql_parts['where'][] = dbConditionInt('i.flags', 238 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 239 ); 240 } 241 242 // with_triggers, with_monitored_triggers 243 if ($options['with_triggers'] !== null) { 244 $sub_sql_parts['from']['i'] = 'items i'; 245 $sub_sql_parts['from']['f'] = 'functions f'; 246 $sub_sql_parts['from']['t'] = 'triggers t'; 247 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 248 $sub_sql_parts['where']['i-f'] = 'i.itemid=f.itemid'; 249 $sub_sql_parts['where']['f-t'] = 'f.triggerid=t.triggerid'; 250 $sub_sql_parts['where'][] = dbConditionInt('t.flags', 251 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 252 ); 253 } 254 elseif ($options['with_monitored_triggers'] !== null) { 255 $sub_sql_parts['from']['i'] = 'items i'; 256 $sub_sql_parts['from']['h'] = 'hosts h'; 257 $sub_sql_parts['from']['f'] = 'functions f'; 258 $sub_sql_parts['from']['t'] = 'triggers t'; 259 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 260 $sub_sql_parts['where']['hg-h'] = 'hg.hostid=h.hostid'; 261 $sub_sql_parts['where']['i-f'] = 'i.itemid=f.itemid'; 262 $sub_sql_parts['where']['f-t'] = 'f.triggerid=t.triggerid'; 263 $sub_sql_parts['where'][] = dbConditionInt('h.status', [HOST_STATUS_MONITORED]); 264 $sub_sql_parts['where'][] = dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]); 265 $sub_sql_parts['where'][] = dbConditionInt('t.status', [TRIGGER_STATUS_ENABLED]); 266 $sub_sql_parts['where'][] = dbConditionInt('t.flags', 267 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 268 ); 269 } 270 271 // with_httptests, with_monitored_httptests 272 if ($options['with_httptests'] !== null) { 273 $sub_sql_parts['from']['ht'] = 'httptest ht'; 274 $sub_sql_parts['where']['hg-ht'] = 'hg.hostid=ht.hostid'; 275 } 276 elseif ($options['with_monitored_httptests'] !== null) { 277 $sub_sql_parts['from']['ht'] = 'httptest ht'; 278 $sub_sql_parts['where']['hg-ht'] = 'hg.hostid=ht.hostid'; 279 $sub_sql_parts['where'][] = dbConditionInt('ht.status', [HTTPTEST_STATUS_ACTIVE]); 280 } 281 282 // with_graphs 283 if ($options['with_graphs'] !== null) { 284 $sub_sql_parts['from']['i'] = 'items i'; 285 $sub_sql_parts['from']['gi'] = 'graphs_items gi'; 286 $sub_sql_parts['from']['gr'] = 'graphs gr'; 287 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 288 $sub_sql_parts['where']['i-gi'] = 'i.itemid=gi.itemid'; 289 $sub_sql_parts['where']['gi-gr'] = 'gi.graphid=gr.graphid'; 290 $sub_sql_parts['where'][] = dbConditionInt('gr.flags', 291 [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED] 292 ); 293 } 294 295 if ($sub_sql_parts) { 296 $sub_sql_parts['from']['hg'] = 'hosts_groups hg'; 297 $sub_sql_parts['where']['g-hg'] = 'g.groupid=hg.groupid'; 298 299 $sqlParts['where'][] = 'EXISTS ('. 300 'SELECT NULL'. 301 ' FROM '.implode(',', $sub_sql_parts['from']). 302 ' WHERE '.implode(' AND ', array_unique($sub_sql_parts['where'])). 303 ')'; 304 } 305 306 $sub_sql_parts = $sub_sql_common; 307 308 // with_item_prototypes, with_simple_graph_item_prototypes 309 if ($options['with_item_prototypes'] !== null) { 310 $sub_sql_parts['from']['i'] = 'items i'; 311 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 312 $sub_sql_parts['where'][] = dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]); 313 } 314 elseif ($options['with_simple_graph_item_prototypes'] !== null) { 315 $sub_sql_parts['from']['i'] = 'items i'; 316 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 317 $sub_sql_parts['where'][] = dbConditionInt('i.value_type', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]); 318 $sub_sql_parts['where'][] = dbConditionInt('i.status', [ITEM_STATUS_ACTIVE]); 319 $sub_sql_parts['where'][] = dbConditionInt('i.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]); 320 } 321 322 // with_graph_prototypes 323 if ($options['with_graph_prototypes'] !== null) { 324 $sub_sql_parts['from']['i'] = 'items i'; 325 $sub_sql_parts['from']['gi'] = 'graphs_items gi'; 326 $sub_sql_parts['from']['gr'] = 'graphs gr'; 327 $sub_sql_parts['where']['hg-i'] = 'hg.hostid=i.hostid'; 328 $sub_sql_parts['where']['i-gi'] = 'i.itemid=gi.itemid'; 329 $sub_sql_parts['where']['gi-gr'] = 'gi.graphid=gr.graphid'; 330 $sub_sql_parts['where'][] = dbConditionInt('gr.flags', [ZBX_FLAG_DISCOVERY_PROTOTYPE]); 331 } 332 333 if ($sub_sql_parts) { 334 $sub_sql_parts['from']['hg'] = 'hosts_groups hg'; 335 $sub_sql_parts['where']['g-hg'] = 'g.groupid=hg.groupid'; 336 337 $sqlParts['where'][] = 'EXISTS ('. 338 'SELECT NULL'. 339 ' FROM '.implode(',', $sub_sql_parts['from']). 340 ' WHERE '.implode(' AND ', array_unique($sub_sql_parts['where'])). 341 ')'; 342 } 343 344 // filter 345 if (is_array($options['filter'])) { 346 $this->dbFilter('hstgrp g', $options, $sqlParts); 347 } 348 349 // search 350 if (is_array($options['search'])) { 351 zbx_db_search('hstgrp g', $options, $sqlParts); 352 } 353 354 // limit 355 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 356 $sqlParts['limit'] = $options['limit']; 357 } 358 359 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 360 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 361 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 362 while ($group = DBfetch($res)) { 363 if ($options['countOutput']) { 364 if ($options['groupCount']) { 365 $result[] = $group; 366 } 367 else { 368 $result = $group['rowscount']; 369 } 370 } 371 else { 372 $result[$group['groupid']] = $group; 373 } 374 } 375 376 if ($options['countOutput']) { 377 return $result; 378 } 379 380 if ($result) { 381 $result = $this->addRelatedObjects($options, $result); 382 } 383 384 // removing keys (hash -> array) 385 if (!$options['preservekeys']) { 386 $result = zbx_cleanHashes($result); 387 } 388 389 return $result; 390 } 391 392 /** 393 * Inherit rights from parent host groups. 394 * 395 * @param array $groups 396 * @param string $groups[]['groupid'] 397 * @param string $groups[]['name'] 398 */ 399 private function inheritRights(array $groups) { 400 $parent_names = []; 401 402 foreach ($groups as $group) { 403 $name = $group['name']; 404 405 while (($pos = strrpos($name, '/')) !== false) { 406 $name = substr($name, 0, $pos); 407 $parent_names[$name][] = $group['groupid']; 408 } 409 } 410 411 if ($parent_names) { 412 $options = [ 413 'output' => ['groupid', 'name'], 414 'filter' => ['name' => array_keys($parent_names)] 415 ]; 416 $result = DBselect(DB::makeSql('hstgrp', $options)); 417 418 $db_parent_groups = []; 419 420 while ($row = DBfetch($result)) { 421 $db_parent_groups[$row['name']] = $row['groupid']; 422 } 423 424 $parent_groupids = []; 425 426 foreach ($groups as $group) { 427 $name = $group['name']; 428 429 while (($pos = strrpos($name, '/')) !== false) { 430 $name = substr($name, 0, $pos); 431 432 if (array_key_exists($name, $db_parent_groups)) { 433 $parent_groupids[$db_parent_groups[$name]][] = $group['groupid']; 434 break; 435 } 436 } 437 } 438 439 if ($parent_groupids) { 440 $db_rights = DB::select('rights', [ 441 'output' => ['groupid', 'id', 'permission'], 442 'filter' => ['id' => array_keys($parent_groupids)] 443 ]); 444 445 $rights = []; 446 447 foreach ($db_rights as $db_right) { 448 foreach ($parent_groupids[$db_right['id']] as $groupid) { 449 $rights[] = [ 450 'groupid' => $db_right['groupid'], 451 'permission' => $db_right['permission'], 452 'id' => $groupid 453 ]; 454 } 455 } 456 457 DB::insertBatch('rights', $rights); 458 } 459 } 460 } 461 462 /** 463 * Inherit tag filters from parent host groups. 464 * 465 * @param array $groups 466 * @param string $groups[]['groupid'] 467 * @param string $groups[]['name'] 468 */ 469 private function inheritTagFilters(array $groups) { 470 $parent_names = []; 471 472 foreach ($groups as $group) { 473 $name = $group['name']; 474 475 while (($pos = strrpos($name, '/')) !== false) { 476 $name = substr($name, 0, $pos); 477 $parent_names[$name][] = $group['groupid']; 478 } 479 } 480 481 if ($parent_names) { 482 $options = [ 483 'output' => ['groupid', 'name'], 484 'filter' => ['name' => array_keys($parent_names)] 485 ]; 486 $result = DBselect(DB::makeSql('hstgrp', $options)); 487 488 $db_parent_groups = []; 489 490 while ($row = DBfetch($result)) { 491 $db_parent_groups[$row['name']] = $row['groupid']; 492 } 493 494 $parent_groupids = []; 495 496 foreach ($groups as $group) { 497 $name = $group['name']; 498 499 while (($pos = strrpos($name, '/')) !== false) { 500 $name = substr($name, 0, $pos); 501 502 if (array_key_exists($name, $db_parent_groups)) { 503 $parent_groupids[$db_parent_groups[$name]][] = $group['groupid']; 504 break; 505 } 506 } 507 } 508 509 if ($parent_groupids) { 510 $db_tag_filters = DB::select('tag_filter', [ 511 'output' => ['usrgrpid', 'groupid', 'tag', 'value'], 512 'filter' => ['groupid' => array_keys($parent_groupids)] 513 ]); 514 515 $tag_filters = []; 516 517 foreach ($db_tag_filters as $db_tag_filter) { 518 foreach ($parent_groupids[$db_tag_filter['groupid']] as $groupid) { 519 $tag_filters[] = [ 520 'usrgrpid' => $db_tag_filter['usrgrpid'], 521 'groupid' => $groupid, 522 'tag' => $db_tag_filter['tag'], 523 'value' => $db_tag_filter['value'] 524 ]; 525 } 526 } 527 528 DB::insertBatch('tag_filter', $tag_filters); 529 } 530 } 531 } 532 533 /** 534 * @param array $groups 535 * 536 * @return array 537 */ 538 public function create(array $groups) { 539 $this->validateCreate($groups); 540 541 $groupids = DB::insertBatch('hstgrp', $groups); 542 543 foreach ($groups as $index => &$group) { 544 $group['groupid'] = $groupids[$index]; 545 } 546 unset($group); 547 548 $this->inheritRights($groups); 549 $this->inheritTagFilters($groups); 550 551 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_HOST_GROUP, $groups); 552 553 return ['groupids' => $groupids]; 554 } 555 556 /** 557 * @param array $groups 558 * 559 * @return array 560 */ 561 public function update(array $groups) { 562 $this->validateUpdate($groups, $db_groups); 563 564 $upd_groups = []; 565 566 foreach ($groups as $group) { 567 $db_group = $db_groups[$group['groupid']]; 568 569 if (array_key_exists('name', $group) && $group['name'] !== $db_group['name']) { 570 $upd_groups[] = [ 571 'values' => ['name' => $group['name']], 572 'where' => ['groupid' => $group['groupid']] 573 ]; 574 } 575 } 576 577 DB::update('hstgrp', $upd_groups); 578 579 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST_GROUP, $groups, $db_groups); 580 581 return ['groupids' => zbx_objectValues($groups, 'groupid')]; 582 } 583 584 /** 585 * @param array $groupids 586 * @param bool $nopermissions 587 * 588 * @throws APIException if the input is invalid. 589 * 590 * @return array 591 */ 592 public function delete(array $groupids, $nopermissions = false) { 593 $this->validateDelete($groupids, $db_groups, $nopermissions); 594 595 // delete sysmap element 596 if (!empty($groupids)) { 597 DB::delete('sysmaps_elements', ['elementtype' => SYSMAP_ELEMENT_TYPE_HOST_GROUP, 'elementid' => $groupids]); 598 } 599 600 // disable actions 601 // actions from conditions 602 $actionids = []; 603 $db_actions = DBselect( 604 'SELECT DISTINCT c.actionid'. 605 ' FROM conditions c'. 606 ' WHERE c.conditiontype='.CONDITION_TYPE_HOST_GROUP. 607 ' AND '.dbConditionString('c.value', $groupids) 608 ); 609 while ($db_action = DBfetch($db_actions)) { 610 $actionids[$db_action['actionid']] = $db_action['actionid']; 611 } 612 613 // actions from operations 614 $db_actions = DBselect( 615 'SELECT o.actionid'. 616 ' FROM operations o,opgroup og'. 617 ' WHERE o.operationid=og.operationid AND '.dbConditionInt('og.groupid', $groupids). 618 ' UNION'. 619 ' SELECT o.actionid'. 620 ' FROM operations o,opcommand_grp ocg'. 621 ' WHERE o.operationid=ocg.operationid AND '.dbConditionInt('ocg.groupid', $groupids) 622 ); 623 while ($db_action = DBfetch($db_actions)) { 624 $actionids[$db_action['actionid']] = $db_action['actionid']; 625 } 626 627 if (!empty($actionids)) { 628 $update = []; 629 $update[] = [ 630 'values' => ['status' => ACTION_STATUS_DISABLED], 631 'where' => ['actionid' => $actionids] 632 ]; 633 DB::update('actions', $update); 634 } 635 636 // delete action conditions 637 DB::delete('conditions', [ 638 'conditiontype' => CONDITION_TYPE_HOST_GROUP, 639 'value' => $groupids 640 ]); 641 642 // delete action operation groups 643 $operationids = []; 644 $db_operations = DBselect( 645 'SELECT DISTINCT og.operationid'. 646 ' FROM opgroup og'. 647 ' WHERE '.dbConditionInt('og.groupid', $groupids) 648 ); 649 while ($db_operation = DBfetch($db_operations)) { 650 $operationids[$db_operation['operationid']] = $db_operation['operationid']; 651 } 652 DB::delete('opgroup', [ 653 'groupid' => $groupids 654 ]); 655 656 // delete action operation commands 657 $db_operations = DBselect( 658 'SELECT DISTINCT ocg.operationid'. 659 ' FROM opcommand_grp ocg'. 660 ' WHERE '.dbConditionInt('ocg.groupid', $groupids) 661 ); 662 while ($db_operation = DBfetch($db_operations)) { 663 $operationids[$db_operation['operationid']] = $db_operation['operationid']; 664 } 665 DB::delete('opcommand_grp', [ 666 'groupid' => $groupids 667 ]); 668 669 // delete empty operations 670 $del_operationids = []; 671 $db_operations = DBselect( 672 'SELECT DISTINCT o.operationid'. 673 ' FROM operations o'. 674 ' WHERE '.dbConditionInt('o.operationid', $operationids). 675 ' AND NOT EXISTS (SELECT NULL FROM opgroup og WHERE o.operationid=og.operationid)'. 676 ' AND NOT EXISTS (SELECT NULL FROM opcommand_grp ocg WHERE o.operationid=ocg.operationid)' 677 ); 678 while ($db_operation = DBfetch($db_operations)) { 679 $del_operationids[$db_operation['operationid']] = $db_operation['operationid']; 680 } 681 682 DB::delete('operations', ['operationid' => $del_operationids]); 683 684 DB::delete('hstgrp', ['groupid' => $groupids]); 685 686 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_HOST_GROUP, $db_groups); 687 688 return ['groupids' => $groupids]; 689 } 690 691 /** 692 * Check for duplicated host groups. 693 * 694 * @param array $names 695 * 696 * @throws APIException if host group already exists. 697 */ 698 private function checkDuplicates(array $names) { 699 $db_groups = DB::select('hstgrp', [ 700 'output' => ['name'], 701 'filter' => ['name' => $names], 702 'limit' => 1 703 ]); 704 705 if ($db_groups) { 706 self::exception(ZBX_API_ERROR_PARAMETERS, 707 _s('Host group "%1$s" already exists.', $db_groups[0]['name']) 708 ); 709 } 710 } 711 712 /** 713 * @param array $groups 714 * 715 * @throws APIException if the input is invalid. 716 */ 717 protected function validateCreate(array &$groups) { 718 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 719 self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only Super Admins can create host groups.')); 720 } 721 722 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['name']], 'fields' => [ 723 'uuid' => ['type' => API_UUID], 724 'name' => ['type' => API_HG_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('hstgrp', 'name')] 725 ]]; 726 if (!CApiInputValidator::validate($api_input_rules, $groups, '/', $error)) { 727 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 728 } 729 730 $this->checkDuplicates(zbx_objectValues($groups, 'name')); 731 $this->checkAndAddUuid($groups); 732 } 733 734 /** 735 * Check that new UUIDs are not already used and generate UUIDs where missing. 736 * 737 * @param array $groups_to_create 738 * 739 * @throws APIException 740 */ 741 protected function checkAndAddUuid(array &$groups_to_create): void { 742 foreach ($groups_to_create as &$group) { 743 if (!array_key_exists('uuid', $group)) { 744 $group['uuid'] = generateUuidV4(); 745 } 746 } 747 unset($group); 748 749 $db_uuid = DB::select('hstgrp', [ 750 'output' => ['uuid'], 751 'filter' => ['uuid' => array_column($groups_to_create, 'uuid')], 752 'limit' => 1 753 ]); 754 755 if ($db_uuid) { 756 self::exception(ZBX_API_ERROR_PARAMETERS, 757 _s('Entry with UUID "%1$s" already exists.', $db_uuid[0]['uuid']) 758 ); 759 } 760 } 761 762 /** 763 * Validates if groups can be deleted. 764 * 765 * @param array $groupids 766 * @param array $db_groups 767 * @param bool $nopermissions 768 * 769 * @throws APIException if the input is invalid. 770 */ 771 private function validateDelete(array $groupids, array &$db_groups = null, $nopermissions) { 772 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 773 if (!CApiInputValidator::validate($api_input_rules, $groupids, '/', $error)) { 774 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 775 } 776 777 $db_groups = $this->get([ 778 'output' => ['groupid', 'name', 'internal'], 779 'groupids' => $groupids, 780 'editable' => true, 781 'selectHosts' => ['hostid', 'host'], 782 'selectTemplates' => ['templateid', 'host'], 783 'preservekeys' => true, 784 'nopermissions' => $nopermissions 785 ]); 786 787 foreach ($groupids as $groupid) { 788 if (!array_key_exists($groupid, $db_groups)) { 789 self::exception(ZBX_API_ERROR_PERMISSIONS, 790 _('No permissions to referred object or it does not exist!') 791 ); 792 } 793 if ($db_groups[$groupid]['internal'] == ZBX_INTERNAL_GROUP) { 794 self::exception(ZBX_API_ERROR_PARAMETERS, 795 _s('Host group "%1$s" is internal and can not be deleted.', $db_groups[$groupid]['name']) 796 ); 797 } 798 } 799 800 // check if a group is used in a group prototype 801 $group_prototype = DBFetch(DBselect( 802 'SELECT groupid'. 803 ' FROM group_prototype gp'. 804 ' WHERE '.dbConditionInt('groupid', $groupids), 805 1 806 )); 807 if ($group_prototype) { 808 self::exception(ZBX_API_ERROR_PARAMETERS, 809 _s('Group "%1$s" cannot be deleted, because it is used by a host prototype.', 810 $db_groups[$group_prototype['groupid']]['name'] 811 ) 812 ); 813 } 814 815 $hosts_to_unlink = []; 816 $templates_to_unlink = []; 817 818 foreach ($db_groups as $db_group) { 819 foreach ($db_group['hosts'] as $host) { 820 $hosts_to_unlink[] = $host; 821 } 822 823 foreach ($db_group['templates'] as $template) { 824 $templates_to_unlink[] = $template; 825 } 826 } 827 828 $this->verifyHostsAndTemplatesAreUnlinkable($hosts_to_unlink, $templates_to_unlink, $groupids); 829 830 $db_scripts = DB::select('scripts', [ 831 'output' => ['groupid'], 832 'filter' => ['groupid' => $groupids], 833 'limit' => 1 834 ]); 835 836 if ($db_scripts) { 837 self::exception(ZBX_API_ERROR_PARAMETERS, 838 _s('Host group "%1$s" cannot be deleted, because it is used in a global script.', 839 $db_groups[$db_scripts[0]['groupid']]['name'] 840 ) 841 ); 842 } 843 844 $corr_condition_group = DBFetch(DBselect( 845 'SELECT cg.groupid'. 846 ' FROM corr_condition_group cg'. 847 ' WHERE '.dbConditionInt('cg.groupid', $groupids), 848 1 849 )); 850 851 if ($corr_condition_group) { 852 self::exception(ZBX_API_ERROR_PARAMETERS, 853 _s('Group "%1$s" cannot be deleted, because it is used in a correlation condition.', 854 $db_groups[$corr_condition_group['groupid']]['name'] 855 ) 856 ); 857 } 858 859 $this->validateDeleteCheckMaintenances($groupids); 860 } 861 862 /** 863 * @param array $groups 864 * @param array $db_groups 865 * 866 * @throws APIException if the input is invalid. 867 */ 868 protected function validateUpdate(array &$groups, array &$db_groups = null) { 869 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['groupid'], ['name']], 'fields' => [ 870 'groupid' => ['type' => API_ID, 'flags' => API_REQUIRED], 871 'name' => ['type' => API_HG_NAME, 'length' => DB::getFieldLength('hstgrp', 'name')] 872 ]]; 873 if (!CApiInputValidator::validate($api_input_rules, $groups, '/', $error)) { 874 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 875 } 876 877 // permissions 878 $db_groups = $this->get([ 879 'output' => ['groupid', 'name', 'flags'], 880 'groupids' => zbx_objectValues($groups, 'groupid'), 881 'editable' => true, 882 'preservekeys' => true 883 ]); 884 885 $update_discovered_validator = new CUpdateDiscoveredValidator([ 886 'messageAllowed' => _('Cannot update a discovered host group.') 887 ]); 888 889 $names = []; 890 891 foreach ($groups as $group) { 892 if (!array_key_exists($group['groupid'], $db_groups)) { 893 self::exception(ZBX_API_ERROR_PERMISSIONS, 894 _('No permissions to referred object or it does not exist!') 895 ); 896 } 897 898 $db_group = $db_groups[$group['groupid']]; 899 900 $this->checkPartialValidator($group, $update_discovered_validator, $db_group); 901 902 if (array_key_exists('name', $group) && $group['name'] !== $db_group['name']) { 903 $names[] = $group['name']; 904 } 905 } 906 907 if ($names) { 908 $this->checkDuplicates($names); 909 } 910 } 911 912 /** 913 * Add hosts and templates to host groups. All given hosts and templates are added to all given host groups. 914 * 915 * @param array $data 916 * @param array $data['groups'] 917 * @param array $data['hosts'] 918 * @param array $data['templates'] 919 * 920 * @return array returns array of group IDs that hosts and templates have been added to 921 */ 922 public function massAdd(array $data) { 923 $data['groups'] = zbx_toArray($data['groups']); 924 $data['hosts'] = isset($data['hosts']) ? zbx_toArray($data['hosts']) : []; 925 $data['templates'] = isset($data['templates']) ? zbx_toArray($data['templates']) : []; 926 927 $this->validateMassAdd($data); 928 929 $groupIds = zbx_objectValues($data['groups'], 'groupid'); 930 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 931 $templateIds = zbx_objectValues($data['templates'], 'templateid'); 932 933 $objectIds = array_merge($hostIds, $templateIds); 934 $objectIds = array_keys(array_flip($objectIds)); 935 936 $linked = []; 937 $linkedDb = DBselect( 938 'SELECT hg.hostid,hg.groupid'. 939 ' FROM hosts_groups hg'. 940 ' WHERE '.dbConditionInt('hg.hostid', $objectIds). 941 ' AND '.dbConditionInt('hg.groupid', $groupIds) 942 ); 943 while ($pair = DBfetch($linkedDb)) { 944 $linked[$pair['groupid']][$pair['hostid']] = 1; 945 } 946 947 $insert = []; 948 foreach ($groupIds as $groupId) { 949 foreach ($objectIds as $objectId) { 950 if (isset($linked[$groupId][$objectId])) { 951 continue; 952 } 953 $insert[] = ['hostid' => $objectId, 'groupid' => $groupId]; 954 } 955 } 956 957 DB::insert('hosts_groups', $insert); 958 959 return ['groupids' => $groupIds]; 960 } 961 962 /** 963 * Remove hosts and templates from host groups. All given hosts and templates are removed from all given host groups. 964 * 965 * @param array $data 966 * @param array $data['groupids'] 967 * @param array $data['hostids'] 968 * @param array $data['templateids'] 969 * 970 * @return array returns array of group IDs that hosts and templates have been removed from 971 */ 972 public function massRemove(array $data) { 973 $data['groupids'] = zbx_toArray($data['groupids'], 'groupid'); 974 $data['hostids'] = isset($data['hostids']) ? zbx_toArray($data['hostids']) : []; 975 $data['templateids'] = isset($data['templateids']) ? zbx_toArray($data['templateids']) : []; 976 977 $this->validateMassRemove($data); 978 979 $objectIds = array_merge($data['hostids'], $data['templateids']); 980 $objectIds = array_keys(array_flip($objectIds)); 981 982 DB::delete('hosts_groups', [ 983 'hostid' => $objectIds, 984 'groupid' => $data['groupids'] 985 ]); 986 987 return ['groupids' => $data['groupids']]; 988 } 989 990 /** 991 * Update host groups with new hosts and templates. 992 * 993 * @param array $data 994 * @param array $data['groups'] 995 * @param array $data['hosts'] 996 * @param array $data['templates'] 997 * 998 * @return array returns array of group IDs that hosts and templates have been added to and 999 * removed from 1000 */ 1001 public function massUpdate(array $data) { 1002 if (!array_key_exists('groups', $data) || !is_array($data['groups'])) { 1003 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'groups')); 1004 } 1005 1006 $data['groups'] = zbx_toArray($data['groups']); 1007 $data['hosts'] = isset($data['hosts']) ? zbx_toArray($data['hosts']) : []; 1008 $data['templates'] = isset($data['templates']) ? zbx_toArray($data['templates']) : []; 1009 1010 $this->validateMassUpdate($data); 1011 1012 $groupIds = zbx_objectValues($data['groups'], 'groupid'); 1013 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 1014 $templateIds = zbx_objectValues($data['templates'], 'templateid'); 1015 1016 $objectIds = zbx_toHash(array_merge($hostIds, $templateIds)); 1017 1018 // get old records and skip discovered hosts 1019 $oldRecords = DBfetchArray(DBselect( 1020 'SELECT hg.hostid,hg.groupid,hg.hostgroupid'. 1021 ' FROM hosts_groups hg,hosts h'. 1022 ' WHERE '.dbConditionInt('hg.groupid', $groupIds). 1023 ' AND hg.hostid=h.hostid'. 1024 ' AND h.flags='.ZBX_FLAG_DISCOVERY_NORMAL 1025 )); 1026 1027 // calculate new records 1028 $replaceRecords = []; 1029 $newRecords = []; 1030 1031 foreach ($groupIds as $groupId) { 1032 $groupRecords = []; 1033 foreach ($oldRecords as $oldRecord) { 1034 if ($oldRecord['groupid'] == $groupId) { 1035 $groupRecords[] = $oldRecord; 1036 } 1037 } 1038 1039 // find records for replace 1040 foreach ($groupRecords as $groupRecord) { 1041 if (isset($objectIds[$groupRecord['hostid']])) { 1042 $replaceRecords[] = $groupRecord; 1043 } 1044 } 1045 1046 // find records for create 1047 $groupHostIds = zbx_toHash(zbx_objectValues($groupRecords, 'hostid')); 1048 1049 $newHostIds = array_diff($objectIds, $groupHostIds); 1050 foreach ($newHostIds as $newHostId) { 1051 $newRecords[] = [ 1052 'groupid' => $groupId, 1053 'hostid' => $newHostId 1054 ]; 1055 } 1056 } 1057 1058 DB::replace('hosts_groups', $oldRecords, array_merge($replaceRecords, $newRecords)); 1059 1060 return ['groupids' => $groupIds]; 1061 } 1062 1063 /** 1064 * Validate write permissions to host groups that are added to given hosts and templates. 1065 * 1066 * @param array $data 1067 * @param array $data['groups'] 1068 * @param array $data['hosts'] 1069 * @param array $data['templates'] 1070 * 1071 * @throws APIException if user has no write permissions to any of the given host groups 1072 */ 1073 protected function validateMassAdd(array $data) { 1074 $groupIds = zbx_objectValues($data['groups'], 'groupid'); 1075 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 1076 $templateIds = zbx_objectValues($data['templates'], 'templateid'); 1077 1078 $groupIdsToAdd = []; 1079 1080 if ($hostIds) { 1081 $dbHosts = API::Host()->get([ 1082 'output' => ['hostid'], 1083 'selectGroups' => ['groupid'], 1084 'hostids' => $hostIds, 1085 'editable' => true, 1086 'preservekeys' => true 1087 ]); 1088 1089 $this->validateHostsPermissions($hostIds, $dbHosts); 1090 1091 $this->checkValidator($hostIds, new CHostNormalValidator([ 1092 'message' => _('Cannot update groups for discovered host "%1$s".') 1093 ])); 1094 1095 foreach ($dbHosts as $dbHost) { 1096 $oldGroupIds = zbx_objectValues($dbHost['groups'], 'groupid'); 1097 1098 foreach (array_diff($groupIds, $oldGroupIds) as $groupId) { 1099 $groupIdsToAdd[$groupId] = $groupId; 1100 } 1101 } 1102 } 1103 1104 if ($templateIds) { 1105 $dbTemplates = API::Template()->get([ 1106 'output' => ['templateid'], 1107 'selectGroups' => ['groupid'], 1108 'templateids' => $templateIds, 1109 'editable' => true, 1110 'preservekeys' => true 1111 ]); 1112 1113 $this->validateHostsPermissions($templateIds, $dbTemplates); 1114 1115 foreach ($dbTemplates as $dbTemplate) { 1116 $oldGroupIds = zbx_objectValues($dbTemplate['groups'], 'groupid'); 1117 1118 foreach (array_diff($groupIds, $oldGroupIds) as $groupId) { 1119 $groupIdsToAdd[$groupId] = $groupId; 1120 } 1121 } 1122 } 1123 1124 if ($groupIdsToAdd) { 1125 $count = $this->get([ 1126 'countOutput' => true, 1127 'groupids' => $groupIdsToAdd, 1128 'editable' => true 1129 ]); 1130 1131 if ($count != count($groupIdsToAdd)) { 1132 self::exception(ZBX_API_ERROR_PERMISSIONS, 1133 _('No permissions to referred object or it does not exist!') 1134 ); 1135 } 1136 } 1137 } 1138 1139 /** 1140 * Validate write permissions to host groups that are added and removed from given hosts and templates. Also check 1141 * if host and template has at least one host group left when removing host groups. 1142 * 1143 * @param array $data 1144 * @param array $data['groups'] 1145 * @param array $data['hosts'] 1146 * @param array $data['templates'] 1147 * 1148 * @throws APIException if user has no write permissions to any of the given host groups or one of the hosts and 1149 * templates is left without a host group 1150 */ 1151 protected function validateMassUpdate(array $data) { 1152 $groupIds = zbx_objectValues($data['groups'], 'groupid'); 1153 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 1154 $templateIds = zbx_objectValues($data['templates'], 'templateid'); 1155 1156 $dbGroups = $this->get([ 1157 'output' => ['groupid'], 1158 'groupids' => $groupIds, 1159 'selectHosts' => ['hostid', 'host'], 1160 'selectTemplates' => ['templateid', 'host'] 1161 ]); 1162 1163 if (!$dbGroups) { 1164 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 1165 } 1166 1167 // Collect group IDs that will added to given hosts and templates. 1168 $groupIdsToAdd = []; 1169 1170 // Collect group IDs that will removed from given hosts and templates. 1171 $groupIdsToRemove = []; 1172 1173 /* 1174 * When given hosts or templates belong to other groups and those group IDs are not passed in parameters, 1175 * those groups will be removed from given hosts and templates. Collect those host and template IDs 1176 * from groups that will be removed. 1177 */ 1178 $objectIds = []; 1179 1180 /* 1181 * New or existing hosts have been passed in parameters. First check write permissions to hosts 1182 * and if hosts are not discovered. Then check if groups should be added and/or removed from given hosts. 1183 */ 1184 if ($hostIds) { 1185 $dbHosts = API::Host()->get([ 1186 'output' => ['hostid'], 1187 'selectGroups' => ['groupid'], 1188 'hostids' => $hostIds, 1189 'editable' => true, 1190 'preservekeys' => true 1191 ]); 1192 1193 $this->validateHostsPermissions($hostIds, $dbHosts); 1194 1195 $this->checkValidator($hostIds, new CHostNormalValidator([ 1196 'message' => _('Cannot update groups for discovered host "%1$s".') 1197 ])); 1198 1199 foreach ($dbHosts as $dbHost) { 1200 $oldGroupIds = zbx_objectValues($dbHost['groups'], 'groupid'); 1201 1202 // Validate groups that are added for current host. 1203 foreach (array_diff($groupIds, $oldGroupIds) as $groupId) { 1204 $groupIdsToAdd[$groupId] = $groupId; 1205 } 1206 1207 // Validate groups that are removed from current host. 1208 foreach (array_diff($oldGroupIds, $groupIds) as $groupId) { 1209 $groupIdsToRemove[$groupId] = $groupId; 1210 } 1211 1212 if ($groupIdsToRemove) { 1213 $objectIds[] = $dbHost['hostid']; 1214 } 1215 } 1216 } 1217 1218 /* 1219 * New or existing templates have been passed in parameters. First check write permissions to templates. 1220 * Then check if groups should be added and/or removed from given templates. 1221 */ 1222 if ($templateIds) { 1223 $dbTemplates = API::Template()->get([ 1224 'output' => ['templateid'], 1225 'selectGroups' => ['groupid'], 1226 'templateids' => $templateIds, 1227 'editable' => true, 1228 'preservekeys' => true 1229 ]); 1230 1231 $this->validateHostsPermissions($templateIds, $dbTemplates); 1232 1233 foreach ($dbTemplates as $dbTemplate) { 1234 $oldGroupIds = zbx_objectValues($dbTemplate['groups'], 'groupid'); 1235 1236 // Validate groups that are added for current template. 1237 foreach (array_diff($groupIds, $oldGroupIds) as $groupId) { 1238 $groupIdsToAdd[$groupId] = $groupId; 1239 } 1240 1241 // Validate groups that are removed from current template. 1242 foreach (array_diff($oldGroupIds, $groupIds) as $groupId) { 1243 $groupIdsToRemove[$groupId] = $groupId; 1244 } 1245 1246 if ($groupIdsToRemove) { 1247 $objectIds[] = $dbTemplate['templateid']; 1248 } 1249 } 1250 } 1251 1252 // Continue to check new, existing or removable groups for given hosts and templates. 1253 $groupIdsToUpdate = $groupIdsToAdd + $groupIdsToRemove; 1254 1255 // Validate write permissions only to changed (added/removed) groups for given hosts and templates. 1256 if ($groupIdsToUpdate) { 1257 $count = $this->get([ 1258 'countOutput' => true, 1259 'groupids' => $groupIdsToUpdate, 1260 'editable' => true 1261 ]); 1262 1263 if ($count != count($groupIdsToUpdate)) { 1264 self::exception(ZBX_API_ERROR_PERMISSIONS, 1265 _('No permissions to referred object or it does not exist!') 1266 ); 1267 } 1268 } 1269 1270 // Check if groups can be removed from given hosts and templates. Only check if no groups are added. 1271 if (!$groupIdsToAdd && $groupIdsToRemove) { 1272 $unlinkableObjectIds = getUnlinkableHostIds($groupIdsToRemove, $objectIds); 1273 1274 if (count($objectIds) != count($unlinkableObjectIds)) { 1275 self::exception(ZBX_API_ERROR_PARAMETERS, _('One of the objects is left without a host group.')); 1276 } 1277 } 1278 1279 $hosts_to_unlink = []; 1280 $templates_to_unlink = []; 1281 $hostIds = array_flip($hostIds); 1282 $templateIds = array_flip($templateIds); 1283 1284 foreach ($dbGroups as $group) { 1285 foreach ($group['hosts'] as $host) { 1286 if (!array_key_exists($host['hostid'], $hostIds)) { 1287 $hosts_to_unlink[] = $host; 1288 } 1289 } 1290 1291 foreach ($group['templates'] as $template) { 1292 if (!array_key_exists($template['templateid'], $templateIds)) { 1293 $templates_to_unlink[] = $template; 1294 } 1295 } 1296 } 1297 1298 $this->verifyHostsAndTemplatesAreUnlinkable($hosts_to_unlink, $templates_to_unlink, $groupIds); 1299 } 1300 1301 /** 1302 * Validate write permissions to host groups that are removed from given hosts and templates. Also check 1303 * if host and template has at least one host group left. 1304 * 1305 * @param array $data 1306 * @param array $data['groupids'] 1307 * @param array $data['hostids'] 1308 * @param array $data['templateids'] 1309 * 1310 * @throws APIException if user has no write permissions to any of the given host groups or one of the hosts and 1311 * templates is left without a host group 1312 */ 1313 protected function validateMassRemove(array $data) { 1314 $groupIdsToRemove = []; 1315 $hostIds = isset($data['hostids']) ? $data['hostids'] : []; 1316 $templateIds = isset($data['templateids']) ? $data['templateids'] : []; 1317 $hosts_to_unlink = []; 1318 $templates_to_unlink = []; 1319 1320 if ($hostIds) { 1321 $dbHosts = API::Host()->get([ 1322 'output' => ['hostid', 'host'], 1323 'selectGroups' => ['groupid'], 1324 'hostids' => $hostIds, 1325 'editable' => true, 1326 'preservekeys' => true 1327 ]); 1328 1329 $this->validateHostsPermissions($hostIds, $dbHosts); 1330 1331 $this->checkValidator($hostIds, new CHostNormalValidator([ 1332 'message' => _('Cannot update groups for discovered host "%1$s".') 1333 ])); 1334 1335 foreach ($dbHosts as $dbHost) { 1336 $oldGroupIds = zbx_objectValues($dbHost['groups'], 'groupid'); 1337 1338 // check if host belongs to the removable host group 1339 $hostGroupIdsToRemove = array_intersect($data['groupids'], $oldGroupIds); 1340 1341 if ($hostGroupIdsToRemove) { 1342 $hosts_to_unlink[] = $dbHost; 1343 1344 foreach ($hostGroupIdsToRemove as $groupId) { 1345 $groupIdsToRemove[$groupId] = $groupId; 1346 } 1347 } 1348 } 1349 } 1350 1351 if ($templateIds) { 1352 $dbTemplates = API::Template()->get([ 1353 'output' => ['templateid', 'host'], 1354 'selectGroups' => ['groupid'], 1355 'templateids' => $templateIds, 1356 'editable' => true, 1357 'preservekeys' => true 1358 ]); 1359 1360 $this->validateHostsPermissions($templateIds, $dbTemplates); 1361 1362 foreach ($dbTemplates as $dbTemplate) { 1363 $oldGroupIds = zbx_objectValues($dbTemplate['groups'], 'groupid'); 1364 1365 // check if template belongs to the removable host group 1366 $templateGroupIdsToRemove = array_intersect($data['groupids'], $oldGroupIds); 1367 1368 if ($templateGroupIdsToRemove) { 1369 $templates_to_unlink[] = $dbTemplate; 1370 1371 foreach ($templateGroupIdsToRemove as $groupId) { 1372 $groupIdsToRemove[$groupId] = $groupId; 1373 } 1374 } 1375 } 1376 } 1377 1378 if ($groupIdsToRemove) { 1379 $count = $this->get([ 1380 'countOutput' => true, 1381 'groupids' => $groupIdsToRemove, 1382 'editable' => true 1383 ]); 1384 1385 if ($count != count($groupIdsToRemove)) { 1386 self::exception(ZBX_API_ERROR_PERMISSIONS, 1387 _('No permissions to referred object or it does not exist!') 1388 ); 1389 } 1390 } 1391 1392 $this->verifyHostsAndTemplatesAreUnlinkable($hosts_to_unlink, $templates_to_unlink, $groupIdsToRemove); 1393 } 1394 1395 /** 1396 * Validate write permissions to hosts or templates by given host or template IDs. 1397 * 1398 * @param array $hostIds array of host IDs or template IDs 1399 * @param array $dbHosts array of allowed hosts or templates 1400 * 1401 * @throws APIException if user has no write permissions to one of the hosts or templates 1402 */ 1403 protected function validateHostsPermissions(array $hostIds, array $dbHosts) { 1404 foreach ($hostIds as $hostId) { 1405 if (!isset($dbHosts[$hostId])) { 1406 self::exception(ZBX_API_ERROR_PERMISSIONS, 1407 _('No permissions to referred object or it does not exist!') 1408 ); 1409 } 1410 } 1411 } 1412 1413 protected function addRelatedObjects(array $options, array $result) { 1414 $result = parent::addRelatedObjects($options, $result); 1415 1416 $groupIds = array_keys($result); 1417 sort($groupIds); 1418 1419 // adding hosts 1420 if ($options['selectHosts'] !== null) { 1421 if ($options['selectHosts'] !== API_OUTPUT_COUNT) { 1422 $hosts = []; 1423 $relationMap = $this->createRelationMap($result, 'groupid', 'hostid', 'hosts_groups'); 1424 $related_ids = $relationMap->getRelatedIds(); 1425 1426 if ($related_ids) { 1427 $hosts = API::Host()->get([ 1428 'output' => $options['selectHosts'], 1429 'hostids' => $related_ids, 1430 'preservekeys' => true 1431 ]); 1432 if (!is_null($options['limitSelects'])) { 1433 order_result($hosts, 'host'); 1434 } 1435 } 1436 1437 $result = $relationMap->mapMany($result, $hosts, 'hosts', $options['limitSelects']); 1438 } 1439 else { 1440 $hosts = API::Host()->get([ 1441 'groupids' => $groupIds, 1442 'countOutput' => true, 1443 'groupCount' => true 1444 ]); 1445 $hosts = zbx_toHash($hosts, 'groupid'); 1446 foreach ($result as $groupid => $group) { 1447 $result[$groupid]['hosts'] = array_key_exists($groupid, $hosts) 1448 ? $hosts[$groupid]['rowscount'] 1449 : '0'; 1450 } 1451 } 1452 } 1453 1454 // adding templates 1455 if ($options['selectTemplates'] !== null) { 1456 if ($options['selectTemplates'] !== API_OUTPUT_COUNT) { 1457 $hosts = []; 1458 $relationMap = $this->createRelationMap($result, 'groupid', 'hostid', 'hosts_groups'); 1459 $related_ids = $relationMap->getRelatedIds(); 1460 1461 if ($related_ids) { 1462 $hosts = API::Template()->get([ 1463 'output' => $options['selectTemplates'], 1464 'templateids' => $related_ids, 1465 'preservekeys' => true 1466 ]); 1467 if (!is_null($options['limitSelects'])) { 1468 order_result($hosts, 'host'); 1469 } 1470 } 1471 1472 $result = $relationMap->mapMany($result, $hosts, 'templates', $options['limitSelects']); 1473 } 1474 else { 1475 $hosts = API::Template()->get([ 1476 'groupids' => $groupIds, 1477 'countOutput' => true, 1478 'groupCount' => true 1479 ]); 1480 $hosts = zbx_toHash($hosts, 'groupid'); 1481 foreach ($result as $groupid => $group) { 1482 $result[$groupid]['templates'] = array_key_exists($groupid, $hosts) 1483 ? $hosts[$groupid]['rowscount'] 1484 : '0'; 1485 } 1486 } 1487 } 1488 1489 // adding discovery rule 1490 if ($options['selectDiscoveryRule'] !== null && $options['selectDiscoveryRule'] != API_OUTPUT_COUNT) { 1491 // discovered items 1492 $discoveryRules = DBFetchArray(DBselect( 1493 'SELECT gd.groupid,hd.parent_itemid'. 1494 ' FROM group_discovery gd,group_prototype gp,host_discovery hd'. 1495 ' WHERE '.dbConditionInt('gd.groupid', $groupIds). 1496 ' AND gd.parent_group_prototypeid=gp.group_prototypeid'. 1497 ' AND gp.hostid=hd.hostid' 1498 )); 1499 $relationMap = $this->createRelationMap($discoveryRules, 'groupid', 'parent_itemid'); 1500 1501 $discoveryRules = API::DiscoveryRule()->get([ 1502 'output' => $options['selectDiscoveryRule'], 1503 'itemids' => $relationMap->getRelatedIds(), 1504 'preservekeys' => true 1505 ]); 1506 $result = $relationMap->mapOne($result, $discoveryRules, 'discoveryRule'); 1507 } 1508 1509 // adding group discovery 1510 if ($options['selectGroupDiscovery'] !== null) { 1511 $groupDiscoveries = API::getApiService()->select('group_discovery', [ 1512 'output' => $this->outputExtend($options['selectGroupDiscovery'], ['groupid']), 1513 'filter' => ['groupid' => $groupIds], 1514 'preservekeys' => true 1515 ]); 1516 $relationMap = $this->createRelationMap($groupDiscoveries, 'groupid', 'groupid'); 1517 1518 $groupDiscoveries = $this->unsetExtraFields($groupDiscoveries, ['groupid'], 1519 $options['selectGroupDiscovery'] 1520 ); 1521 $result = $relationMap->mapOne($result, $groupDiscoveries, 'groupDiscovery'); 1522 } 1523 1524 return $result; 1525 } 1526 1527 /** 1528 * Verify that hosts and templates are unlinkable from groups. 1529 * 1530 * @param array $hosts 1531 * @param integer $hosts[]['hostid'] 1532 * @param string $hosts[]['host'] 1533 * @param array $templates 1534 * @param integer $templates[]['templateid'] 1535 * @param string $templates[]['host'] 1536 * @param array $groupids 1537 */ 1538 protected function verifyHostsAndTemplatesAreUnlinkable(array $hosts, array $templates, array $groupids) { 1539 $objectids = []; 1540 $host_names = []; 1541 $template_names = []; 1542 1543 foreach ($hosts as $host) { 1544 $objectids[] = $host['hostid']; 1545 $host_names[$host['hostid']] = $host['host']; 1546 } 1547 1548 foreach ($templates as $template) { 1549 $objectids[] = $template['templateid']; 1550 $template_names[$template['templateid']] = $template['host']; 1551 } 1552 1553 if ($objectids && $groupids) { 1554 $not_unlinkable_objectids = array_diff($objectids, getUnlinkableHostIds($groupids, $objectids)); 1555 1556 if ($not_unlinkable_objectids) { 1557 $objectid = reset($not_unlinkable_objectids); 1558 1559 if (array_key_exists($objectid, $host_names)) { 1560 self::exception(ZBX_API_ERROR_PARAMETERS, 1561 _s('Host "%1$s" cannot be without host group.', $host_names[$objectid]) 1562 ); 1563 } 1564 1565 self::exception(ZBX_API_ERROR_PARAMETERS, 1566 _s('Template "%1$s" cannot be without host group.', $template_names[$objectid]) 1567 ); 1568 } 1569 } 1570 } 1571 1572 /** 1573 * Validates if host groups may be deleted, due to maintenance constrain. 1574 * 1575 * @throws APIException if a constrain failed 1576 * 1577 * @param array $groupids 1578 */ 1579 protected function validateDeleteCheckMaintenances(array $groupids) { 1580 $maintenance = DBfetch(DBselect( 1581 'SELECT m.name'. 1582 ' FROM maintenances m'. 1583 ' WHERE NOT EXISTS ('. 1584 'SELECT NULL'. 1585 ' FROM maintenances_groups mg'. 1586 ' WHERE m.maintenanceid=mg.maintenanceid'. 1587 ' AND '.dbConditionInt('mg.groupid', $groupids, true). 1588 ')'. 1589 ' AND NOT EXISTS ('. 1590 'SELECT NULL'. 1591 ' FROM maintenances_hosts mh'. 1592 ' WHERE m.maintenanceid=mh.maintenanceid'. 1593 ')' 1594 )); 1595 1596 if ($maintenance) { 1597 self::exception(ZBX_API_ERROR_PARAMETERS, _n( 1598 'Cannot delete host group because maintenance "%1$s" must contain at least one host or host group.', 1599 'Cannot delete selected host groups because maintenance "%1$s" must contain at least one host or host group.', 1600 $maintenance['name'], 1601 count($groupids) 1602 )); 1603 } 1604 } 1605} 1606