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