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 maps. 24 */ 25class CMap extends CMapElement { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_ZABBIX_USER, 'action' => CRoleHelper::ACTIONS_EDIT_MAPS], 30 'update' => ['min_user_type' => USER_TYPE_ZABBIX_USER, 'action' => CRoleHelper::ACTIONS_EDIT_MAPS], 31 'delete' => ['min_user_type' => USER_TYPE_ZABBIX_USER, 'action' => CRoleHelper::ACTIONS_EDIT_MAPS] 32 ]; 33 34 protected $tableName = 'sysmaps'; 35 protected $tableAlias = 's'; 36 protected $sortColumns = ['name', 'width', 'height']; 37 38 private $defOptions = [ 39 'sysmapids' => null, 40 'userids' => null, 41 'editable' => false, 42 'nopermissions' => null, 43 // filter 44 'filter' => null, 45 'search' => null, 46 'searchByAny' => null, 47 'startSearch' => false, 48 'excludeSearch' => false, 49 'searchWildcardsEnabled' => null, 50 // output 51 'output' => API_OUTPUT_EXTEND, 52 'selectSelements' => null, 53 'selectShapes' => null, 54 'selectLines' => null, 55 'selectLinks' => null, 56 'selectIconMap' => null, 57 'selectUrls' => null, 58 'selectUsers' => null, 59 'selectUserGroups' => null, 60 'countOutput' => false, 61 'expandUrls' => null, 62 'preservekeys' => false, 63 'sortfield' => '', 64 'sortorder' => '', 65 'limit' => null 66 ]; 67 68 /** 69 * Get map data. 70 * 71 * @param array $options 72 * @param array $options['sysmapids'] Map IDs. 73 * @param bool $options['output'] List of map parameters to return. 74 * @param array $options['selectSelements'] List of map element properties to return. 75 * @param array $options['selectShapes'] List of map shape properties to return. 76 * @param array $options['selectLines'] List of map line properties to return. 77 * @param array $options['selectLinks'] List of map link properties to return. 78 * @param array $options['selectIconMap'] List of map icon map properties to return. 79 * @param array $options['selectUrls'] List of map URL properties to return. 80 * @param array $options['selectUsers'] List of users that the map is shared with. 81 * @param array $options['selectUserGroups'] List of user groups that the map is shared with. 82 * @param bool $options['countOutput'] Return the count of records, instead of actual results. 83 * @param array $options['userids'] Map owner user IDs. 84 * @param bool $options['editable'] Return with read-write permission only. Ignored for 85 * SuperAdmins. 86 * @param bool $options['nopermissions'] Return requested maps even if user has no permissions to 87 * them. 88 * @param array $options['filter'] List of field and exactly matched value pairs by which maps 89 * need to be filtered. 90 * @param array $options['search'] List of field-value pairs by which maps need to be searched. 91 * @param array $options['expandUrls'] Adds global map URLs to the corresponding map elements and 92 * expands macros in all map element URLs. 93 * @param bool $options['searchByAny'] 94 * @param bool $options['startSearch'] 95 * @param bool $options['excludeSearch'] 96 * @param bool $options['searchWildcardsEnabled'] 97 * @param array $options['preservekeys'] Use IDs as keys in the resulting array. 98 * @param int $options['limit'] Limit selection. 99 * @param string $options['sortorder'] 100 * @param string $options['sortfield'] 101 * 102 * @return array|integer Requested map data as array or the count of retrieved objects, if the countOutput 103 * parameter has been used. 104 */ 105 public function get(array $options = []) { 106 $options = zbx_array_merge($this->defOptions, $options); 107 108 $limit = $options['limit']; 109 $options['limit'] = null; 110 111 if ($options['countOutput']) { 112 $count_output = true; 113 $options['output'] = ['sysmapid']; 114 $options['countOutput'] = false; 115 } 116 else { 117 $count_output = false; 118 } 119 120 $result = $this->getMaps($options); 121 122 if ($result && self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 123 $sysmapids = array_flip($this->checkPermissions(array_keys($result), (bool) $options['editable'])); 124 125 foreach ($result as $sysmapid => $foo) { 126 if (!array_key_exists($sysmapid, $sysmapids)) { 127 unset($result[$sysmapid]); 128 } 129 } 130 } 131 132 if ($count_output) { 133 return (string) count($result); 134 } 135 136 if ($limit !== null) { 137 $result = array_slice($result, 0, $limit, true); 138 } 139 140 if ($result) { 141 $result = $this->addRelatedObjects($options, $result); 142 } 143 144 // removing keys (hash -> array) 145 if (!$options['preservekeys']) { 146 $result = zbx_cleanHashes($result); 147 } 148 149 return $result; 150 } 151 152 /** 153 * Returns maps without checking permissions to the elements. 154 */ 155 private function getMaps(array $options) { 156 $sql_parts = [ 157 'select' => ['sysmaps' => 's.sysmapid'], 158 'from' => ['sysmaps' => 'sysmaps s'], 159 'where' => [], 160 'order' => [], 161 'limit' => null 162 ]; 163 164 // Editable + permission check. 165 if (self::$userData['type'] < USER_TYPE_ZABBIX_ADMIN && !$options['nopermissions']) { 166 $public_maps = ''; 167 168 if ($options['editable']) { 169 $permission = PERM_READ_WRITE; 170 } 171 else { 172 $permission = PERM_READ; 173 $public_maps = ' OR s.private='.PUBLIC_SHARING; 174 } 175 176 $user_groups = getUserGroupsByUserId(self::$userData['userid']); 177 178 $sql_parts['where'][] = '(EXISTS ('. 179 'SELECT NULL'. 180 ' FROM sysmap_user su'. 181 ' WHERE s.sysmapid=su.sysmapid'. 182 ' AND su.userid='.self::$userData['userid']. 183 ' AND su.permission>='.$permission. 184 ')'. 185 ' OR EXISTS ('. 186 'SELECT NULL'. 187 ' FROM sysmap_usrgrp sg'. 188 ' WHERE s.sysmapid=sg.sysmapid'. 189 ' AND '.dbConditionInt('sg.usrgrpid', $user_groups). 190 ' AND sg.permission>='.$permission. 191 ')'. 192 ' OR s.userid='.self::$userData['userid']. 193 $public_maps. 194 ')'; 195 } 196 197 // sysmapids 198 if ($options['sysmapids'] !== null) { 199 zbx_value2array($options['sysmapids']); 200 $sql_parts['where']['sysmapid'] = dbConditionInt('s.sysmapid', $options['sysmapids']); 201 } 202 203 // userids 204 if ($options['userids'] !== null) { 205 zbx_value2array($options['userids']); 206 207 $sql_parts['where'][] = dbConditionInt('s.userid', $options['userids']); 208 } 209 210 // search 211 if ($options['search'] !== null) { 212 zbx_db_search('sysmaps s', $options, $sql_parts); 213 } 214 215 // filter 216 if ($options['filter'] !== null) { 217 $this->dbFilter('sysmaps s', $options, $sql_parts); 218 } 219 220 // limit 221 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 222 $sql_parts['limit'] = $options['limit']; 223 } 224 225 $result = []; 226 227 $sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 228 $sql_parts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 229 $sysmaps = DBselect(self::createSelectQueryFromParts($sql_parts), $sql_parts['limit']); 230 231 while ($sysmap = DBfetch($sysmaps)) { 232 $result[$sysmap['sysmapid']] = $sysmap; 233 } 234 235 return $result; 236 } 237 238 /** 239 * Returns maps with selected permission level. 240 * 241 * @param array $sysmapids 242 * @param bool $editable 243 * 244 * @return array 245 */ 246 private function checkPermissions(array $sysmapids, $editable) { 247 $sysmaps_r = []; 248 foreach ($sysmapids as $sysmapid) { 249 $sysmaps_r[$sysmapid] = true; 250 } 251 252 $selement_maps = []; 253 254 // Populating the map tree $selement_maps and list of shared maps $sysmaps_r. 255 do { 256 $selements = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_MAP); 257 258 $sysmapids = []; 259 260 foreach ($selements as $sysmapid => $selement) { 261 if (!array_key_exists($sysmapid, $sysmaps_r)) { 262 $sysmapids[$sysmapid] = true; 263 } 264 } 265 266 $sysmapids = array_keys($sysmapids); 267 $selement_maps += $selements; 268 269 if ($sysmapids) { 270 $db_sysmaps = $this->getMaps([ 271 'output' => [], 272 'sysmapids' => $sysmapids, 273 'preservekeys' => true 274 ] + $this->defOptions); 275 276 foreach ($sysmapids as $i => $sysmapid) { 277 if (array_key_exists($sysmapid, $db_sysmaps)) { 278 $sysmaps_r[$sysmapid] = true; 279 } 280 else { 281 unset($sysmapids[$i]); 282 } 283 } 284 } 285 } 286 while ($sysmapids); 287 288 $sysmaps_rw = $editable ? $sysmaps_r : []; 289 290 foreach ($sysmaps_r as &$sysmap_r) { 291 $sysmap_r = ['permission' => PERM_NONE, 'has_elements' => false]; 292 } 293 unset($sysmap_r); 294 295 self::setHasElements($sysmaps_r, $selement_maps); 296 297 // Setting PERM_READ permission for maps with at least one image. 298 $selement_images = self::getSelements(array_keys($sysmaps_r), SYSMAP_ELEMENT_TYPE_IMAGE); 299 self::setMapPermissions($sysmaps_r, $selement_images, [0 => []], $selement_maps); 300 self::setHasElements($sysmaps_r, $selement_images); 301 302 $sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw); 303 304 // Check permissions to the host groups. 305 if ($sysmapids) { 306 $selement_groups = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_HOST_GROUP); 307 308 $db_groups = API::HostGroup()->get([ 309 'output' => [], 310 'groupids' => array_keys($selement_groups), 311 'preservekeys' => true 312 ]); 313 314 if ($editable) { 315 self::unsetMapsByElements($sysmaps_rw, $selement_groups, $db_groups); 316 } 317 self::setMapPermissions($sysmaps_r, $selement_groups, $db_groups, $selement_maps); 318 self::setHasElements($sysmaps_r, $selement_groups); 319 320 $sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw); 321 } 322 323 // Check permissions to the hosts. 324 if ($sysmapids) { 325 $selement_hosts = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_HOST); 326 327 $db_hosts = API::Host()->get([ 328 'output' => [], 329 'hostids' => array_keys($selement_hosts), 330 'preservekeys' => true 331 ]); 332 333 if ($editable) { 334 self::unsetMapsByElements($sysmaps_rw, $selement_hosts, $db_hosts); 335 } 336 self::setMapPermissions($sysmaps_r, $selement_hosts, $db_hosts, $selement_maps); 337 self::setHasElements($sysmaps_r, $selement_hosts); 338 339 $sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw); 340 } 341 342 // Check permissions to the triggers. 343 if ($sysmapids) { 344 $selement_triggers = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_TRIGGER); 345 $link_triggers = self::getLinkTriggers($sysmapids); 346 347 $db_triggers = API::Trigger()->get([ 348 'output' => [], 349 'triggerids' => array_keys($selement_triggers + $link_triggers), 350 'preservekeys' => true 351 ]); 352 353 if ($editable) { 354 self::unsetMapsByElements($sysmaps_rw, $selement_triggers, $db_triggers); 355 self::unsetMapsByElements($sysmaps_rw, $link_triggers, $db_triggers); 356 } 357 self::setMapPermissions($sysmaps_r, $selement_triggers, $db_triggers, $selement_maps); 358 self::setMapPermissions($sysmaps_r, $link_triggers, $db_triggers, $selement_maps); 359 self::setHasElements($sysmaps_r, $selement_triggers); 360 self::setHasElements($sysmaps_r, $link_triggers); 361 } 362 363 foreach ($sysmaps_r as $sysmapid => $sysmap_r) { 364 if (!$sysmap_r['has_elements']) { 365 self::setMapPermission($sysmaps_r, $selement_maps, $sysmapid); 366 } 367 } 368 369 foreach ($sysmaps_r as $sysmapid => $sysmap_r) { 370 if ($sysmap_r['permission'] == PERM_NONE) { 371 unset($sysmaps_r[$sysmapid]); 372 } 373 } 374 375 if ($editable) { 376 self::unsetMapsByTree($sysmaps_rw, $sysmaps_r, $selement_maps); 377 } 378 379 return array_keys($editable ? $sysmaps_rw : $sysmaps_r); 380 } 381 382 /** 383 * Returns map elements for selected maps. 384 */ 385 private static function getSelements(array $sysmapids, $elementtype) { 386 $selements = []; 387 388 switch ($elementtype) { 389 case SYSMAP_ELEMENT_TYPE_IMAGE: 390 $sql = 'SELECT se.sysmapid,0 AS elementid'. 391 ' FROM sysmaps_elements se'. 392 ' WHERE '.dbConditionInt('se.sysmapid', $sysmapids). 393 ' AND '.dbConditionInt('se.elementtype', [$elementtype]); 394 break; 395 396 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 397 case SYSMAP_ELEMENT_TYPE_HOST: 398 case SYSMAP_ELEMENT_TYPE_MAP: 399 $sql = 'SELECT se.sysmapid,se.elementid'. 400 ' FROM sysmaps_elements se'. 401 ' WHERE '.dbConditionInt('se.sysmapid', $sysmapids). 402 ' AND '.dbConditionInt('se.elementtype', [$elementtype]); 403 break; 404 405 case SYSMAP_ELEMENT_TYPE_TRIGGER: 406 $sql = 'SELECT se.sysmapid,st.triggerid AS elementid'. 407 ' FROM sysmaps_elements se,sysmap_element_trigger st'. 408 ' WHERE se.selementid=st.selementid'. 409 ' AND '.dbConditionInt('se.sysmapid', $sysmapids). 410 ' AND '.dbConditionInt('se.elementtype', [$elementtype]); 411 break; 412 } 413 $db_selements = DBSelect($sql); 414 415 while ($db_selement = DBfetch($db_selements)) { 416 $selements[$db_selement['elementid']][] = ['sysmapid' => $db_selement['sysmapid']]; 417 } 418 419 return $selements; 420 } 421 422 /** 423 * Returns map links for selected maps. 424 */ 425 private static function getLinkTriggers(array $sysmapids) { 426 $link_triggers = []; 427 428 $db_links = DBSelect( 429 'SELECT sl.sysmapid,slt.triggerid'. 430 ' FROM sysmaps_links sl,sysmaps_link_triggers slt'. 431 ' WHERE sl.linkid=slt.linkid'. 432 ' AND '.dbConditionInt('sl.sysmapid', $sysmapids) 433 ); 434 435 while ($db_link = DBfetch($db_links)) { 436 $link_triggers[$db_link['triggerid']][] = ['sysmapid' => $db_link['sysmapid']]; 437 } 438 439 return $link_triggers; 440 } 441 442 /** 443 * Removes all inaccessible maps by map tree. 444 * 445 * @param array $sysmaps_rw[<sysmapids>] The list of writable maps. 446 * @param array $sysmaps_r[<sysmapids>] The list of readable maps. 447 * @param array $selement_maps The map tree. 448 * @param array $selement_maps[<sysmapid>][]['sysmapid'] Parent map ID. 449 */ 450 private static function unsetMapsByTree(array &$sysmaps_rw, array $sysmaps_r, array $selement_maps) { 451 foreach ($selement_maps as $child_sysmapid => $selements) { 452 if (!array_key_exists($child_sysmapid, $sysmaps_r)) { 453 foreach ($selements as $selement) { 454 unset($sysmaps_rw[$selement['sysmapid']]); 455 } 456 } 457 } 458 } 459 460 /** 461 * Removes all inaccessible maps by inacessible elements. 462 * 463 * @param array $sysmaps_rw[<sysmapids>] The list of writable maps. 464 * @param array $elements The map elements. 465 * @param array $elements[<elementid>][]['sysmapid'] Map ID. 466 * @param array $db_elements The list of readable elements. 467 * @param array $db_elements[<elementid>] 468 */ 469 private static function unsetMapsByElements(array &$sysmaps_rw, array $elements, array $db_elements) { 470 foreach ($elements as $elementid => $selements) { 471 if (!array_key_exists($elementid, $db_elements)) { 472 foreach ($selements as $selement) { 473 unset($sysmaps_rw[$selement['sysmapid']]); 474 } 475 } 476 } 477 } 478 479 /** 480 * Set PERM_READ permission for map and all parent maps. 481 * 482 * @param array $sysmaps_r[<sysmapids>] The list of readable maps. 483 * @param array $selement_maps The map elements. 484 * @param array $selement_maps[<sysmapid>][]['sysmapid'] Map ID. 485 * @param string $sysmapid 486 */ 487 private static function setMapPermission(array &$sysmaps_r, array $selement_maps, $sysmapid) { 488 if (array_key_exists($sysmapid, $selement_maps)) { 489 foreach ($selement_maps[$sysmapid] as $selement) { 490 self::setMapPermission($sysmaps_r, $selement_maps, $selement['sysmapid']); 491 } 492 } 493 $sysmaps_r[$sysmapid]['permission'] = PERM_READ; 494 } 495 496 /** 497 * Setting PERM_READ permissions for maps with at least one available element. 498 * 499 * @param array $sysmaps_r[<sysmapids>] The list of readable maps. 500 * @param array $elements The map elements. 501 * @param array $elements[<elementid>][]['sysmapid'] Map ID. 502 * @param array $db_elements The list of readable elements. 503 * @param array $db_elements[<elementid>] 504 * @param array $selement_maps The map elements. 505 * @param array $selement_maps[<sysmapid>][]['sysmapid'] Map ID. 506 */ 507 private static function setMapPermissions(array &$sysmaps_r, array $elements, array $db_elements, 508 array $selement_maps) { 509 foreach ($elements as $elementid => $selements) { 510 if (array_key_exists($elementid, $db_elements)) { 511 foreach ($selements as $selement) { 512 self::setMapPermission($sysmaps_r, $selement_maps, $selement['sysmapid']); 513 } 514 } 515 } 516 } 517 518 /** 519 * Setting "has_elements" flag for maps. 520 * 521 * @param array $sysmaps_r[<sysmapids>] The list of readable maps. 522 * @param array $elements The map elements. 523 * @param array $elements[<elementid>] 524 */ 525 private static function setHasElements(array &$sysmaps_r, array $elements) { 526 foreach ($elements as $elementid => $selements) { 527 foreach ($selements as $selement) { 528 $sysmaps_r[$selement['sysmapid']]['has_elements'] = true; 529 } 530 } 531 } 532 533 /** 534 * Returns map ids which will be checked for permissions. 535 * 536 * @param array $sysmaps_r 537 * @param array $sysmaps_r[<sysmapid>]['permission'] 538 * @param array $sysmaps_rw 539 * @param array $sysmaps_rw[<sysmapid>] 540 */ 541 private static function getSysmapIds(array $sysmaps_r, array $sysmaps_rw) { 542 $sysmapids = $sysmaps_rw; 543 544 foreach ($sysmaps_r as $sysmapid => $sysmap) { 545 if ($sysmap['permission'] == PERM_NONE) { 546 $sysmapids[$sysmapid] = true; 547 } 548 } 549 550 return array_keys($sysmapids); 551 } 552 553 /** 554 * Validates the input parameters for the delete() method. 555 * 556 * @param array $sysmapids 557 * 558 * @throws APIException if the input is invalid. 559 */ 560 protected function validateDelete(array $sysmapids) { 561 if (!$sysmapids) { 562 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 563 } 564 565 $db_maps = $this->get([ 566 'output' => ['sysmapid'], 567 'sysmapids' => $sysmapids, 568 'editable' => true, 569 'preservekeys' => true 570 ]); 571 572 foreach ($sysmapids as $sysmapid) { 573 if (!array_key_exists($sysmapid, $db_maps)) { 574 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 575 } 576 } 577 } 578 579 /** 580 * Validate the input parameters for the create() method. 581 * 582 * @param array $maps maps data array 583 * 584 * @throws APIException if the input is invalid. 585 */ 586 protected function validateCreate(array $maps) { 587 if (!$maps) { 588 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 589 } 590 591 $user_data = self::$userData; 592 593 $map_db_fields = [ 594 'name' => null, 595 'width' => null, 596 'height' => null, 597 'urls' => [], 598 'selements' => [], 599 'links' => [] 600 ]; 601 602 // Validate mandatory fields and map name. 603 foreach ($maps as $map) { 604 if (!check_db_fields($map_db_fields, $map)) { 605 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect fields for sysmap.')); 606 } 607 } 608 609 // Check for duplicate names. 610 $duplicate = CArrayHelper::findDuplicate($maps, 'name'); 611 if ($duplicate) { 612 self::exception(ZBX_API_ERROR_PARAMETERS, 613 _s('Duplicate "name" value "%1$s" for map.', $duplicate['name']) 614 ); 615 } 616 617 // Check if map already exists. 618 $db_maps = $this->get([ 619 'output' => ['name'], 620 'filter' => ['name' => zbx_objectValues($maps, 'name')], 621 'nopermissions' => true, 622 'limit' => 1 623 ]); 624 625 if ($db_maps) { 626 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Map "%1$s" already exists.', $db_maps[0]['name'])); 627 } 628 629 $private_validator = new CLimitedSetValidator([ 630 'values' => [PUBLIC_SHARING, PRIVATE_SHARING] 631 ]); 632 633 $permission_validator = new CLimitedSetValidator([ 634 'values' => [PERM_READ, PERM_READ_WRITE] 635 ]); 636 637 $show_suppressed_types = [ZBX_PROBLEM_SUPPRESSED_FALSE, ZBX_PROBLEM_SUPPRESSED_TRUE]; 638 $show_suppressed_validator = new CLimitedSetValidator(['values' => $show_suppressed_types]); 639 640 $expandproblem_types = [SYSMAP_PROBLEMS_NUMBER, SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER_CRITICAL]; 641 $expandproblem_validator = new CLimitedSetValidator(['values' => $expandproblem_types]); 642 643 // Continue to check 2 more mandatory fields and other optional fields. 644 foreach ($maps as $map_index => $map) { 645 // Check mandatory fields "width" and "height". 646 if ($map['width'] > 65535 || $map['width'] < 1) { 647 self::exception(ZBX_API_ERROR_PARAMETERS, 648 _s('Incorrect "width" value for map "%1$s".', $map['name']) 649 ); 650 } 651 652 if ($map['height'] > 65535 || $map['height'] < 1) { 653 self::exception(ZBX_API_ERROR_PARAMETERS, 654 _s('Incorrect "height" value for map "%1$s".', $map['name']) 655 ); 656 } 657 658 // Check if owner can be set. 659 if (array_key_exists('userid', $map)) { 660 if ($map['userid'] === '' || $map['userid'] === null || $map['userid'] === false) { 661 self::exception(ZBX_API_ERROR_PARAMETERS, _('Map owner cannot be empty.')); 662 } 663 elseif ($map['userid'] != $user_data['userid'] && $user_data['type'] != USER_TYPE_SUPER_ADMIN 664 && $user_data['type'] != USER_TYPE_ZABBIX_ADMIN) { 665 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only administrators can set map owner.')); 666 } 667 } 668 669 // Check for invalid "private" values. 670 if (array_key_exists('private', $map)) { 671 if (!$private_validator->validate($map['private'])) { 672 self::exception(ZBX_API_ERROR_PARAMETERS, 673 _s('Incorrect "private" value "%1$s" for map "%2$s".', $map['private'], $map['name']) 674 ); 675 } 676 } 677 678 // Check for invalid "show_suppressed" values. 679 if (array_key_exists('show_suppressed', $map) 680 && !$show_suppressed_validator->validate($map['show_suppressed'])) { 681 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 682 'show_suppressed', _s('value must be one of %1$s', implode(', ', $show_suppressed_types)) 683 )); 684 } 685 686 if (array_key_exists('expandproblem', $map) && !$expandproblem_validator->validate($map['expandproblem'])) { 687 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'expandproblem', 688 _s('value must be one of %1$s', implode(', ', $expandproblem_types)) 689 )); 690 } 691 692 $userids = []; 693 694 // Map user shares. 695 if (array_key_exists('users', $map)) { 696 if (!is_array($map['users'])) { 697 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 698 } 699 700 $required_fields = ['userid', 'permission']; 701 702 foreach ($map['users'] as $share) { 703 // Check required parameters. 704 $missing_keys = array_diff($required_fields, array_keys($share)); 705 706 if ($missing_keys) { 707 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 708 'User sharing is missing parameters: %1$s for map "%2$s".', 709 implode(', ', $missing_keys), 710 $map['name'] 711 )); 712 } 713 else { 714 foreach ($required_fields as $field) { 715 if ($share[$field] === '' || $share[$field] === null) { 716 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 717 'Sharing option "%1$s" is missing a value for map "%2$s".', 718 $field, 719 $map['name'] 720 )); 721 } 722 } 723 } 724 725 if (!$permission_validator->validate($share['permission'])) { 726 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 727 'Incorrect "permission" value "%1$s" in users for map "%2$s".', 728 $share['permission'], 729 $map['name'] 730 )); 731 } 732 733 if (array_key_exists('private', $map) && $map['private'] == PUBLIC_SHARING 734 && $share['permission'] == PERM_READ) { 735 self::exception(ZBX_API_ERROR_PARAMETERS, 736 _s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name']) 737 ); 738 } 739 740 if (array_key_exists($share['userid'], $userids)) { 741 self::exception(ZBX_API_ERROR_PARAMETERS, 742 _s('Duplicate userid "%1$s" in users for map "%2$s".', $share['userid'], $map['name']) 743 ); 744 } 745 746 $userids[$share['userid']] = $share['userid']; 747 } 748 } 749 750 if (array_key_exists('userid', $map) && $map['userid']) { 751 $userids[$map['userid']] = $map['userid']; 752 } 753 754 // Users validation. 755 if ($userids) { 756 $db_users = API::User()->get([ 757 'userids' => $userids, 758 'countOutput' => true 759 ]); 760 761 if (count($userids) != $db_users) { 762 self::exception(ZBX_API_ERROR_PARAMETERS, 763 _s('Incorrect user ID specified for map "%1$s".', $map['name']) 764 ); 765 } 766 } 767 768 // Map user group shares. 769 if (array_key_exists('userGroups', $map)) { 770 if (!is_array($map['userGroups'])) { 771 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 772 } 773 774 $shared_user_groupids = []; 775 $required_fields = ['usrgrpid', 'permission']; 776 777 foreach ($map['userGroups'] as $share) { 778 // Check required parameters. 779 $missing_keys = array_diff($required_fields, array_keys($share)); 780 781 if ($missing_keys) { 782 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 783 'User group sharing is missing parameters: %1$s for map "%2$s".', 784 implode(', ', $missing_keys), 785 $map['name'] 786 )); 787 } 788 else { 789 foreach ($required_fields as $field) { 790 if ($share[$field] === '' || $share[$field] === null) { 791 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 792 'Field "%1$s" is missing a value for map "%2$s".', 793 $field, 794 $map['name'] 795 )); 796 } 797 } 798 } 799 800 if (!$permission_validator->validate($share['permission'])) { 801 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 802 'Incorrect "permission" value "%1$s" in user groups for map "%2$s".', 803 $share['permission'], 804 $map['name'] 805 )); 806 } 807 808 if (array_key_exists('private', $map) && $map['private'] == PUBLIC_SHARING 809 && $share['permission'] == PERM_READ) { 810 self::exception(ZBX_API_ERROR_PARAMETERS, 811 _s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name']) 812 ); 813 } 814 815 if (array_key_exists($share['usrgrpid'], $shared_user_groupids)) { 816 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 817 'Duplicate usrgrpid "%1$s" in user groups for map "%2$s".', 818 $share['usrgrpid'], 819 $map['name'] 820 )); 821 } 822 823 $shared_user_groupids[$share['usrgrpid']] = $share['usrgrpid']; 824 } 825 826 if ($shared_user_groupids) { 827 $db_user_groups = API::UserGroup()->get([ 828 'usrgrpids' => $shared_user_groupids, 829 'countOutput' => true 830 ]); 831 832 if (count($shared_user_groupids) != $db_user_groups) { 833 self::exception(ZBX_API_ERROR_PARAMETERS, 834 _s('Incorrect user group ID specified for map "%1$s".', $map['name']) 835 ); 836 } 837 } 838 839 unset($shared_user_groupids); 840 } 841 842 // Map labels. 843 $map_labels = ['label_type' => ['typeName' => _('icon')]]; 844 845 if (array_key_exists('label_format', $map) && $map['label_format'] == SYSMAP_LABEL_ADVANCED_ON) { 846 $map_labels['label_type_hostgroup'] = [ 847 'string' => 'label_string_hostgroup', 848 'typeName' => _('host group') 849 ]; 850 $map_labels['label_type_host'] = [ 851 'string' => 'label_string_host', 852 'typeName' => _('host') 853 ]; 854 $map_labels['label_type_trigger'] = [ 855 'string' => 'label_string_trigger', 856 'typeName' => _('trigger') 857 ]; 858 $map_labels['label_type_map'] = [ 859 'string' => 'label_string_map', 860 'typeName' => _('map') 861 ]; 862 $map_labels['label_type_image'] = [ 863 'string' => 'label_string_image', 864 'typeName' => _('image') 865 ]; 866 } 867 868 foreach ($map_labels as $label_name => $label_data) { 869 if (!array_key_exists($label_name, $map)) { 870 continue; 871 } 872 873 if (sysmapElementLabel($map[$label_name]) === false) { 874 self::exception(ZBX_API_ERROR_PARAMETERS, 875 _s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name']) 876 ); 877 } 878 879 if ($map[$label_name] == MAP_LABEL_TYPE_CUSTOM) { 880 if (!array_key_exists('string', $label_data)) { 881 self::exception(ZBX_API_ERROR_PARAMETERS, 882 _s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name']) 883 ); 884 } 885 886 if (!array_key_exists($label_data['string'], $map) || zbx_empty($map[$label_data['string']])) { 887 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 888 'Custom label for map "%2$s" elements of type "%1$s" may not be empty.', 889 $label_data['typeName'], 890 $map['name'] 891 ) 892 ); 893 } 894 } 895 896 if ($label_name == 'label_type_image' && $map[$label_name] == MAP_LABEL_TYPE_STATUS) { 897 self::exception(ZBX_API_ERROR_PARAMETERS, 898 _s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name']) 899 ); 900 } 901 902 if ($label_name === 'label_type' || $label_name === 'label_type_host') { 903 continue; 904 } 905 906 if ($map[$label_name] == MAP_LABEL_TYPE_IP) { 907 self::exception(ZBX_API_ERROR_PARAMETERS, 908 _s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name']) 909 ); 910 } 911 } 912 913 // Validating grid options. 914 $possibleGridSizes = [20, 40, 50, 75, 100]; 915 916 // Grid size. 917 if (array_key_exists('grid_size', $map) && !in_array($map['grid_size'], $possibleGridSizes)) { 918 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 919 'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s".', 920 $map['grid_size'], 921 implode('", "', $possibleGridSizes) 922 )); 923 } 924 925 // Grid auto align. 926 if (array_key_exists('grid_align', $map) && $map['grid_align'] != SYSMAP_GRID_ALIGN_ON 927 && $map['grid_align'] != SYSMAP_GRID_ALIGN_OFF) { 928 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 929 'Value "%1$s" is invalid for parameter "grid_align". Choices are: "%2$s" and "%3$s"', 930 $map['grid_align'], 931 SYSMAP_GRID_ALIGN_ON, 932 SYSMAP_GRID_ALIGN_OFF 933 )); 934 } 935 936 // Grid show. 937 if (array_key_exists('grid_show', $map) && $map['grid_show'] != SYSMAP_GRID_SHOW_ON 938 && $map['grid_show'] != SYSMAP_GRID_SHOW_OFF) { 939 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 940 'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s" and "%3$s".', 941 $map['grid_show'], 942 SYSMAP_GRID_SHOW_ON, 943 SYSMAP_GRID_SHOW_OFF 944 )); 945 } 946 947 // Urls. 948 if (array_key_exists('urls', $map) && $map['urls']) { 949 $url_names = zbx_toHash($map['urls'], 'name'); 950 951 foreach ($map['urls'] as $url) { 952 if ($url['name'] === '' || $url['url'] === '') { 953 self::exception(ZBX_API_ERROR_PARAMETERS, 954 _s('URL should have both "name" and "url" fields for map "%1$s".', $map['name']) 955 ); 956 } 957 958 if (!array_key_exists($url['name'], $url_names)) { 959 self::exception(ZBX_API_ERROR_PARAMETERS, 960 _s('URL name should be unique for map "%1$s".', $map['name']) 961 ); 962 } 963 964 $url_validate_options = ['allow_user_macro' => false]; 965 if ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) { 966 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_HOST; 967 } 968 elseif ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) { 969 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_TRIGGER; 970 } 971 else { 972 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_NONE; 973 } 974 975 if (!CHtmlUrlValidator::validate($url['url'], $url_validate_options)) { 976 self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong value for url field.')); 977 } 978 979 unset($url_names[$url['name']]); 980 } 981 } 982 983 if (array_key_exists('selements', $map)) { 984 if (!is_array($map['selements'])) { 985 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 986 } 987 elseif (!CMapHelper::checkSelementPermissions($map['selements'])) { 988 self::exception(ZBX_API_ERROR_PERMISSIONS, 989 _('No permissions to referred object or it does not exist!') 990 ); 991 } 992 993 foreach (array_values($map['selements']) as $selement_index => $selement) { 994 $this->validateSelementTags($selement, '/'.($map_index + 1).'/selements/'.($selement_index + 1)); 995 } 996 } 997 998 // Map selement links. 999 if (array_key_exists('links', $map) && $map['links']) { 1000 $selementids = zbx_objectValues($map['selements'], 'selementid'); 1001 1002 foreach ($map['links'] as $link) { 1003 if (!in_array($link['selementid1'], $selementids)) { 1004 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1005 'Link selementid1 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".', 1006 $link['selementid1'], 1007 $map['name'] 1008 )); 1009 } 1010 1011 if (!in_array($link['selementid2'], $selementids)) { 1012 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1013 'Link selementid2 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".', 1014 $link['selementid2'], 1015 $map['name'] 1016 )); 1017 } 1018 } 1019 } 1020 } 1021 } 1022 1023 /** 1024 * Validate the input parameters for the update() method. 1025 * 1026 * @param array $maps maps data array 1027 * @param array $db_maps db maps data array 1028 * 1029 * @throws APIException if the input is invalid. 1030 */ 1031 protected function validateUpdate(array $maps, array $db_maps) { 1032 if (!$maps) { 1033 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 1034 } 1035 1036 $user_data = self::$userData; 1037 1038 // Validate given IDs. 1039 $this->checkObjectIds($maps, 'sysmapid', 1040 _('No "%1$s" given for map.'), 1041 _('Empty map ID.'), 1042 _('Incorrect map ID.') 1043 ); 1044 1045 $check_names = []; 1046 1047 foreach ($maps as $map) { 1048 // Check if this map exists and user has write permissions. 1049 if (!array_key_exists($map['sysmapid'], $db_maps)) { 1050 self::exception(ZBX_API_ERROR_PERMISSIONS, 1051 _('No permissions to referred object or it does not exist!') 1052 ); 1053 } 1054 1055 // Validate "name" field. 1056 if (array_key_exists('name', $map)) { 1057 if (is_array($map['name'])) { 1058 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1059 } 1060 elseif ($map['name'] === '' || $map['name'] === null || $map['name'] === false) { 1061 self::exception(ZBX_API_ERROR_PARAMETERS, _('Map name cannot be empty.')); 1062 } 1063 1064 if ($db_maps[$map['sysmapid']]['name'] !== $map['name']) { 1065 $check_names[] = $map; 1066 } 1067 } 1068 } 1069 1070 if ($check_names) { 1071 // Check for duplicate names. 1072 $duplicate = CArrayHelper::findDuplicate($check_names, 'name'); 1073 if ($duplicate) { 1074 self::exception(ZBX_API_ERROR_PARAMETERS, 1075 _s('Duplicate "name" value "%1$s" for map.', $duplicate['name']) 1076 ); 1077 } 1078 1079 $db_map_names = $this->get([ 1080 'output' => ['sysmapid', 'name'], 1081 'filter' => ['name' => zbx_objectValues($check_names, 'name')], 1082 'nopermissions' => true 1083 ]); 1084 $db_map_names = zbx_toHash($db_map_names, 'name'); 1085 1086 // Check for existing names. 1087 foreach ($check_names as $map) { 1088 if (array_key_exists($map['name'], $db_map_names) 1089 && bccomp($db_map_names[$map['name']]['sysmapid'], $map['sysmapid']) != 0) { 1090 self::exception(ZBX_API_ERROR_PARAMETERS, 1091 _s('Map "%1$s" already exists.', $map['name']) 1092 ); 1093 } 1094 } 1095 } 1096 1097 $private_validator = new CLimitedSetValidator([ 1098 'values' => [PUBLIC_SHARING, PRIVATE_SHARING] 1099 ]); 1100 1101 $permission_validator = new CLimitedSetValidator([ 1102 'values' => [PERM_READ, PERM_READ_WRITE] 1103 ]); 1104 1105 $show_suppressed_types = [ZBX_PROBLEM_SUPPRESSED_FALSE, ZBX_PROBLEM_SUPPRESSED_TRUE]; 1106 $show_suppressed_validator = new CLimitedSetValidator(['values' => $show_suppressed_types]); 1107 1108 $expandproblem_types = [SYSMAP_PROBLEMS_NUMBER, SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER_CRITICAL]; 1109 $expandproblem_validator = new CLimitedSetValidator(['values' => $expandproblem_types]); 1110 1111 foreach ($maps as $map_index => $map) { 1112 // Check if owner can be set. 1113 if (array_key_exists('userid', $map)) { 1114 if ($map['userid'] === '' || $map['userid'] === null || $map['userid'] === false) { 1115 self::exception(ZBX_API_ERROR_PARAMETERS, _('Map owner cannot be empty.')); 1116 } 1117 elseif ($map['userid'] != $user_data['userid'] && $user_data['type'] != USER_TYPE_SUPER_ADMIN 1118 && $user_data['type'] != USER_TYPE_ZABBIX_ADMIN) { 1119 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only administrators can set map owner.')); 1120 } 1121 } 1122 1123 // Unset extra field. 1124 unset($db_maps[$map['sysmapid']]['userid']); 1125 1126 $map = array_merge($db_maps[$map['sysmapid']], $map); 1127 1128 // Check "width" and "height" fields. 1129 if ($map['width'] > 65535 || $map['width'] < 1) { 1130 self::exception(ZBX_API_ERROR_PARAMETERS, 1131 _s('Incorrect "width" value for map "%1$s".', $map['name']) 1132 ); 1133 } 1134 1135 if ($map['height'] > 65535 || $map['height'] < 1) { 1136 self::exception(ZBX_API_ERROR_PARAMETERS, 1137 _s('Incorrect "height" value for map "%1$s".', $map['name']) 1138 ); 1139 } 1140 1141 if (!$private_validator->validate($map['private'])) { 1142 self::exception(ZBX_API_ERROR_PARAMETERS, 1143 _s('Incorrect "private" value "%1$s" for map "%2$s".', $map['private'], $map['name']) 1144 ); 1145 } 1146 1147 // Check for invalid "show_suppressed" values. 1148 if (array_key_exists('show_suppressed', $map) 1149 && !$show_suppressed_validator->validate($map['show_suppressed'])) { 1150 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 1151 'show_suppressed', _s('value must be one of %1$s', implode(', ', $show_suppressed_types)) 1152 )); 1153 } 1154 1155 if (array_key_exists('expandproblem', $map) && !$expandproblem_validator->validate($map['expandproblem'])) { 1156 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'expandproblem', 1157 _s('value must be one of %1$s', implode(', ', $expandproblem_types)) 1158 )); 1159 } 1160 1161 $userids = []; 1162 1163 // Map user shares. 1164 if (array_key_exists('users', $map)) { 1165 if (!is_array($map['users'])) { 1166 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1167 } 1168 1169 $required_fields = ['userid', 'permission']; 1170 1171 foreach ($map['users'] as $share) { 1172 // Check required parameters. 1173 $missing_keys = array_diff($required_fields, array_keys($share)); 1174 1175 if ($missing_keys) { 1176 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1177 'User sharing is missing parameters: %1$s for map "%2$s".', 1178 implode(', ', $missing_keys), 1179 $map['name'] 1180 )); 1181 } 1182 else { 1183 foreach ($required_fields as $field) { 1184 if ($share[$field] === '' || $share[$field] === null) { 1185 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1186 'Sharing option "%1$s" is missing a value for map "%2$s".', 1187 $field, 1188 $map['name'] 1189 )); 1190 } 1191 } 1192 } 1193 1194 if (!$permission_validator->validate($share['permission'])) { 1195 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1196 'Incorrect "permission" value "%1$s" in users for map "%2$s".', 1197 $share['permission'], 1198 $map['name'] 1199 )); 1200 } 1201 1202 if ($map['private'] == PUBLIC_SHARING && $share['permission'] == PERM_READ) { 1203 self::exception(ZBX_API_ERROR_PARAMETERS, 1204 _s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name']) 1205 ); 1206 } 1207 1208 if (array_key_exists($share['userid'], $userids)) { 1209 self::exception(ZBX_API_ERROR_PARAMETERS, 1210 _s('Duplicate userid "%1$s" in users for map "%2$s".', $share['userid'], $map['name']) 1211 ); 1212 } 1213 1214 $userids[$share['userid']] = $share['userid']; 1215 } 1216 } 1217 1218 if (array_key_exists('userid', $map) && $map['userid']) { 1219 $userids[$map['userid']] = $map['userid']; 1220 } 1221 1222 // Users validation. 1223 if ($userids) { 1224 $db_users = API::User()->get([ 1225 'userids' => $userids, 1226 'countOutput' => true 1227 ]); 1228 1229 if (count($userids) != $db_users) { 1230 self::exception(ZBX_API_ERROR_PARAMETERS, 1231 _s('Incorrect user ID specified for map "%1$s".', $map['name']) 1232 ); 1233 } 1234 } 1235 1236 // Map user group shares. 1237 if (array_key_exists('userGroups', $map)) { 1238 if (!is_array($map['userGroups'])) { 1239 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1240 } 1241 1242 $shared_user_groupids = []; 1243 $required_fields = ['usrgrpid', 'permission']; 1244 1245 foreach ($map['userGroups'] as $share) { 1246 // Check required parameters. 1247 $missing_keys = array_diff($required_fields, array_keys($share)); 1248 1249 if ($missing_keys) { 1250 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1251 'User group sharing is missing parameters: %1$s for map "%2$s".', 1252 implode(', ', $missing_keys), 1253 $map['name']) 1254 ); 1255 } 1256 else { 1257 foreach ($required_fields as $field) { 1258 if ($share[$field] === '' || $share[$field] === null) { 1259 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1260 'Sharing option "%1$s" is missing a value for map "%2$s".', 1261 $field, 1262 $map['name'] 1263 )); 1264 } 1265 } 1266 } 1267 1268 if (!$permission_validator->validate($share['permission'])) { 1269 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1270 'Incorrect "permission" value "%1$s" in user groups for map "%2$s".', 1271 $share['permission'], 1272 $map['name'] 1273 )); 1274 } 1275 1276 if ($map['private'] == PUBLIC_SHARING && $share['permission'] == PERM_READ) { 1277 self::exception(ZBX_API_ERROR_PARAMETERS, 1278 _s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name']) 1279 ); 1280 } 1281 1282 if (array_key_exists($share['usrgrpid'], $shared_user_groupids)) { 1283 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1284 'Duplicate usrgrpid "%1$s" in user groups for map "%2$s".', 1285 $share['usrgrpid'], 1286 $map['name'] 1287 )); 1288 } 1289 1290 $shared_user_groupids[$share['usrgrpid']] = $share['usrgrpid']; 1291 } 1292 1293 if ($shared_user_groupids) { 1294 $db_user_groups = API::UserGroup()->get([ 1295 'usrgrpids' => $shared_user_groupids, 1296 'countOutput' => true 1297 ]); 1298 1299 if (count($shared_user_groupids) != $db_user_groups) { 1300 self::exception(ZBX_API_ERROR_PARAMETERS, 1301 _s('Incorrect user group ID specified for map "%1$s".', $map['name']) 1302 ); 1303 } 1304 } 1305 1306 unset($shared_user_groupids); 1307 } 1308 1309 // Map labels. 1310 $map_labels = ['label_type' => ['typeName' => _('icon')]]; 1311 1312 if (array_key_exists('label_format', $map) 1313 && $map['label_format'] == SYSMAP_LABEL_ADVANCED_ON) { 1314 $map_labels['label_type_hostgroup'] = [ 1315 'string' => 'label_string_hostgroup', 1316 'typeName' => _('host group') 1317 ]; 1318 $map_labels['label_type_host'] = [ 1319 'string' => 'label_string_host', 1320 'typeName' => _('host') 1321 ]; 1322 $map_labels['label_type_trigger'] = [ 1323 'string' => 'label_string_trigger', 1324 'typeName' => _('trigger') 1325 ]; 1326 $map_labels['label_type_map'] = [ 1327 'string' => 'label_string_map', 1328 'typeName' => _('map') 1329 ]; 1330 $map_labels['label_type_image'] = [ 1331 'string' => 'label_string_image', 1332 'typeName' => _('image') 1333 ]; 1334 } 1335 1336 foreach ($map_labels as $label_name => $labelData) { 1337 if (!array_key_exists($label_name, $map)) { 1338 continue; 1339 } 1340 1341 if (sysmapElementLabel($map[$label_name]) === false) { 1342 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1343 'Incorrect %1$s label type value for map "%2$s".', 1344 $labelData['typeName'], 1345 $map['name'] 1346 )); 1347 } 1348 1349 if ($map[$label_name] == MAP_LABEL_TYPE_CUSTOM) { 1350 if (!array_key_exists('string', $labelData)) { 1351 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1352 'Incorrect %1$s label type value for map "%2$s".', 1353 $labelData['typeName'], 1354 $map['name'] 1355 )); 1356 } 1357 1358 if (!array_key_exists($labelData['string'], $map) || zbx_empty($map[$labelData['string']])) { 1359 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1360 'Custom label for map "%2$s" elements of type "%1$s" may not be empty.', 1361 $labelData['typeName'], 1362 $map['name'] 1363 )); 1364 } 1365 } 1366 1367 if ($label_name === 'label_type_image' && $map[$label_name] == MAP_LABEL_TYPE_STATUS) { 1368 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1369 'Incorrect %1$s label type value for map "%2$s".', 1370 $labelData['typeName'], 1371 $map['name'] 1372 )); 1373 } 1374 1375 if ($label_name === 'label_type' || $label_name === 'label_type_host') { 1376 continue; 1377 } 1378 1379 if ($map[$label_name] == MAP_LABEL_TYPE_IP) { 1380 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1381 'Incorrect %1$s label type value for map "%2$s".', 1382 $labelData['typeName'], 1383 $map['name'] 1384 )); 1385 } 1386 } 1387 1388 // Validating grid options. 1389 $possibleGridSizes = [20, 40, 50, 75, 100]; 1390 1391 // Grid size. 1392 if (array_key_exists('grid_size', $map) && !in_array($map['grid_size'], $possibleGridSizes)) { 1393 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1394 'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s".', 1395 $map['grid_size'], 1396 implode('", "', $possibleGridSizes) 1397 )); 1398 } 1399 1400 // Grid auto align. 1401 if (array_key_exists('grid_align', $map) && $map['grid_align'] != SYSMAP_GRID_ALIGN_ON 1402 && $map['grid_align'] != SYSMAP_GRID_ALIGN_OFF) { 1403 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1404 'Value "%1$s" is invalid for parameter "grid_align". Choices are: "%2$s" and "%3$s"', 1405 $map['grid_align'], 1406 SYSMAP_GRID_ALIGN_ON, 1407 SYSMAP_GRID_ALIGN_OFF 1408 )); 1409 } 1410 1411 // Grid show. 1412 if (array_key_exists('grid_show', $map) && $map['grid_show'] != SYSMAP_GRID_SHOW_ON 1413 && $map['grid_show'] != SYSMAP_GRID_SHOW_OFF) { 1414 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1415 'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s" and "%3$s".', 1416 $map['grid_show'], 1417 SYSMAP_GRID_SHOW_ON, 1418 SYSMAP_GRID_SHOW_OFF 1419 )); 1420 } 1421 1422 // Urls. 1423 if (array_key_exists('urls', $map) && !empty($map['urls'])) { 1424 $urlNames = zbx_toHash($map['urls'], 'name'); 1425 1426 foreach ($map['urls'] as $url) { 1427 if ($url['name'] === '' || $url['url'] === '') { 1428 self::exception(ZBX_API_ERROR_PARAMETERS, 1429 _s('URL should have both "name" and "url" fields for map "%1$s".', $map['name']) 1430 ); 1431 } 1432 1433 if (!array_key_exists($url['name'], $urlNames)) { 1434 self::exception(ZBX_API_ERROR_PARAMETERS, 1435 _s('URL name should be unique for map "%1$s".', $map['name']) 1436 ); 1437 } 1438 1439 $url_validate_options = ['allow_user_macro' => false]; 1440 if ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) { 1441 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_HOST; 1442 } 1443 elseif ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) { 1444 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_TRIGGER; 1445 } 1446 else { 1447 $url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_NONE; 1448 } 1449 1450 if (!CHtmlUrlValidator::validate($url['url'], $url_validate_options)) { 1451 self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong value for url field.')); 1452 } 1453 1454 unset($urlNames[$url['name']]); 1455 } 1456 } 1457 1458 if (array_key_exists('selements', $map) && !is_array($map['selements'])) { 1459 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1460 } 1461 1462 if (array_key_exists('selements', $map)) { 1463 foreach (array_values($map['selements']) as $selement_index => $selement) { 1464 $this->validateSelementTags($selement, '/'.($map_index + 1).'/selements/'.($selement_index + 1)); 1465 } 1466 } 1467 1468 // Map selement links. 1469 if (array_key_exists('links', $map) && $map['links']) { 1470 $selementids = zbx_objectValues($map['selements'], 'selementid'); 1471 1472 foreach ($map['links'] as $link) { 1473 if (!in_array($link['selementid1'], $selementids)) { 1474 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1475 'Link selementid1 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".', 1476 $link['selementid1'], 1477 $map['name'] 1478 )); 1479 } 1480 1481 if (!in_array($link['selementid2'], $selementids)) { 1482 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1483 'Link selementid2 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".', 1484 $link['selementid2'], 1485 $map['name'] 1486 )); 1487 } 1488 } 1489 } 1490 } 1491 1492 // Validate circular reference. 1493 foreach ($maps as &$map) { 1494 $map = array_merge($db_maps[$map['sysmapid']], $map); 1495 $this->cref_maps[$map['sysmapid']] = $map; 1496 } 1497 unset($map); 1498 1499 $this->validateCircularReference($maps); 1500 } 1501 1502 /** 1503 * Validate Map element tag properties. 1504 * 1505 * @param array $selement['evaltype'] 1506 * @param array $selement['tags'] 1507 * @param string $selement['tags'][]['tag'] 1508 * @param string $selement['tags'][]['value'] 1509 * @param int $selement['tags'][]['operator'] 1510 * @param string $path 1511 * 1512 * @throws APIException if input is invalid. 1513 */ 1514 protected function validateSelementTags(array $selement, string $path): void { 1515 if (!array_key_exists('tags', $selement)) { 1516 return; 1517 } 1518 1519 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1520 'evaltype' => ['type' => API_INT32, 'in' => implode(',', [CONDITION_EVAL_TYPE_AND_OR, CONDITION_EVAL_TYPE_OR]), 'default' => DB::getDefault('sysmaps_elements', 'evaltype')], 1521 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [ 1522 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sysmaps_element_tag', 'tag')], 1523 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmaps_element_tag', 'value'), 'default' => DB::getDefault('sysmaps_element_tag', 'value')], 1524 'operator' => ['type' => API_STRING_UTF8, 'in' => implode(',', [TAG_OPERATOR_LIKE, TAG_OPERATOR_EQUAL, TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_EXISTS, TAG_OPERATOR_NOT_EXISTS]), 'default' => DB::getDefault('sysmaps_element_tag', 'operator')] 1525 ]] 1526 ]]; 1527 1528 $data = array_intersect_key($selement, $api_input_rules['fields']); 1529 if (!CApiInputValidator::validate($api_input_rules, $data, $path, $error)) { 1530 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1531 } 1532 } 1533 1534 /** 1535 * Hash of maps data for circular reference validation. Map id is used as key. 1536 * 1537 * @var array 1538 */ 1539 protected $cref_maps; 1540 1541 /** 1542 * Validate maps for circular reference. 1543 * 1544 * @param array $maps Array of maps to be validated for circular reference. 1545 * 1546 * @throws APIException if input is invalid. 1547 */ 1548 protected function validateCircularReference(array $maps) { 1549 foreach ($maps as $map) { 1550 if (!array_key_exists('selements', $map) || !$map['selements']) { 1551 continue; 1552 } 1553 $cref_mapids = array_key_exists('sysmapid', $map) ? [$map['sysmapid']] : []; 1554 1555 foreach ($map['selements'] as $selement) { 1556 if (!$this->validateCircularReferenceRecursive($selement, $cref_mapids)) { 1557 self::exception(ZBX_API_ERROR_PARAMETERS, 1558 _s('Cannot add map element of the map "%1$s" due to circular reference.', $map['name']) 1559 ); 1560 } 1561 } 1562 } 1563 } 1564 1565 /** 1566 * Recursive map element circular reference validation. 1567 * 1568 * @param array $selement Map selement data array. 1569 * @param array $cref_mapids Array of map ids for current recursion step. 1570 * 1571 * @return bool 1572 */ 1573 protected function validateCircularReferenceRecursive(array $selement, &$cref_mapids) { 1574 if ($selement['elementtype'] != SYSMAP_ELEMENT_TYPE_MAP) { 1575 return true; 1576 } 1577 1578 $sysmapid = $selement['elements'][0]['sysmapid']; 1579 1580 if ($sysmapid !== null && !array_key_exists($sysmapid, $this->cref_maps)) { 1581 $db_maps = DB::select($this->tableName, [ 1582 'output' => ['name'], 1583 'filter' => ['sysmapid' => $sysmapid] 1584 ]); 1585 1586 if ($db_maps) { 1587 $db_map = $db_maps[0]; 1588 $db_map['selements'] = []; 1589 $selements = DB::select('sysmaps_elements', [ 1590 'output' => ['elementid'], 1591 'filter' => [ 1592 'sysmapid' => $sysmapid, 1593 'elementtype' => SYSMAP_ELEMENT_TYPE_MAP 1594 ] 1595 ]); 1596 1597 foreach ($selements as $selement) { 1598 $db_map['selements'][] = [ 1599 'elementtype' => SYSMAP_ELEMENT_TYPE_MAP, 1600 'elements' => [ 1601 ['sysmapid' => $selement['elementid']] 1602 ] 1603 ]; 1604 } 1605 1606 $this->cref_maps[$sysmapid] = $db_map; 1607 unset($selements); 1608 } 1609 else { 1610 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 1611 } 1612 } 1613 1614 if (in_array($sysmapid, $cref_mapids)) { 1615 $cref_mapids[] = $sysmapid; 1616 return false; 1617 } 1618 1619 // Find maps that reference the current element, and if one has selements, check all of them recursively. 1620 if ($sysmapid !== null && array_key_exists('selements', $this->cref_maps[$sysmapid]) 1621 && is_array($this->cref_maps[$sysmapid]['selements'])) { 1622 $cref_mapids[] = $sysmapid; 1623 1624 foreach ($this->cref_maps[$sysmapid]['selements'] as $selement) { 1625 if (!$this->validateCircularReferenceRecursive($selement, $cref_mapids)) { 1626 return false; 1627 } 1628 } 1629 1630 array_pop($cref_mapids); 1631 } 1632 1633 return true; 1634 } 1635 1636 /** 1637 * Add map. 1638 * 1639 * @param array $maps 1640 * @param string $maps['name'] 1641 * @param array $maps['width'] 1642 * @param int $maps['height'] 1643 * @param string $maps['backgroundid'] 1644 * @param string $maps['highlight'] 1645 * @param array $maps['label_type'] 1646 * @param int $maps['label_location'] 1647 * @param int $maps['grid_size'] size of one grid cell. 100 refers to 100x100 and so on. 1648 * @param int $maps['grid_show'] does grid need to be shown. Constants: SYSMAP_GRID_SHOW_ON / SYSMAP_GRID_SHOW_OFF 1649 * @param int $maps['grid_align'] do elements need to be aligned to the grid. Constants: SYSMAP_GRID_ALIGN_ON / SYSMAP_GRID_ALIGN_OFF 1650 * 1651 * @return array 1652 */ 1653 public function create($maps) { 1654 $maps = zbx_toArray($maps); 1655 1656 $this->validateCreate($maps); 1657 1658 foreach ($maps as &$map) { 1659 $map['userid'] = array_key_exists('userid', $map) ? $map['userid'] : self::$userData['userid']; 1660 } 1661 unset($map); 1662 1663 $sysmapids = DB::insert('sysmaps', $maps); 1664 1665 $shared_users = []; 1666 $shared_user_groups = []; 1667 $urls = []; 1668 $shapes = []; 1669 $selements = []; 1670 $links = []; 1671 $api_shape_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [ 1672 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE])], 1673 'x' => ['type' => API_INT32], 1674 'y' => ['type' => API_INT32], 1675 'width' => ['type' => API_INT32], 1676 'height' => ['type' => API_INT32], 1677 'font' => ['type' => API_INT32, 'in' => '0:12'], 1678 'font_size' => ['type' => API_INT32, 'in' => '1:250'], 1679 'text_halign' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_HALIGN_CENTER, SYSMAP_SHAPE_LABEL_HALIGN_LEFT, SYSMAP_SHAPE_LABEL_HALIGN_RIGHT])], 1680 'text_valign' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_VALIGN_MIDDLE, SYSMAP_SHAPE_LABEL_VALIGN_TOP, SYSMAP_SHAPE_LABEL_VALIGN_BOTTOM])], 1681 'border_type' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])], 1682 'border_width' => ['type' => API_INT32, 'in' => '0:50'], 1683 'border_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')], 1684 'background_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'background_color')], 1685 'font_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'font_color')], 1686 'text' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'text')], 1687 'zindex' => ['type' => API_INT32] 1688 ]]; 1689 $api_line_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [ 1690 'x1' => ['type' => API_INT32], 1691 'y1' => ['type' => API_INT32], 1692 'x2' => ['type' => API_INT32], 1693 'y2' => ['type' => API_INT32], 1694 'line_type' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])], 1695 'line_width' => ['type' => API_INT32, 'in' => '0:50'], 1696 'line_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')], 1697 'zindex' => ['type' => API_INT32] 1698 ]]; 1699 $default_shape_width = DB::getDefault('sysmap_shape', 'width'); 1700 $default_shape_height = DB::getDefault('sysmap_shape', 'height'); 1701 1702 foreach ($sysmapids as $key => $sysmapid) { 1703 // Map user shares. 1704 if (array_key_exists('users', $maps[$key])) { 1705 foreach ($maps[$key]['users'] as $user) { 1706 $shared_users[] = [ 1707 'sysmapid' => $sysmapid, 1708 'userid' => $user['userid'], 1709 'permission' => $user['permission'] 1710 ]; 1711 } 1712 } 1713 1714 // Map user group shares. 1715 if (array_key_exists('userGroups', $maps[$key])) { 1716 foreach ($maps[$key]['userGroups'] as $user_group) { 1717 $shared_user_groups[] = [ 1718 'sysmapid' => $sysmapid, 1719 'usrgrpid' => $user_group['usrgrpid'], 1720 'permission' => $user_group['permission'] 1721 ]; 1722 } 1723 } 1724 1725 if (array_key_exists('urls', $maps[$key])) { 1726 foreach ($maps[$key]['urls'] as $url) { 1727 $url['sysmapid'] = $sysmapid; 1728 $urls[] = $url; 1729 } 1730 } 1731 1732 if (array_key_exists('selements', $maps[$key])) { 1733 foreach ($maps[$key]['selements'] as $snum => $selement) { 1734 $maps[$key]['selements'][$snum]['sysmapid'] = $sysmapid; 1735 } 1736 1737 $selements = array_merge($selements, $maps[$key]['selements']); 1738 } 1739 1740 if (array_key_exists('shapes', $maps[$key])) { 1741 $path = '/'.($key + 1).'/shape'; 1742 $api_shape_rules['fields']['x']['in'] = '0:'.$maps[$key]['width']; 1743 $api_shape_rules['fields']['y']['in'] = '0:'.$maps[$key]['height']; 1744 $api_shape_rules['fields']['width']['in'] = '1:'.$maps[$key]['width']; 1745 $api_shape_rules['fields']['height']['in'] = '1:'.$maps[$key]['height']; 1746 1747 foreach ($maps[$key]['shapes'] as &$shape) { 1748 $shape['width'] = array_key_exists('width', $shape) ? $shape['width'] : $default_shape_width; 1749 $shape['height'] = array_key_exists('height', $shape) ? $shape['height'] : $default_shape_height; 1750 } 1751 unset($shape); 1752 1753 if (!CApiInputValidator::validate($api_shape_rules, $maps[$key]['shapes'], $path, $error)) { 1754 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1755 } 1756 1757 foreach ($maps[$key]['shapes'] as $snum => $shape) { 1758 $maps[$key]['shapes'][$snum]['sysmapid'] = $sysmapid; 1759 } 1760 1761 $shapes = array_merge($shapes, $maps[$key]['shapes']); 1762 } 1763 1764 if (array_key_exists('lines', $maps[$key])) { 1765 $path = '/'.($key + 1).'/line'; 1766 $api_line_rules['fields']['x1']['in'] = '0:'.$maps[$key]['width']; 1767 $api_line_rules['fields']['y1']['in'] = '0:'.$maps[$key]['height']; 1768 $api_line_rules['fields']['x2']['in'] = '0:'.$maps[$key]['width']; 1769 $api_line_rules['fields']['y2']['in'] = '0:'.$maps[$key]['height']; 1770 1771 foreach ($maps[$key]['lines'] as &$line) { 1772 $line['x2'] = array_key_exists('x2', $line) ? $line['x2'] : $default_shape_width; 1773 $line['y2'] = array_key_exists('y2', $line) ? $line['y2'] : $default_shape_height; 1774 } 1775 unset($line); 1776 1777 if (!CApiInputValidator::validate($api_line_rules, $maps[$key]['lines'], $path, $error)) { 1778 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1779 } 1780 1781 foreach ($maps[$key]['lines'] as $line) { 1782 $shape = CMapHelper::convertLineToShape($line); 1783 $shape['sysmapid'] = $sysmapid; 1784 $shapes[] = $shape; 1785 } 1786 } 1787 1788 if (array_key_exists('links', $maps[$key])) { 1789 foreach ($maps[$key]['links'] as $lnum => $link) { 1790 $maps[$key]['links'][$lnum]['sysmapid'] = $sysmapid; 1791 } 1792 1793 $links = array_merge($links, $maps[$key]['links']); 1794 } 1795 } 1796 1797 DB::insert('sysmap_user', $shared_users); 1798 DB::insert('sysmap_usrgrp', $shared_user_groups); 1799 DB::insert('sysmap_url', $urls); 1800 1801 if ($selements) { 1802 $selementids = $this->createSelements($selements); 1803 1804 if ($links) { 1805 $map_virt_selements = []; 1806 foreach ($selementids['selementids'] as $key => $selementid) { 1807 $map_virt_selements[$selements[$key]['selementid']] = $selementid; 1808 } 1809 1810 foreach ($links as $key => $link) { 1811 $links[$key]['selementid1'] = $map_virt_selements[$link['selementid1']]; 1812 $links[$key]['selementid2'] = $map_virt_selements[$link['selementid2']]; 1813 } 1814 unset($map_virt_selements); 1815 1816 $linkids = $this->createLinks($links); 1817 1818 $link_triggers = []; 1819 foreach ($linkids['linkids'] as $key => $linkId) { 1820 if (!array_key_exists('linktriggers', $links[$key])) { 1821 continue; 1822 } 1823 1824 foreach ($links[$key]['linktriggers'] as $link_trigger) { 1825 $link_trigger['linkid'] = $linkId; 1826 $link_triggers[] = $link_trigger; 1827 } 1828 } 1829 1830 if ($link_triggers) { 1831 $this->createLinkTriggers($link_triggers); 1832 } 1833 } 1834 } 1835 1836 if ($shapes) { 1837 $this->createShapes($shapes); 1838 } 1839 1840 return ['sysmapids' => $sysmapids]; 1841 } 1842 1843 /** 1844 * Update map. 1845 * 1846 * @param array $maps multidimensional array with Hosts data 1847 * @param string $maps['sysmapid'] 1848 * @param string $maps['name'] 1849 * @param array $maps['width'] 1850 * @param int $maps['height'] 1851 * @param string $maps['backgroundid'] 1852 * @param array $maps['label_type'] 1853 * @param int $maps['label_location'] 1854 * @param int $maps['grid_size'] size of one grid cell. 100 refers to 100x100 and so on. 1855 * @param int $maps['grid_show'] does grid need to be shown. Constants: SYSMAP_GRID_SHOW_ON / SYSMAP_GRID_SHOW_OFF 1856 * @param int $maps['grid_align'] do elements need to be aligned to the grid. Constants: SYSMAP_GRID_ALIGN_ON / SYSMAP_GRID_ALIGN_OFF 1857 * 1858 * @return array 1859 */ 1860 public function update(array $maps) { 1861 $maps = zbx_toArray($maps); 1862 $sysmapids = zbx_objectValues($maps, 'sysmapid'); 1863 1864 $db_maps = $this->get([ 1865 'output' => API_OUTPUT_EXTEND, 1866 'sysmapids' => zbx_objectValues($maps, 'sysmapid'), 1867 'selectLinks' => API_OUTPUT_EXTEND, 1868 'selectSelements' => API_OUTPUT_EXTEND, 1869 'selectShapes' => ['sysmap_shapeid', 'type', 'x', 'y', 'width', 'height', 'text', 'font', 'font_size', 1870 'font_color', 'text_halign', 'text_valign', 'border_type', 'border_width', 'border_color', 1871 'background_color', 'zindex' 1872 ], 1873 'selectLines' => ['sysmap_shapeid', 'x1', 'y1', 'x2', 'y2', 'line_type', 'line_width', 'line_color', 1874 'zindex' 1875 ], 1876 'selectUrls' => ['sysmapid', 'sysmapurlid', 'name', 'url', 'elementtype'], 1877 'selectUsers' => ['sysmapuserid', 'sysmapid', 'userid', 'permission'], 1878 'selectUserGroups' => ['sysmapusrgrpid', 'sysmapid', 'usrgrpid', 'permission'], 1879 'editable' => true, 1880 'preservekeys' => true 1881 ]); 1882 1883 $this->validateUpdate($maps, $db_maps); 1884 1885 $update_maps = []; 1886 $url_ids_to_delete = []; 1887 $urls_to_update = []; 1888 $urls_to_add = []; 1889 $selements_to_delete = []; 1890 $selements_to_update = []; 1891 $selements_to_add = []; 1892 $shapes_to_delete = []; 1893 $shapes_to_update = []; 1894 $shapes_to_add = []; 1895 $links_to_delete = []; 1896 $links_to_update = []; 1897 $links_to_add = []; 1898 $shared_userids_to_delete = []; 1899 $shared_users_to_update = []; 1900 $shared_users_to_add = []; 1901 $shared_user_groupids_to_delete = []; 1902 $shared_user_groups_to_update = []; 1903 $shared_user_groups_to_add = []; 1904 $api_shape_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [ 1905 'sysmap_shapeid' => ['type' => API_ID], 1906 'type' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE])], 1907 'x' => ['type' => API_INT32], 1908 'y' => ['type' => API_INT32], 1909 'width' => ['type' => API_INT32], 1910 'height' => ['type' => API_INT32], 1911 'font' => ['type' => API_INT32, 'in' => '0:12'], 1912 'font_size' => ['type' => API_INT32, 'in' => '1:250'], 1913 'text_halign' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_HALIGN_CENTER, SYSMAP_SHAPE_LABEL_HALIGN_LEFT, SYSMAP_SHAPE_LABEL_HALIGN_RIGHT])], 1914 'text_valign' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_VALIGN_MIDDLE, SYSMAP_SHAPE_LABEL_VALIGN_TOP, SYSMAP_SHAPE_LABEL_VALIGN_BOTTOM])], 1915 'border_type' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])], 1916 'border_width' => ['type' => API_INT32, 'in' => '0:50'], 1917 'border_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')], 1918 'background_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'background_color')], 1919 'font_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'font_color')], 1920 'text' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'text')], 1921 'zindex' => ['type' => API_INT32] 1922 ]]; 1923 $api_line_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [ 1924 'sysmap_shapeid' => ['type' => API_ID], 1925 'x1' => ['type' => API_INT32], 1926 'y1' => ['type' => API_INT32], 1927 'x2' => ['type' => API_INT32], 1928 'y2' => ['type' => API_INT32], 1929 'line_type' => ['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])], 1930 'line_width' => ['type' => API_INT32, 'in' => '0:50'], 1931 'line_color' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')], 1932 'zindex' => ['type' => API_INT32] 1933 ]]; 1934 $default_shape_width = DB::getDefault('sysmap_shape', 'width'); 1935 $default_shape_height = DB::getDefault('sysmap_shape', 'height'); 1936 1937 foreach ($maps as $index => $map) { 1938 $update_maps[] = [ 1939 'values' => $map, 1940 'where' => ['sysmapid' => $map['sysmapid']] 1941 ]; 1942 1943 $db_map = $db_maps[$map['sysmapid']]; 1944 1945 // Map user shares. 1946 if (array_key_exists('users', $map)) { 1947 $user_shares_diff = zbx_array_diff($map['users'], $db_map['users'], 'userid'); 1948 1949 foreach ($user_shares_diff['both'] as $update_user_share) { 1950 $shared_users_to_update[] = [ 1951 'values' => $update_user_share, 1952 'where' => ['userid' => $update_user_share['userid'], 'sysmapid' => $map['sysmapid']] 1953 ]; 1954 } 1955 1956 foreach ($user_shares_diff['first'] as $new_shared_user) { 1957 $new_shared_user['sysmapid'] = $map['sysmapid']; 1958 $shared_users_to_add[] = $new_shared_user; 1959 } 1960 1961 $shared_userids_to_delete = array_merge($shared_userids_to_delete, 1962 zbx_objectValues($user_shares_diff['second'], 'sysmapuserid') 1963 ); 1964 } 1965 1966 // Map user group shares. 1967 if (array_key_exists('userGroups', $map)) { 1968 $user_group_shares_diff = zbx_array_diff($map['userGroups'], $db_map['userGroups'], 1969 'usrgrpid' 1970 ); 1971 1972 foreach ($user_group_shares_diff['both'] as $update_user_share) { 1973 $shared_user_groups_to_update[] = [ 1974 'values' => $update_user_share, 1975 'where' => ['usrgrpid' => $update_user_share['usrgrpid'], 'sysmapid' => $map['sysmapid']] 1976 ]; 1977 } 1978 1979 foreach ($user_group_shares_diff['first'] as $new_shared_user_group) { 1980 $new_shared_user_group['sysmapid'] = $map['sysmapid']; 1981 $shared_user_groups_to_add[] = $new_shared_user_group; 1982 } 1983 1984 $shared_user_groupids_to_delete = array_merge($shared_user_groupids_to_delete, 1985 zbx_objectValues($user_group_shares_diff['second'], 'sysmapusrgrpid') 1986 ); 1987 } 1988 1989 // Urls. 1990 if (array_key_exists('urls', $map)) { 1991 $url_diff = zbx_array_diff($map['urls'], $db_map['urls'], 'name'); 1992 1993 foreach ($url_diff['both'] as $updateUrl) { 1994 $urls_to_update[] = [ 1995 'values' => $updateUrl, 1996 'where' => ['name' => $updateUrl['name'], 'sysmapid' => $map['sysmapid']] 1997 ]; 1998 } 1999 2000 foreach ($url_diff['first'] as $new_url) { 2001 $new_url['sysmapid'] = $map['sysmapid']; 2002 $urls_to_add[] = $new_url; 2003 } 2004 2005 $url_ids_to_delete = array_merge($url_ids_to_delete, 2006 zbx_objectValues($url_diff['second'], 'sysmapurlid') 2007 ); 2008 } 2009 2010 // Map elements. 2011 if (array_key_exists('selements', $map)) { 2012 $selement_diff = zbx_array_diff($map['selements'], $db_map['selements'], 'selementid'); 2013 2014 // We need sysmapid for add operations. 2015 foreach ($selement_diff['first'] as $new_selement) { 2016 $new_selement['sysmapid'] = $map['sysmapid']; 2017 $selements_to_add[] = $new_selement; 2018 } 2019 2020 foreach ($selement_diff['both'] as &$selement) { 2021 $selement['sysmapid'] = $map['sysmapid']; 2022 } 2023 unset($selement); 2024 2025 $selements_to_update = array_merge($selements_to_update, $selement_diff['both']); 2026 $selements_to_delete = array_merge($selements_to_delete, $selement_diff['second']); 2027 } 2028 2029 $map_width = array_key_exists('width', $map) ? $map['width'] : $db_map['width']; 2030 $map_height = array_key_exists('height', $map) ? $map['height'] : $db_map['height']; 2031 2032 // Map shapes. 2033 if (array_key_exists('shapes', $map)) { 2034 $map['shapes'] = array_values($map['shapes']); 2035 2036 foreach ($map['shapes'] as &$shape) { 2037 $shape['width'] = array_key_exists('width', $shape) ? $shape['width'] : $default_shape_width; 2038 $shape['height'] = array_key_exists('height', $shape) ? $shape['height'] : $default_shape_height; 2039 } 2040 unset($shape); 2041 2042 $shape_diff = zbx_array_diff($map['shapes'], $db_map['shapes'], 'sysmap_shapeid'); 2043 2044 $path = '/'.($index + 1).'/shape'; 2045 foreach ($shape_diff['first'] as $new_shape) { 2046 if (array_key_exists('sysmap_shapeid', $new_shape)) { 2047 self::exception(ZBX_API_ERROR_PARAMETERS, 2048 _('No permissions to referred object or it does not exist!') 2049 ); 2050 } 2051 } 2052 2053 unset($api_shape_rules['fields']['sysmap_shapeid']); 2054 $api_shape_rules['fields']['type']['flags'] = API_REQUIRED; 2055 $api_shape_rules['fields']['x']['in'] = '0:'.$map_width; 2056 $api_shape_rules['fields']['y']['in'] = '0:'.$map_height; 2057 $api_shape_rules['fields']['width']['in'] = '1:'.$map_width; 2058 $api_shape_rules['fields']['height']['in'] = '1:'.$map_height; 2059 2060 if (!CApiInputValidator::validate($api_shape_rules, $shape_diff['first'], $path, $error)) { 2061 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 2062 } 2063 2064 $api_shape_rules['fields']['sysmap_shapeid'] = ['type' => API_ID, 'flags' => API_REQUIRED]; 2065 $api_shape_rules['fields']['type']['flags'] = 0; 2066 if (!CApiInputValidator::validate($api_shape_rules, $shape_diff['both'], $path, $error)) { 2067 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 2068 } 2069 2070 $shapes_to_update = array_merge($shapes_to_update, $shape_diff['both']); 2071 $shapes_to_delete = array_merge($shapes_to_delete, $shape_diff['second']); 2072 2073 // We need sysmapid for add operations. 2074 foreach ($shape_diff['first'] as $new_shape) { 2075 $new_shape['sysmapid'] = $map['sysmapid']; 2076 $shapes_to_add[] = $new_shape; 2077 } 2078 } 2079 2080 if (array_key_exists('lines', $map)) { 2081 $map['lines'] = array_values($map['lines']); 2082 $shapes = []; 2083 2084 $api_line_rules['fields']['x1']['in'] = '0:'.$map_width; 2085 $api_line_rules['fields']['y1']['in'] = '0:'.$map_height; 2086 $api_line_rules['fields']['x2']['in'] = '0:'.$map_width; 2087 $api_line_rules['fields']['y2']['in'] = '0:'.$map_height; 2088 2089 $path = '/'.($index + 1).'/line'; 2090 2091 foreach ($map['lines'] as &$line) { 2092 $line['x2'] = array_key_exists('x2', $line) ? $line['x2'] : $default_shape_width; 2093 $line['y2'] = array_key_exists('y2', $line) ? $line['y2'] : $default_shape_height; 2094 } 2095 unset($line); 2096 2097 if (!CApiInputValidator::validate($api_line_rules, $map['lines'], $path, $error)) { 2098 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 2099 } 2100 2101 foreach ($map['lines'] as $line) { 2102 $shapes[] = CMapHelper::convertLineToShape($line); 2103 } 2104 2105 $line_diff = zbx_array_diff($shapes, $db_map['lines'], 'sysmap_shapeid'); 2106 2107 foreach ($line_diff['first'] as $new_line) { 2108 if (array_key_exists('sysmap_shapeid', $new_line)) { 2109 self::exception(ZBX_API_ERROR_PARAMETERS, 2110 _('No permissions to referred object or it does not exist!') 2111 ); 2112 } 2113 } 2114 2115 $shapes_to_update = array_merge($shapes_to_update, $line_diff['both']); 2116 $shapes_to_delete = array_merge($shapes_to_delete, $line_diff['second']); 2117 2118 // We need sysmapid for add operations. 2119 foreach ($line_diff['first'] as $new_shape) { 2120 $new_shape['sysmapid'] = $map['sysmapid']; 2121 $shapes_to_add[] = $new_shape; 2122 } 2123 } 2124 2125 // Links. 2126 if (array_key_exists('links', $map)) { 2127 $link_diff = zbx_array_diff($map['links'], $db_map['links'], 'linkid'); 2128 2129 // We need sysmapId for add operations. 2130 foreach ($link_diff['first'] as $newLink) { 2131 $newLink['sysmapid'] = $map['sysmapid']; 2132 2133 $links_to_add[] = $newLink; 2134 } 2135 2136 $links_to_update = array_merge($links_to_update, $link_diff['both']); 2137 $links_to_delete = array_merge($links_to_delete, $link_diff['second']); 2138 } 2139 } 2140 2141 DB::update('sysmaps', $update_maps); 2142 2143 // User shares. 2144 DB::insert('sysmap_user', $shared_users_to_add); 2145 DB::update('sysmap_user', $shared_users_to_update); 2146 2147 if ($shared_userids_to_delete) { 2148 DB::delete('sysmap_user', ['sysmapuserid' => $shared_userids_to_delete]); 2149 } 2150 2151 // User group shares. 2152 DB::insert('sysmap_usrgrp', $shared_user_groups_to_add); 2153 DB::update('sysmap_usrgrp', $shared_user_groups_to_update); 2154 2155 if ($shared_user_groupids_to_delete) { 2156 DB::delete('sysmap_usrgrp', ['sysmapusrgrpid' => $shared_user_groupids_to_delete]); 2157 } 2158 2159 // Urls. 2160 DB::insert('sysmap_url', $urls_to_add); 2161 DB::update('sysmap_url', $urls_to_update); 2162 2163 if ($url_ids_to_delete) { 2164 DB::delete('sysmap_url', ['sysmapurlid' => $url_ids_to_delete]); 2165 } 2166 2167 // Selements. 2168 $new_selementids = ['selementids' => []]; 2169 if ($selements_to_add) { 2170 $new_selementids = $this->createSelements($selements_to_add); 2171 } 2172 2173 if ($selements_to_update) { 2174 $this->updateSelements($selements_to_update); 2175 } 2176 2177 if ($selements_to_delete) { 2178 $this->deleteSelements($selements_to_delete); 2179 } 2180 2181 if ($shapes_to_add) { 2182 $this->createShapes($shapes_to_add); 2183 } 2184 2185 if ($shapes_to_update) { 2186 $this->updateShapes($shapes_to_update); 2187 } 2188 2189 if ($shapes_to_delete) { 2190 $this->deleteShapes($shapes_to_delete); 2191 } 2192 2193 // Links. 2194 if ($links_to_add || $links_to_update) { 2195 $selements_names = []; 2196 foreach ($new_selementids['selementids'] as $key => $selementId) { 2197 $selements_names[$selements_to_add[$key]['selementid']] = $selementId; 2198 } 2199 2200 foreach ($selements_to_update as $selement) { 2201 $selements_names[$selement['selementid']] = $selement['selementid']; 2202 } 2203 2204 foreach ($links_to_add as $key => $link) { 2205 if (array_key_exists($link['selementid1'], $selements_names)) { 2206 $links_to_add[$key]['selementid1'] = $selements_names[$link['selementid1']]; 2207 } 2208 if (array_key_exists($link['selementid2'], $selements_names)) { 2209 $links_to_add[$key]['selementid2'] = $selements_names[$link['selementid2']]; 2210 } 2211 } 2212 2213 foreach ($links_to_update as $key => $link) { 2214 if (array_key_exists($link['selementid1'], $selements_names)) { 2215 $links_to_update[$key]['selementid1'] = $selements_names[$link['selementid1']]; 2216 } 2217 if (array_key_exists($link['selementid2'], $selements_names)) { 2218 $links_to_update[$key]['selementid2'] = $selements_names[$link['selementid2']]; 2219 } 2220 } 2221 2222 unset($selements_names); 2223 } 2224 2225 $new_linkids = ['linkids' => []]; 2226 $update_linkids = ['linkids' => []]; 2227 2228 if ($links_to_add) { 2229 $new_linkids = $this->createLinks($links_to_add); 2230 } 2231 2232 if ($links_to_update) { 2233 $update_linkids = $this->updateLinks($links_to_update); 2234 } 2235 2236 if ($links_to_delete) { 2237 $this->deleteLinks($links_to_delete); 2238 } 2239 2240 // Link triggers. 2241 $link_triggers_to_delete = []; 2242 $link_triggers_to_update = []; 2243 $link_triggers_to_add = []; 2244 2245 foreach ($new_linkids['linkids'] as $key => $linkid) { 2246 if (!array_key_exists('linktriggers', $links_to_add[$key])) { 2247 continue; 2248 } 2249 2250 foreach ($links_to_add[$key]['linktriggers'] as $link_trigger) { 2251 $link_trigger['linkid'] = $linkid; 2252 $link_triggers_to_add[] = $link_trigger; 2253 } 2254 } 2255 2256 $db_links = []; 2257 2258 $link_trigger_resource = DBselect( 2259 'SELECT slt.* FROM sysmaps_link_triggers slt WHERE '.dbConditionInt('slt.linkid', $update_linkids['linkids']) 2260 ); 2261 while ($db_link_trigger = DBfetch($link_trigger_resource)) { 2262 zbx_subarray_push($db_links, $db_link_trigger['linkid'], $db_link_trigger); 2263 } 2264 2265 foreach ($update_linkids['linkids'] as $key => $linkid) { 2266 if (!array_key_exists('linktriggers', $links_to_update[$key])) { 2267 continue; 2268 } 2269 2270 $db_link_triggers = array_key_exists($linkid, $db_links) ? $db_links[$linkid] : []; 2271 $db_link_triggers_diff = zbx_array_diff($links_to_update[$key]['linktriggers'], 2272 $db_link_triggers, 'linktriggerid' 2273 ); 2274 2275 foreach ($db_link_triggers_diff['first'] as $new_link_trigger) { 2276 $new_link_trigger['linkid'] = $linkid; 2277 $link_triggers_to_add[] = $new_link_trigger; 2278 } 2279 2280 $link_triggers_to_update = array_merge($link_triggers_to_update, $db_link_triggers_diff['both']); 2281 $link_triggers_to_delete = array_merge($link_triggers_to_delete, $db_link_triggers_diff['second']); 2282 } 2283 2284 if ($link_triggers_to_delete) { 2285 $this->deleteLinkTriggers($link_triggers_to_delete); 2286 } 2287 2288 if ($link_triggers_to_add) { 2289 $this->createLinkTriggers($link_triggers_to_add); 2290 } 2291 2292 if ($link_triggers_to_update) { 2293 $this->updateLinkTriggers($link_triggers_to_update); 2294 } 2295 2296 return ['sysmapids' => $sysmapids]; 2297 } 2298 2299 /** 2300 * Delete Map. 2301 * 2302 * @param array $sysmapids 2303 * 2304 * @return array 2305 */ 2306 public function delete(array $sysmapids) { 2307 $this->validateDelete($sysmapids); 2308 2309 DB::delete('sysmaps_elements', [ 2310 'elementid' => $sysmapids, 2311 'elementtype' => SYSMAP_ELEMENT_TYPE_MAP 2312 ]); 2313 DB::delete('profiles', [ 2314 'idx' => 'web.maps.sysmapid', 2315 'value_id' => $sysmapids 2316 ]); 2317 DB::delete('profiles', [ 2318 'idx' => 'web.favorite.sysmapids', 2319 'source' => 'sysmapid', 2320 'value_id' => $sysmapids 2321 ]); 2322 DB::delete('sysmaps', ['sysmapid' => $sysmapids]); 2323 2324 return ['sysmapids' => $sysmapids]; 2325 } 2326 2327 protected function addRelatedObjects(array $options, array $result) { 2328 $result = parent::addRelatedObjects($options, $result); 2329 2330 $sysmapIds = array_keys($result); 2331 2332 // adding elements 2333 if ($options['selectSelements'] !== null && $options['selectSelements'] != API_OUTPUT_COUNT) { 2334 $selements = API::getApiService()->select('sysmaps_elements', [ 2335 'output' => $this->outputExtend($options['selectSelements'], ['selementid', 'sysmapid', 'elementtype', 2336 'elementid', 'elementsubtype' 2337 ]), 2338 'filter' => ['sysmapid' => $sysmapIds], 2339 'preservekeys' => true 2340 ]); 2341 $relation_map = $this->createRelationMap($selements, 'sysmapid', 'selementid'); 2342 2343 if ($this->outputIsRequested('elements', $options['selectSelements']) && $selements) { 2344 foreach ($selements as &$selement) { 2345 $selement['elements'] = []; 2346 } 2347 unset($selement); 2348 2349 $selement_triggers = DBselect( 2350 'SELECT st.selementid,st.triggerid,st.selement_triggerid'. 2351 ' FROM sysmap_element_trigger st,triggers tr'. 2352 ' WHERE '.dbConditionInt('st.selementid', array_keys($selements)). 2353 ' AND st.triggerid=tr.triggerid'. 2354 ' ORDER BY tr.priority DESC,st.selement_triggerid' 2355 ); 2356 while ($selement_trigger = DBfetch($selement_triggers)) { 2357 $selements[$selement_trigger['selementid']]['elements'][] = [ 2358 'triggerid' => $selement_trigger['triggerid'] 2359 ]; 2360 if ($selements[$selement_trigger['selementid']]['elementid'] == 0) { 2361 $selements[$selement_trigger['selementid']]['elementid'] = $selement_trigger['triggerid']; 2362 } 2363 } 2364 2365 $single_element_types = [SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP, 2366 SYSMAP_ELEMENT_TYPE_HOST_GROUP 2367 ]; 2368 2369 foreach ($selements as &$selement) { 2370 if (in_array($selement['elementtype'], $single_element_types)) { 2371 switch ($selement['elementtype']) { 2372 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 2373 $field = 'groupid'; 2374 break; 2375 2376 case SYSMAP_ELEMENT_TYPE_HOST: 2377 $field = 'hostid'; 2378 break; 2379 2380 case SYSMAP_ELEMENT_TYPE_MAP: 2381 $field = 'sysmapid'; 2382 break; 2383 } 2384 $selement['elements'][] = [$field => $selement['elementid']]; 2385 } 2386 } 2387 unset($selement); 2388 } 2389 2390 // add selement URLs 2391 if ($this->outputIsRequested('urls', $options['selectSelements'])) { 2392 foreach ($selements as &$selement) { 2393 $selement['urls'] = []; 2394 } 2395 unset($selement); 2396 2397 if (!is_null($options['expandUrls'])) { 2398 $dbMapUrls = DBselect( 2399 'SELECT su.sysmapurlid,su.sysmapid,su.name,su.url,su.elementtype'. 2400 ' FROM sysmap_url su'. 2401 ' WHERE '.dbConditionInt('su.sysmapid', $sysmapIds) 2402 ); 2403 while ($mapUrl = DBfetch($dbMapUrls)) { 2404 foreach ($selements as $snum => $selement) { 2405 if (bccomp($selement['sysmapid'], $mapUrl['sysmapid']) == 0 2406 && (($selement['elementtype'] == $mapUrl['elementtype'] 2407 && $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP 2408 ) 2409 || ($selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS 2410 && $mapUrl['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) 2411 )) { 2412 $selements[$snum]['urls'][] = $mapUrl; 2413 } 2414 } 2415 } 2416 } 2417 2418 $dbSelementUrls = DBselect( 2419 'SELECT seu.sysmapelementurlid,seu.selementid,seu.name,seu.url'. 2420 ' FROM sysmap_element_url seu'. 2421 ' WHERE '.dbConditionInt('seu.selementid', array_keys($selements)) 2422 ); 2423 while ($selementUrl = DBfetch($dbSelementUrls)) { 2424 $selements[$selementUrl['selementid']]['urls'][] = $selementUrl; 2425 } 2426 2427 if (!is_null($options['expandUrls'])) { 2428 $resolve_opt = ['resolve_element_urls' => true]; 2429 $selements = CMacrosResolverHelper::resolveMacrosInMapElements($selements, $resolve_opt); 2430 } 2431 } 2432 2433 if ($this->outputIsRequested('tags', $options['selectSelements']) && $selements) { 2434 $db_tags = DBselect( 2435 'SELECT selementid, tag, value, operator'. 2436 ' FROM sysmaps_element_tag'. 2437 ' WHERE '.dbConditionInt('selementid', array_keys($selements)) 2438 ); 2439 2440 array_walk($selements, function (&$selement) { 2441 $selement['tags'] = []; 2442 }); 2443 2444 while ($db_tag = DBfetch($db_tags)) { 2445 $selements[$db_tag['selementid']]['tags'][] = [ 2446 'tag' => $db_tag['tag'], 2447 'value' => $db_tag['value'], 2448 'operator' => $db_tag['operator'] 2449 ]; 2450 } 2451 } 2452 2453 if ($this->outputIsRequested('permission', $options['selectSelements']) && $selements) { 2454 if ($options['editable']) { 2455 foreach ($selements as &$selement) { 2456 $selement['permission'] = PERM_READ_WRITE; 2457 } 2458 unset($selement); 2459 } 2460 elseif (self::$userData['type'] == USER_TYPE_SUPER_ADMIN) { 2461 foreach ($selements as &$selement) { 2462 $selement['permission'] = PERM_READ; 2463 } 2464 unset($selement); 2465 } 2466 else { 2467 $ids = [ 2468 SYSMAP_ELEMENT_TYPE_HOST_GROUP => [], 2469 SYSMAP_ELEMENT_TYPE_HOST => [], 2470 SYSMAP_ELEMENT_TYPE_TRIGGER => [], 2471 SYSMAP_ELEMENT_TYPE_MAP => [] 2472 ]; 2473 $trigger_selementids = []; 2474 2475 foreach ($selements as &$selement) { 2476 switch ($selement['elementtype']) { 2477 case SYSMAP_ELEMENT_TYPE_HOST_GROUP: 2478 case SYSMAP_ELEMENT_TYPE_HOST: 2479 case SYSMAP_ELEMENT_TYPE_MAP: 2480 $ids[$selement['elementtype']][$selement['elementid']][] = $selement['selementid']; 2481 $selement['permission'] = PERM_NONE; 2482 break; 2483 2484 case SYSMAP_ELEMENT_TYPE_TRIGGER: 2485 $trigger_selementids[$selement['selementid']] = true; 2486 $selement['permission'] = PERM_NONE; 2487 break; 2488 2489 case SYSMAP_ELEMENT_TYPE_IMAGE: 2490 $selement['permission'] = PERM_READ; 2491 break; 2492 } 2493 } 2494 unset($selement); 2495 2496 $db[SYSMAP_ELEMENT_TYPE_HOST_GROUP] = $ids[SYSMAP_ELEMENT_TYPE_HOST_GROUP] 2497 ? API::HostGroup()->get([ 2498 'output' => [], 2499 'groupids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_HOST_GROUP]), 2500 'preservekeys' => true 2501 ]) 2502 : []; 2503 2504 $db[SYSMAP_ELEMENT_TYPE_HOST] = $ids[SYSMAP_ELEMENT_TYPE_HOST] 2505 ? API::Host()->get([ 2506 'output' => [], 2507 'hostids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_HOST]), 2508 'preservekeys' => true 2509 ]) 2510 : []; 2511 2512 $db[SYSMAP_ELEMENT_TYPE_MAP] = $ids[SYSMAP_ELEMENT_TYPE_MAP] 2513 ? API::Map()->get([ 2514 'output' => [], 2515 'sysmapids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_MAP]), 2516 'preservekeys' => true 2517 ]) 2518 : []; 2519 2520 if ($trigger_selementids) { 2521 $db_selement_triggers = DBselect( 2522 'SELECT st.selementid,st.triggerid'. 2523 ' FROM sysmap_element_trigger st'. 2524 ' WHERE '.dbConditionInt('st.selementid', array_keys($trigger_selementids)) 2525 ); 2526 2527 while ($db_selement_trigger = DBfetch($db_selement_triggers)) { 2528 $ids[SYSMAP_ELEMENT_TYPE_TRIGGER][$db_selement_trigger['triggerid']][] = 2529 $db_selement_trigger['selementid']; 2530 } 2531 } 2532 2533 $db[SYSMAP_ELEMENT_TYPE_TRIGGER] = $ids[SYSMAP_ELEMENT_TYPE_TRIGGER] 2534 ? API::Trigger()->get([ 2535 'output' => [], 2536 'triggerids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_TRIGGER]), 2537 'preservekeys' => true 2538 ]) 2539 : []; 2540 2541 foreach ($ids as $elementtype => $elementids) { 2542 foreach ($elementids as $elementid => $selementids) { 2543 if (array_key_exists($elementid, $db[$elementtype])) { 2544 foreach ($selementids as $selementid) { 2545 $selements[$selementid]['permission'] = PERM_READ; 2546 } 2547 } 2548 } 2549 } 2550 } 2551 } 2552 2553 foreach ($selements as &$selement) { 2554 unset($selement['elementid']); 2555 } 2556 unset($selement); 2557 2558 $selements = $this->unsetExtraFields($selements, 2559 ['sysmapid', 'selementid', 'elementtype', 'elementsubtype'], 2560 $options['selectSelements'] 2561 ); 2562 $result = $relation_map->mapMany($result, $selements, 'selements'); 2563 } 2564 2565 $shape_types = []; 2566 if ($options['selectShapes'] !== null && $options['selectShapes'] != API_OUTPUT_COUNT) { 2567 $shape_types = [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE]; 2568 } 2569 2570 if ($options['selectLines'] !== null && $options['selectLines'] != API_OUTPUT_COUNT) { 2571 $shape_types[] = SYSMAP_SHAPE_TYPE_LINE; 2572 } 2573 2574 // Adding shapes. 2575 if ($shape_types) { 2576 $fields = API_OUTPUT_EXTEND; 2577 2578 if ($options['selectShapes'] != API_OUTPUT_EXTEND && $options['selectLines'] != API_OUTPUT_EXTEND) { 2579 $fields = ['sysmap_shapeid', 'sysmapid', 'type']; 2580 $mapping = [ 2581 'x1' => 'x', 2582 'y1' => 'y', 2583 'x2' => 'width', 2584 'y2' => 'height', 2585 'line_type' => 'border_type', 2586 'line_width' => 'border_width', 2587 'line_color' => 'border_color' 2588 ]; 2589 2590 if (is_array($options['selectLines'])) { 2591 foreach ($mapping as $source_field => $target_field) { 2592 if (in_array($source_field, $options['selectLines'])) { 2593 $fields[] = $target_field; 2594 } 2595 } 2596 } 2597 2598 if (is_array($options['selectShapes'])) { 2599 $fields = array_merge($fields, $options['selectShapes']); 2600 } 2601 } 2602 2603 $db_shapes = API::getApiService()->select('sysmap_shape', [ 2604 'output' => $fields, 2605 'filter' => ['sysmapid' => $sysmapIds, 'type' => $shape_types], 2606 'preservekeys' => true 2607 ]); 2608 2609 $shapes = []; 2610 $lines = []; 2611 foreach ($db_shapes as $key => $db_shape) { 2612 if ($db_shape['type'] == SYSMAP_SHAPE_TYPE_LINE) { 2613 $lines[$key] = CMapHelper::convertShapeToLine($db_shape); 2614 } 2615 else { 2616 $shapes[$key] = $db_shape; 2617 } 2618 } 2619 2620 $relation_map = $this->createRelationMap($db_shapes, 'sysmapid', 'sysmap_shapeid'); 2621 2622 if ($options['selectShapes'] !== null && $options['selectShapes'] != API_OUTPUT_COUNT) { 2623 $shapes = $this->unsetExtraFields($shapes, ['sysmap_shapeid', 'type', 'x', 'y', 'width', 'height', 2624 'text', 'font', 'font_size', 'font_color', 'text_halign', 'text_valign', 'border_type', 2625 'border_width', 'border_color', 'background_color', 'zindex' 2626 ], $options['selectShapes']); 2627 $shapes = $this->unsetExtraFields($shapes, ['sysmapid'], null); 2628 2629 $result = $relation_map->mapMany($result, $shapes, 'shapes'); 2630 } 2631 2632 if ($options['selectLines'] !== null && $options['selectLines'] != API_OUTPUT_COUNT) { 2633 $lines = $this->unsetExtraFields($lines, ['sysmap_shapeid', 'x1', 'x2', 'y1', 'y2', 'line_type', 2634 'line_width', 'line_color', 'zindex' 2635 ], $options['selectLines']); 2636 $lines = $this->unsetExtraFields($lines, ['sysmapid', 'type'], null); 2637 2638 $result = $relation_map->mapMany($result, $lines, 'lines'); 2639 } 2640 } 2641 2642 // adding icon maps 2643 if ($options['selectIconMap'] !== null && $options['selectIconMap'] != API_OUTPUT_COUNT) { 2644 $iconMaps = API::getApiService()->select($this->tableName(), [ 2645 'output' => ['sysmapid', 'iconmapid'], 2646 'filter' => ['sysmapid' => $sysmapIds] 2647 ]); 2648 2649 $relation_map = $this->createRelationMap($iconMaps, 'sysmapid', 'iconmapid'); 2650 2651 $iconMaps = API::IconMap()->get([ 2652 'output' => $this->outputExtend($options['selectIconMap'], ['iconmapid']), 2653 'iconmapids' => zbx_objectValues($iconMaps, 'iconmapid'), 2654 'preservekeys' => true 2655 ]); 2656 2657 $iconMaps = $this->unsetExtraFields($iconMaps, ['iconmapid'], $options['selectIconMap']); 2658 2659 $result = $relation_map->mapOne($result, $iconMaps, 'iconmap'); 2660 } 2661 2662 // adding links 2663 if ($options['selectLinks'] !== null && $options['selectLinks'] != API_OUTPUT_COUNT) { 2664 $links = API::getApiService()->select('sysmaps_links', [ 2665 'output' => $this->outputExtend($options['selectLinks'], ['sysmapid', 'linkid']), 2666 'filter' => ['sysmapid' => $sysmapIds], 2667 'preservekeys' => true 2668 ]); 2669 $relation_map = $this->createRelationMap($links, 'sysmapid', 'linkid'); 2670 2671 // add link triggers 2672 if ($this->outputIsRequested('linktriggers', $options['selectLinks'])) { 2673 $linkTriggers = DBFetchArrayAssoc(DBselect( 2674 'SELECT DISTINCT slt.*'. 2675 ' FROM sysmaps_link_triggers slt'. 2676 ' WHERE '.dbConditionInt('slt.linkid', $relation_map->getRelatedIds()) 2677 ), 'linktriggerid'); 2678 $linkTriggerRelationMap = $this->createRelationMap($linkTriggers, 'linkid', 'linktriggerid'); 2679 $links = $linkTriggerRelationMap->mapMany($links, $linkTriggers, 'linktriggers'); 2680 } 2681 2682 if ($this->outputIsRequested('permission', $options['selectLinks']) && $links) { 2683 if ($options['editable']) { 2684 foreach ($links as &$link) { 2685 $link['permission'] = PERM_READ_WRITE; 2686 } 2687 unset($link); 2688 } 2689 elseif (self::$userData['type'] == USER_TYPE_SUPER_ADMIN) { 2690 foreach ($links as &$link) { 2691 $link['permission'] = PERM_READ; 2692 } 2693 unset($link); 2694 } 2695 else { 2696 $db_link_triggers = DBselect( 2697 'SELECT slt.linkid,slt.triggerid'. 2698 ' FROM sysmaps_link_triggers slt'. 2699 ' WHERE '.dbConditionInt('slt.linkid', array_keys($links)) 2700 ); 2701 2702 $triggerids = []; 2703 $has_triggers = []; 2704 2705 while ($db_link_trigger = DBfetch($db_link_triggers)) { 2706 $triggerids[$db_link_trigger['triggerid']][] = $db_link_trigger['linkid']; 2707 $has_triggers[$db_link_trigger['linkid']] = true; 2708 } 2709 2710 foreach ($links as &$link) { 2711 $link['permission'] = array_key_exists($link['linkid'], $has_triggers) ? PERM_NONE : PERM_READ; 2712 } 2713 unset($link); 2714 2715 $db_triggers = $triggerids 2716 ? API::Trigger()->get([ 2717 'output' => [], 2718 'triggerids' => array_keys($triggerids), 2719 'preservekeys' => true 2720 ]) 2721 : []; 2722 2723 foreach ($triggerids as $triggerid => $linkids) { 2724 if (array_key_exists($triggerid, $db_triggers)) { 2725 foreach ($linkids as $linkid) { 2726 $links[$linkid]['permission'] = PERM_READ; 2727 } 2728 } 2729 } 2730 } 2731 } 2732 2733 $links = $this->unsetExtraFields($links, ['sysmapid', 'linkid'], $options['selectLinks']); 2734 $result = $relation_map->mapMany($result, $links, 'links'); 2735 } 2736 2737 // adding urls 2738 if ($options['selectUrls'] !== null && $options['selectUrls'] != API_OUTPUT_COUNT) { 2739 $links = API::getApiService()->select('sysmap_url', [ 2740 'output' => $this->outputExtend($options['selectUrls'], ['sysmapid', 'sysmapurlid']), 2741 'filter' => ['sysmapid' => $sysmapIds], 2742 'preservekeys' => true 2743 ]); 2744 $relation_map = $this->createRelationMap($links, 'sysmapid', 'sysmapurlid'); 2745 2746 $links = $this->unsetExtraFields($links, ['sysmapid', 'sysmapurlid'], $options['selectUrls']); 2747 $result = $relation_map->mapMany($result, $links, 'urls'); 2748 } 2749 2750 // Adding user shares. 2751 if ($options['selectUsers'] !== null && $options['selectUsers'] != API_OUTPUT_COUNT) { 2752 $userids = []; 2753 $relation_map = $this->createRelationMap($result, 'sysmapid', 'userid', 'sysmap_user'); 2754 $related_ids = $relation_map->getRelatedIds(); 2755 2756 if ($related_ids) { 2757 // Get all allowed users. 2758 $users = API::User()->get([ 2759 'output' => ['userid'], 2760 'userids' => $related_ids, 2761 'preservekeys' => true 2762 ]); 2763 2764 $userids = zbx_objectValues($users, 'userid'); 2765 } 2766 2767 if ($userids) { 2768 $users = API::getApiService()->select('sysmap_user', [ 2769 'output' => $this->outputExtend($options['selectUsers'], ['sysmapid', 'userid']), 2770 'filter' => ['sysmapid' => $sysmapIds, 'userid' => $userids], 2771 'preservekeys' => true 2772 ]); 2773 2774 $relation_map = $this->createRelationMap($users, 'sysmapid', 'sysmapuserid'); 2775 2776 $users = $this->unsetExtraFields($users, ['sysmapuserid', 'userid', 'permission'], 2777 $options['selectUsers'] 2778 ); 2779 2780 foreach ($users as &$user) { 2781 unset($user['sysmapid']); 2782 } 2783 unset($user); 2784 2785 $result = $relation_map->mapMany($result, $users, 'users'); 2786 } 2787 else { 2788 foreach ($result as &$row) { 2789 $row['users'] = []; 2790 } 2791 unset($row); 2792 } 2793 } 2794 2795 // Adding user group shares. 2796 if ($options['selectUserGroups'] !== null && $options['selectUserGroups'] != API_OUTPUT_COUNT) { 2797 $groupids = []; 2798 $relation_map = $this->createRelationMap($result, 'sysmapid', 'usrgrpid', 'sysmap_usrgrp'); 2799 $related_ids = $relation_map->getRelatedIds(); 2800 2801 if ($related_ids) { 2802 // Get all allowed groups. 2803 $groups = API::UserGroup()->get([ 2804 'output' => ['usrgrpid'], 2805 'usrgrpids' => $related_ids, 2806 'preservekeys' => true 2807 ]); 2808 2809 $groupids = zbx_objectValues($groups, 'usrgrpid'); 2810 } 2811 2812 if ($groupids) { 2813 $user_groups = API::getApiService()->select('sysmap_usrgrp', [ 2814 'output' => $this->outputExtend($options['selectUserGroups'], ['sysmapid', 'usrgrpid']), 2815 'filter' => ['sysmapid' => $sysmapIds, 'usrgrpid' => $groupids], 2816 'preservekeys' => true 2817 ]); 2818 2819 $relation_map = $this->createRelationMap($user_groups, 'sysmapid', 'sysmapusrgrpid'); 2820 2821 $user_groups = $this->unsetExtraFields($user_groups, ['sysmapusrgrpid', 'usrgrpid', 'permission'], 2822 $options['selectUserGroups'] 2823 ); 2824 2825 foreach ($user_groups as &$user_group) { 2826 unset($user_group['sysmapid']); 2827 } 2828 unset($user_group); 2829 2830 $result = $relation_map->mapMany($result, $user_groups, 'userGroups'); 2831 } 2832 else { 2833 foreach ($result as &$row) { 2834 $row['userGroups'] = []; 2835 } 2836 unset($row); 2837 } 2838 } 2839 2840 return $result; 2841 } 2842} 2843