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 * Get ipmi auth type label by it's number. 24 * 25 * @param null|int $type 26 * 27 * @return array|string 28 */ 29function ipmiAuthTypes($type = null) { 30 $types = [ 31 IPMI_AUTHTYPE_DEFAULT => _('Default'), 32 IPMI_AUTHTYPE_NONE => _('None'), 33 IPMI_AUTHTYPE_MD2 => _('MD2'), 34 IPMI_AUTHTYPE_MD5 => _('MD5'), 35 IPMI_AUTHTYPE_STRAIGHT => _('Straight'), 36 IPMI_AUTHTYPE_OEM => _('OEM'), 37 IPMI_AUTHTYPE_RMCP_PLUS => _('RMCP+') 38 ]; 39 40 if (is_null($type)) { 41 return $types; 42 } 43 elseif (isset($types[$type])) { 44 return $types[$type]; 45 } 46 else { 47 return _('Unknown'); 48 } 49} 50 51/** 52 * Get ipmi auth privilege label by it's number. 53 * 54 * @param null|int $type 55 * 56 * @return array|string 57 */ 58function ipmiPrivileges($type = null) { 59 $types = [ 60 IPMI_PRIVILEGE_CALLBACK => _('Callback'), 61 IPMI_PRIVILEGE_USER => _('User'), 62 IPMI_PRIVILEGE_OPERATOR => _('Operator'), 63 IPMI_PRIVILEGE_ADMIN => _('Admin'), 64 IPMI_PRIVILEGE_OEM => _('OEM') 65 ]; 66 67 if (is_null($type)) { 68 return $types; 69 } 70 elseif (isset($types[$type])) { 71 return $types[$type]; 72 } 73 else { 74 return _('Unknown'); 75 } 76} 77 78/** 79 * Get info about what host inventory fields we have, their numbers and names. 80 * Example of usage: 81 * $inventories = getHostInventories(); 82 * echo $inventories[1]['db_field']; // host_networks 83 * echo $inventories[1]['title']; // Host networks 84 * echo $inventories[1]['nr']; // 1 85 * 86 * @param bool $orderedByTitle whether an array should be ordered by field title, not by number 87 * 88 * @return array 89 */ 90function getHostInventories($orderedByTitle = false) { 91 /* 92 * WARNING! Before modifying this array, make sure changes are synced with C 93 * C analog is located in function DBget_inventory_field() in src/libs/zbxdbhigh/db.c 94 */ 95 $inventoryFields = [ 96 1 => [ 97 'nr' => 1, 98 'db_field' => 'type', 99 'title' => _('Type') 100 ], 101 2 => [ 102 'nr' => 2, 103 'db_field' => 'type_full', 104 'title' => _('Type (Full details)') 105 ], 106 3 => [ 107 'nr' => 3, 108 'db_field' => 'name', 109 'title' => _('Name') 110 ], 111 4 => [ 112 'nr' => 4, 113 'db_field' => 'alias', 114 'title' => _('Alias') 115 ], 116 5 => [ 117 'nr' => 5, 118 'db_field' => 'os', 119 'title' => _('OS') 120 ], 121 6 => [ 122 'nr' => 6, 123 'db_field' => 'os_full', 124 'title' => _('OS (Full details)') 125 ], 126 7 => [ 127 'nr' => 7, 128 'db_field' => 'os_short', 129 'title' => _('OS (Short)') 130 ], 131 8 => [ 132 'nr' => 8, 133 'db_field' => 'serialno_a', 134 'title' => _('Serial number A') 135 ], 136 9 => [ 137 'nr' => 9, 138 'db_field' => 'serialno_b', 139 'title' => _('Serial number B') 140 ], 141 10 => [ 142 'nr' => 10, 143 'db_field' => 'tag', 144 'title' => _('Tag') 145 ], 146 11 => [ 147 'nr' => 11, 148 'db_field' => 'asset_tag', 149 'title' => _('Asset tag') 150 ], 151 12 => [ 152 'nr' => 12, 153 'db_field' => 'macaddress_a', 154 'title' => _('MAC address A') 155 ], 156 13 => [ 157 'nr' => 13, 158 'db_field' => 'macaddress_b', 159 'title' => _('MAC address B') 160 ], 161 14 => [ 162 'nr' => 14, 163 'db_field' => 'hardware', 164 'title' => _('Hardware') 165 ], 166 15 => [ 167 'nr' => 15, 168 'db_field' => 'hardware_full', 169 'title' => _('Hardware (Full details)') 170 ], 171 16 => [ 172 'nr' => 16, 173 'db_field' => 'software', 174 'title' => _('Software') 175 ], 176 17 => [ 177 'nr' => 17, 178 'db_field' => 'software_full', 179 'title' => _('Software (Full details)') 180 ], 181 18 => [ 182 'nr' => 18, 183 'db_field' => 'software_app_a', 184 'title' => _('Software application A') 185 ], 186 19 => [ 187 'nr' => 19, 188 'db_field' => 'software_app_b', 189 'title' => _('Software application B') 190 ], 191 20 => [ 192 'nr' => 20, 193 'db_field' => 'software_app_c', 194 'title' => _('Software application C') 195 ], 196 21 => [ 197 'nr' => 21, 198 'db_field' => 'software_app_d', 199 'title' => _('Software application D') 200 ], 201 22 => [ 202 'nr' => 22, 203 'db_field' => 'software_app_e', 204 'title' => _('Software application E') 205 ], 206 23 => [ 207 'nr' => 23, 208 'db_field' => 'contact', 209 'title' => _('Contact') 210 ], 211 24 => [ 212 'nr' => 24, 213 'db_field' => 'location', 214 'title' => _('Location') 215 ], 216 25 => [ 217 'nr' => 25, 218 'db_field' => 'location_lat', 219 'title' => _('Location latitude') 220 ], 221 26 => [ 222 'nr' => 26, 223 'db_field' => 'location_lon', 224 'title' => _('Location longitude') 225 ], 226 27 => [ 227 'nr' => 27, 228 'db_field' => 'notes', 229 'title' => _('Notes') 230 ], 231 28 => [ 232 'nr' => 28, 233 'db_field' => 'chassis', 234 'title' => _('Chassis') 235 ], 236 29 => [ 237 'nr' => 29, 238 'db_field' => 'model', 239 'title' => _('Model') 240 ], 241 30 => [ 242 'nr' => 30, 243 'db_field' => 'hw_arch', 244 'title' => _('HW architecture') 245 ], 246 31 => [ 247 'nr' => 31, 248 'db_field' => 'vendor', 249 'title' => _('Vendor') 250 ], 251 32 => [ 252 'nr' => 32, 253 'db_field' => 'contract_number', 254 'title' => _('Contract number') 255 ], 256 33 => [ 257 'nr' => 33, 258 'db_field' => 'installer_name', 259 'title' => _('Installer name') 260 ], 261 34 => [ 262 'nr' => 34, 263 'db_field' => 'deployment_status', 264 'title' => _('Deployment status') 265 ], 266 35 => [ 267 'nr' => 35, 268 'db_field' => 'url_a', 269 'title' => _('URL A') 270 ], 271 36 => [ 272 'nr' => 36, 273 'db_field' => 'url_b', 274 'title' => _('URL B') 275 ], 276 37 => [ 277 'nr' => 37, 278 'db_field' => 'url_c', 279 'title' => _('URL C') 280 ], 281 38 => [ 282 'nr' => 38, 283 'db_field' => 'host_networks', 284 'title' => _('Host networks') 285 ], 286 39 => [ 287 'nr' => 39, 288 'db_field' => 'host_netmask', 289 'title' => _('Host subnet mask') 290 ], 291 40 => [ 292 'nr' => 40, 293 'db_field' => 'host_router', 294 'title' => _('Host router') 295 ], 296 41 => [ 297 'nr' => 41, 298 'db_field' => 'oob_ip', 299 'title' => _('OOB IP address') 300 ], 301 42 => [ 302 'nr' => 42, 303 'db_field' => 'oob_netmask', 304 'title' => _('OOB subnet mask') 305 ], 306 43 => [ 307 'nr' => 43, 308 'db_field' => 'oob_router', 309 'title' => _('OOB router') 310 ], 311 44 => [ 312 'nr' => 44, 313 'db_field' => 'date_hw_purchase', 314 'title' => _('Date HW purchased') 315 ], 316 45 => [ 317 'nr' => 45, 318 'db_field' => 'date_hw_install', 319 'title' => _('Date HW installed') 320 ], 321 46 => [ 322 'nr' => 46, 323 'db_field' => 'date_hw_expiry', 324 'title' => _('Date HW maintenance expires') 325 ], 326 47 => [ 327 'nr' => 47, 328 'db_field' => 'date_hw_decomm', 329 'title' => _('Date HW decommissioned') 330 ], 331 48 => [ 332 'nr' => 48, 333 'db_field' => 'site_address_a', 334 'title' => _('Site address A') 335 ], 336 49 => [ 337 'nr' => 49, 338 'db_field' => 'site_address_b', 339 'title' => _('Site address B') 340 ], 341 50 => [ 342 'nr' => 50, 343 'db_field' => 'site_address_c', 344 'title' => _('Site address C') 345 ], 346 51 => [ 347 'nr' => 51, 348 'db_field' => 'site_city', 349 'title' => _('Site city') 350 ], 351 52 => [ 352 'nr' => 52, 353 'db_field' => 'site_state', 354 'title' => _('Site state / province') 355 ], 356 53 => [ 357 'nr' => 53, 358 'db_field' => 'site_country', 359 'title' => _('Site country') 360 ], 361 54 => [ 362 'nr' => 54, 363 'db_field' => 'site_zip', 364 'title' => _('Site ZIP / postal') 365 ], 366 55 => [ 367 'nr' => 55, 368 'db_field' => 'site_rack', 369 'title' => _('Site rack location') 370 ], 371 56 => [ 372 'nr' => 56, 373 'db_field' => 'site_notes', 374 'title' => _('Site notes') 375 ], 376 57 => [ 377 'nr' => 57, 378 'db_field' => 'poc_1_name', 379 'title' => _('Primary POC name') 380 ], 381 58 => [ 382 'nr' => 58, 383 'db_field' => 'poc_1_email', 384 'title' => _('Primary POC email') 385 ], 386 59 => [ 387 'nr' => 59, 388 'db_field' => 'poc_1_phone_a', 389 'title' => _('Primary POC phone A') 390 ], 391 60 => [ 392 'nr' => 60, 393 'db_field' => 'poc_1_phone_b', 394 'title' => _('Primary POC phone B') 395 ], 396 61 => [ 397 'nr' => 61, 398 'db_field' => 'poc_1_cell', 399 'title' => _('Primary POC cell') 400 ], 401 62 => [ 402 'nr' => 62, 403 'db_field' => 'poc_1_screen', 404 'title' => _('Primary POC screen name') 405 ], 406 63 => [ 407 'nr' => 63, 408 'db_field' => 'poc_1_notes', 409 'title' => _('Primary POC notes') 410 ], 411 64 => [ 412 'nr' => 64, 413 'db_field' => 'poc_2_name', 414 'title' => _('Secondary POC name') 415 ], 416 65 => [ 417 'nr' => 65, 418 'db_field' => 'poc_2_email', 419 'title' => _('Secondary POC email') 420 ], 421 66 => [ 422 'nr' => 66, 423 'db_field' => 'poc_2_phone_a', 424 'title' => _('Secondary POC phone A') 425 ], 426 67 => [ 427 'nr' => 67, 428 'db_field' => 'poc_2_phone_b', 429 'title' => _('Secondary POC phone B') 430 ], 431 68 => [ 432 'nr' => 68, 433 'db_field' => 'poc_2_cell', 434 'title' => _('Secondary POC cell') 435 ], 436 69 => [ 437 'nr' => 69, 438 'db_field' => 'poc_2_screen', 439 'title' => _('Secondary POC screen name') 440 ], 441 70 => [ 442 'nr' => 70, 443 'db_field' => 'poc_2_notes', 444 'title' => _('Secondary POC notes') 445 ] 446 ]; 447 448 // array is ordered by number by default, should we change that and order by title? 449 if ($orderedByTitle) { 450 function sortInventoriesByTitle($a, $b) { 451 return strcmp($a['title'], $b['title']); 452 } 453 uasort($inventoryFields, 'sortInventoriesByTitle'); 454 } 455 456 return $inventoryFields; 457} 458 459function hostInterfaceTypeNumToName($type) { 460 switch ($type) { 461 case INTERFACE_TYPE_AGENT: 462 $name = _('agent'); 463 break; 464 case INTERFACE_TYPE_SNMP: 465 $name = _('SNMP'); 466 break; 467 case INTERFACE_TYPE_JMX: 468 $name = _('JMX'); 469 break; 470 case INTERFACE_TYPE_IPMI: 471 $name = _('IPMI'); 472 break; 473 default: 474 throw new Exception(_('Unknown interface type.')); 475 } 476 477 return $name; 478} 479 480function get_hostgroup_by_groupid($groupid) { 481 $groups = DBfetch(DBselect('SELECT g.* FROM groups g WHERE g.groupid='.zbx_dbstr($groupid))); 482 483 if ($groups) { 484 return $groups; 485 } 486 487 error(_s('No host groups with groupid "%s".', $groupid)); 488 489 return false; 490} 491 492function get_host_by_itemid($itemids) { 493 $res_array = is_array($itemids); 494 zbx_value2array($itemids); 495 $result = false; 496 $hosts = []; 497 498 $db_hostsItems = DBselect( 499 'SELECT i.itemid,h.*'. 500 ' FROM hosts h,items i'. 501 ' WHERE i.hostid=h.hostid'. 502 ' AND '.dbConditionInt('i.itemid', $itemids) 503 ); 504 while ($hostItem = DBfetch($db_hostsItems)) { 505 $result = true; 506 $hosts[$hostItem['itemid']] = $hostItem; 507 } 508 509 if (!$res_array) { 510 foreach ($hosts as $itemid => $host) { 511 $result = $host; 512 } 513 } 514 elseif ($result) { 515 $result = $hosts; 516 unset($hosts); 517 } 518 519 return $result; 520} 521 522function get_host_by_hostid($hostid, $no_error_message = 0) { 523 $row = DBfetch(DBselect('SELECT h.* FROM hosts h WHERE h.hostid='.zbx_dbstr($hostid))); 524 525 if ($row) { 526 return $row; 527 } 528 529 if ($no_error_message == 0) { 530 error(_s('No host with hostid "%s".', $hostid)); 531 } 532 533 return false; 534} 535 536function updateHostStatus($hostids, $status) { 537 zbx_value2array($hostids); 538 539 $hostIds = []; 540 $oldStatus = ($status == HOST_STATUS_MONITORED ? HOST_STATUS_NOT_MONITORED : HOST_STATUS_MONITORED); 541 542 $db_hosts = DBselect( 543 'SELECT h.hostid,h.host,h.status'. 544 ' FROM hosts h'. 545 ' WHERE '.dbConditionInt('h.hostid', $hostids). 546 ' AND h.status='.zbx_dbstr($oldStatus) 547 ); 548 while ($host = DBfetch($db_hosts)) { 549 $hostIds[] = $host['hostid']; 550 551 $host_new = $host; 552 $host_new['status'] = $status; 553 add_audit_ext(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_HOST, $host['hostid'], $host['host'], 'hosts', $host, $host_new); 554 info(_s('Updated status of host "%1$s".', $host['host'])); 555 } 556 557 return DB::update('hosts', [ 558 'values' => ['status' => $status], 559 'where' => ['hostid' => $hostIds] 560 ]); 561} 562 563function get_application_by_applicationid($applicationid, $no_error_message = 0) { 564 $row = DBfetch(DBselect('SELECT a.* FROM applications a WHERE a.applicationid='.zbx_dbstr($applicationid))); 565 566 if ($row) { 567 return $row; 568 } 569 570 if ($no_error_message == 0) { 571 error(_s('No application with ID "%1$s".', $applicationid)); 572 } 573 574 return false; 575} 576 577/** 578 * Returns the farthest application ancestor for each given application. 579 * 580 * @param array $applicationIds 581 * @param array $templateApplicationIds array with parent application IDs as keys and arrays of child application 582 * IDs as values 583 * 584 * @return array an array with child IDs as keys and arrays of ancestor IDs as values 585 */ 586function getApplicationSourceParentIds(array $applicationIds, array $templateApplicationIds = []) { 587 $query = DBSelect( 588 'SELECT at.applicationid,at.templateid'. 589 ' FROM application_template at'. 590 ' WHERE '.dbConditionInt('at.applicationid', $applicationIds) 591 ); 592 593 $applicationIds = []; 594 $unsetApplicationIds = []; 595 while ($applicationTemplate = DBfetch($query)) { 596 // check if we already have an application inherited from the current application 597 // if we do - copy all of its child applications to the parent template 598 if (isset($templateApplicationIds[$applicationTemplate['applicationid']])) { 599 $templateApplicationIds[$applicationTemplate['templateid']] = $templateApplicationIds[$applicationTemplate['applicationid']]; 600 $unsetApplicationIds[$applicationTemplate['applicationid']] = $applicationTemplate['applicationid']; 601 } 602 // if no - just add the application 603 else { 604 $templateApplicationIds[$applicationTemplate['templateid']][] = $applicationTemplate['applicationid']; 605 } 606 $applicationIds[$applicationTemplate['applicationid']] = $applicationTemplate['templateid']; 607 } 608 609 // unset children of all applications that we found a new parent for 610 foreach ($unsetApplicationIds as $applicationId) { 611 unset($templateApplicationIds[$applicationId]); 612 } 613 614 // continue while we still have new applications to check 615 if ($applicationIds) { 616 return getApplicationSourceParentIds($applicationIds, $templateApplicationIds); 617 } 618 else { 619 // return an inverse hash with application IDs as keys and arrays of parent application IDs as values 620 $result = []; 621 foreach ($templateApplicationIds as $templateId => $applicationIds) { 622 foreach ($applicationIds as $applicationId) { 623 $result[$applicationId][] = $templateId; 624 } 625 } 626 627 return $result; 628 } 629} 630 631/** 632 * Returns the farthest host prototype ancestor for each given host prototype. 633 * 634 * @param array $hostPrototypeIds 635 * @param array $templateHostPrototypeIds array with parent host prototype IDs as keys and arrays of child host 636 * prototype IDs as values 637 * 638 * @return array an array of child ID - ancestor ID pairs 639 */ 640function getHostPrototypeSourceParentIds(array $hostPrototypeIds, array $templateHostPrototypeIds = []) { 641 $query = DBSelect( 642 'SELECT h.hostid,h.templateid'. 643 ' FROM hosts h'. 644 ' WHERE '.dbConditionInt('h.hostid', $hostPrototypeIds). 645 ' AND h.templateid>0' 646 ); 647 648 $hostPrototypeIds = []; 649 while ($hostPrototype = DBfetch($query)) { 650 // check if we already have host prototype inherited from the current host prototype 651 // if we do - move all of its child prototypes to the parent template 652 if (isset($templateHostPrototypeIds[$hostPrototype['hostid']])) { 653 $templateHostPrototypeIds[$hostPrototype['templateid']] = $templateHostPrototypeIds[$hostPrototype['hostid']]; 654 unset($templateHostPrototypeIds[$hostPrototype['hostid']]); 655 } 656 // if no - just add the prototype 657 else { 658 $templateHostPrototypeIds[$hostPrototype['templateid']][] = $hostPrototype['hostid']; 659 $hostPrototypeIds[] = $hostPrototype['templateid']; 660 } 661 } 662 663 // continue while we still have new host prototypes to check 664 if ($hostPrototypeIds) { 665 return getHostPrototypeSourceParentIds($hostPrototypeIds, $templateHostPrototypeIds); 666 } 667 else { 668 // return an inverse hash with prototype IDs as keys and parent prototype IDs as values 669 $result = []; 670 foreach ($templateHostPrototypeIds as $templateId => $hostIds) { 671 foreach ($hostIds as $hostId) { 672 $result[$hostId] = $templateId; 673 } 674 } 675 676 return $result; 677 } 678} 679 680/** 681 * Get host ids of hosts which $groupids can be unlinked from. 682 * if $hostids is passed, function will check only these hosts. 683 * 684 * @param array $groupids 685 * @param array $hostids 686 * 687 * @return array 688 */ 689function getUnlinkableHostIds(array $groupIds, array $hostIds) { 690 if (!$hostIds) { 691 return []; 692 } 693 694 $dbResult = DBselect( 695 'SELECT hg.hostid'. 696 ' FROM hosts_groups hg'. 697 ' WHERE '.dbConditionInt('hg.groupid', $groupIds, true). 698 ' AND '.dbConditionInt('hg.hostid', $hostIds). 699 ' GROUP BY hg.hostid' 700 ); 701 702 $unlinkableHostIds = []; 703 while ($dbRow = DBfetch($dbResult)) { 704 $unlinkableHostIds[] = $dbRow['hostid']; 705 } 706 707 return $unlinkableHostIds; 708} 709 710function getDeletableHostGroupIds(array $groupIds) { 711 // selecting the list of hosts linked to the host groups 712 $dbResult = DBselect( 713 'SELECT hg.hostid'. 714 ' FROM hosts_groups hg'. 715 ' WHERE '.dbConditionInt('hg.groupid', $groupIds) 716 ); 717 718 $linkedHostIds = []; 719 while ($dbRow = DBfetch($dbResult)) { 720 $linkedHostIds[] = $dbRow['hostid']; 721 } 722 723 // the list of hosts which can be unlinked from the host groups 724 $hostIds = getUnlinkableHostIds($groupIds, $linkedHostIds); 725 726 $dbResult = DBselect( 727 'SELECT g.groupid'. 728 ' FROM groups g'. 729 ' WHERE g.internal='.ZBX_NOT_INTERNAL_GROUP. 730 ' AND '.dbConditionInt('g.groupid', $groupIds). 731 ' AND NOT EXISTS ('. 732 'SELECT NULL'. 733 ' FROM hosts_groups hg'. 734 ' WHERE g.groupid=hg.groupid'. 735 ($hostIds ? ' AND '.dbConditionInt('hg.hostid', $hostIds, true) : ''). 736 ')' 737 ); 738 739 $deletableGroupIds = []; 740 while ($dbRow = DBfetch($dbResult)) { 741 $deletableGroupIds[$dbRow['groupid']] = $dbRow['groupid']; 742 } 743 744 return $deletableGroupIds; 745} 746 747function isTemplate($hostId) { 748 $dbHost = DBfetch(DBselect('SELECT h.status FROM hosts h WHERE h.hostid='.zbx_dbstr($hostId))); 749 750 return ($dbHost && $dbHost['status'] == HOST_STATUS_TEMPLATE); 751} 752 753/** 754 * Get list of inherited macros by host ids. 755 * 756 * Returns an array like: 757 * array( 758 * '{$MACRO}' => array( 759 * 'macro' => '{$MACRO}', 760 * 'template' => array( <- optional 761 * 'value' => 'template-level value' 762 * 'templateid' => 10001, 763 * 'name' => 'Template OS Linux' 764 * ), 765 * 'global' => array( <- optional 766 * 'value' => 'global-level value' 767 * ) 768 * ) 769 * ) 770 * 771 * @param array $hostids 772 * 773 * @return array 774 */ 775function getInheritedMacros(array $hostids) { 776 $user_macro_parser = new CUserMacroParser(); 777 778 $all_macros = []; 779 $global_macros = []; 780 781 $db_global_macros = API::UserMacro()->get([ 782 'output' => ['macro', 'value'], 783 'globalmacro' => true 784 ]); 785 786 foreach ($db_global_macros as $db_global_macro) { 787 $all_macros[$db_global_macro['macro']] = true; 788 $global_macros[$db_global_macro['macro']] = $db_global_macro['value']; 789 } 790 791 // hostid => array('name' => name, 'macros' => array(macro => value), 'templateids' => array(templateid)) 792 $hosts = []; 793 794 $templateids = $hostids; 795 796 do { 797 $db_templates = API::Template()->get([ 798 'output' => ['name'], 799 'selectParentTemplates' => ['templateid'], 800 'selectMacros' => ['macro', 'value'], 801 'templateids' => $templateids, 802 'preservekeys' => true 803 ]); 804 805 $templateids = []; 806 807 foreach ($db_templates as $hostid => $db_template) { 808 $hosts[$hostid] = [ 809 'templateid' => $hostid, 810 'name' => $db_template['name'], 811 'templateids' => zbx_objectValues($db_template['parentTemplates'], 'templateid'), 812 'macros' => [] 813 ]; 814 815 /* 816 * Global macros are overwritten by template macros and template macros are overwritten by host macros. 817 * Macros with contexts require additional checking for contexts, since {$MACRO:} is the same as 818 * {$MACRO:""}. 819 */ 820 foreach ($db_template['macros'] as $dbMacro) { 821 if (array_key_exists($dbMacro['macro'], $all_macros)) { 822 $hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value']; 823 $all_macros[$dbMacro['macro']] = true; 824 } 825 else { 826 $user_macro_parser->parse($dbMacro['macro']); 827 $tpl_macro = $user_macro_parser->getMacro(); 828 $tpl_context = $user_macro_parser->getContext(); 829 830 if ($tpl_context === null) { 831 $hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value']; 832 $all_macros[$dbMacro['macro']] = true; 833 } 834 else { 835 $match_found = false; 836 837 foreach ($global_macros as $global_macro => $global_value) { 838 $user_macro_parser->parse($global_macro); 839 $gbl_macro = $user_macro_parser->getMacro(); 840 $gbl_context = $user_macro_parser->getContext(); 841 842 if ($tpl_macro === $gbl_macro && $tpl_context === $gbl_context) { 843 $match_found = true; 844 845 unset($global_macros[$global_macro], $hosts[$hostid][$global_macro], 846 $all_macros[$global_macro] 847 ); 848 849 $hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value']; 850 $all_macros[$dbMacro['macro']] = true; 851 $global_macros[$dbMacro['macro']] = $global_value; 852 853 break; 854 } 855 } 856 857 if (!$match_found) { 858 $hosts[$hostid]['macros'][$dbMacro['macro']] = $dbMacro['value']; 859 $all_macros[$dbMacro['macro']] = true; 860 } 861 } 862 } 863 } 864 } 865 866 foreach ($db_templates as $db_template) { 867 // only unprocessed templates will be populated 868 foreach ($db_template['parentTemplates'] as $template) { 869 if (!array_key_exists($template['templateid'], $hosts)) { 870 $templateids[$template['templateid']] = $template['templateid']; 871 } 872 } 873 } 874 } while ($templateids); 875 876 $all_macros = array_keys($all_macros); 877 $all_templates = []; 878 $inherited_macros = []; 879 880 // resolving 881 foreach ($all_macros as $macro) { 882 $inherited_macro = ['macro' => $macro]; 883 884 if (array_key_exists($macro, $global_macros)) { 885 $inherited_macro['global'] = [ 886 'value' => $global_macros[$macro] 887 ]; 888 } 889 890 $templateids = $hostids; 891 892 do { 893 natsort($templateids); 894 895 foreach ($templateids as $templateid) { 896 if (array_key_exists($templateid, $hosts) && array_key_exists($macro, $hosts[$templateid]['macros'])) { 897 $inherited_macro['template'] = [ 898 'value' => $hosts[$templateid]['macros'][$macro], 899 'templateid' => $hosts[$templateid]['templateid'], 900 'name' => $hosts[$templateid]['name'], 901 'rights' => PERM_READ 902 ]; 903 904 if (!array_key_exists($hosts[$templateid]['templateid'], $all_templates)) { 905 $all_templates[$hosts[$templateid]['templateid']] = []; 906 } 907 $all_templates[$hosts[$templateid]['templateid']][] = &$inherited_macro['template']; 908 909 break 2; 910 } 911 } 912 913 $parent_templateids = []; 914 915 foreach ($templateids as $templateid) { 916 if (array_key_exists($templateid, $hosts)) { 917 foreach ($hosts[$templateid]['templateids'] as $templateid) { 918 $parent_templateids[$templateid] = $templateid; 919 } 920 } 921 } 922 923 $templateids = $parent_templateids; 924 } while ($templateids); 925 926 $inherited_macros[$macro] = $inherited_macro; 927 } 928 929 // checking permissions 930 if ($all_templates) { 931 $db_templates = API::Template()->get([ 932 'output' => ['templateid'], 933 'templateids' => array_keys($all_templates), 934 'editable' => true 935 ]); 936 937 foreach ($db_templates as $db_template) { 938 foreach ($all_templates[$db_template['templateid']] as &$template) { 939 $template['rights'] = PERM_READ_WRITE; 940 } 941 unset($template); 942 } 943 } 944 945 return $inherited_macros; 946} 947 948/** 949 * Merge list of inherited and host-level macros. 950 * 951 * Returns an array like: 952 * array( 953 * '{$MACRO}' => array( 954 * 'macro' => '{$MACRO}', 955 * 'type' => 0x03, <- MACRO_TYPE_INHERITED, MACRO_TYPE_HOSTMACRO or MACRO_TYPE_BOTH 956 * 'value' => 'effective value', 957 * 'hostmacroid' => 7532, <- optional 958 * 'template' => array( <- optional 959 * 'value' => 'template-level value' 960 * 'templateid' => 10001, 961 * 'name' => 'Template OS Linux' 962 * ), 963 * 'global' => array( <- optional 964 * 'value' => 'global-level value' 965 * ) 966 * ) 967 * ) 968 * 969 * @param array $host_macros the list of host macros 970 * @param array $inherited_macros the list of inherited macros (the output of the getInheritedMacros() function) 971 * 972 * @return array 973 */ 974function mergeInheritedMacros(array $host_macros, array $inherited_macros) { 975 $user_macro_parser = new CUserMacroParser(); 976 977 foreach ($inherited_macros as &$inherited_macro) { 978 $inherited_macro['type'] = MACRO_TYPE_INHERITED; 979 $inherited_macro['value'] = array_key_exists('template', $inherited_macro) 980 ? $inherited_macro['template']['value'] 981 : $inherited_macro['global']['value']; 982 } 983 unset($inherited_macro); 984 985 /* 986 * Global macros and template macros are overwritten by host macros. Macros with contexts require additional 987 * checking for contexts, since {$MACRO:} is the same as {$MACRO:""}. 988 */ 989 foreach ($host_macros as &$host_macro) { 990 if (array_key_exists($host_macro['macro'], $inherited_macros)) { 991 $host_macro = array_merge($inherited_macros[$host_macro['macro']], $host_macro); 992 unset($inherited_macros[$host_macro['macro']]); 993 } 994 else { 995 /* 996 * Cannot use array dereferencing because "$host_macro['macro']" may contain invalid macros 997 * which results in empty array. 998 */ 999 if ($user_macro_parser->parse($host_macro['macro']) == CParser::PARSE_SUCCESS) { 1000 $hst_macro = $user_macro_parser->getMacro(); 1001 $hst_context = $user_macro_parser->getContext(); 1002 1003 if ($hst_context === null) { 1004 $host_macro['type'] = 0x00; 1005 } 1006 else { 1007 $match_found = false; 1008 1009 foreach ($inherited_macros as $inherited_macro => $inherited_values) { 1010 // Safe to use array dereferencing since these values come from database. 1011 $user_macro_parser->parse($inherited_macro); 1012 $inh_macro = $user_macro_parser->getMacro(); 1013 $inh_context = $user_macro_parser->getContext(); 1014 1015 if ($hst_macro === $inh_macro && $hst_context === $inh_context) { 1016 $match_found = true; 1017 1018 $host_macro = array_merge($inherited_macros[$inherited_macro], $host_macro); 1019 unset($inherited_macros[$inherited_macro]); 1020 1021 break; 1022 } 1023 } 1024 1025 if (!$match_found) { 1026 $host_macro['type'] = 0x00; 1027 } 1028 } 1029 } 1030 else { 1031 $host_macro['type'] = 0x00; 1032 } 1033 } 1034 1035 $host_macro['type'] |= MACRO_TYPE_HOSTMACRO; 1036 } 1037 unset($host_macro); 1038 1039 foreach ($inherited_macros as $inherited_macro) { 1040 $host_macros[] = $inherited_macro; 1041 } 1042 1043 return $host_macros; 1044} 1045 1046/** 1047 * Remove inherited macros data. 1048 * 1049 * @param array $macros 1050 * 1051 * @return array 1052 */ 1053function cleanInheritedMacros(array $macros) { 1054 foreach ($macros as $idx => $macro) { 1055 if (array_key_exists('type', $macro) && !($macro['type'] & MACRO_TYPE_HOSTMACRO)) { 1056 unset($macros[$idx]); 1057 } 1058 else { 1059 unset($macros[$idx]['type'], $macros[$idx]['inherited']); 1060 } 1061 } 1062 1063 return $macros; 1064} 1065 1066/** 1067 * An array of available host inventory modes. 1068 * 1069 * @return array 1070 */ 1071function getHostInventoryModes() { 1072 return [ 1073 HOST_INVENTORY_DISABLED => _('Disabled'), 1074 HOST_INVENTORY_MANUAL => _('Manual'), 1075 HOST_INVENTORY_AUTOMATIC => _('Automatic') 1076 ]; 1077} 1078