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 user macro. 24 */ 25class CUserMacro extends CApiService { 26 27 protected $tableName = 'hostmacro'; 28 protected $tableAlias = 'hm'; 29 protected $sortColumns = ['macro']; 30 31 /** 32 * Get UserMacros data. 33 * 34 * @param array $options 35 * @param array $options['groupids'] usermacrosgroup ids 36 * @param array $options['hostids'] host ids 37 * @param array $options['hostmacroids'] host macros ids 38 * @param array $options['globalmacroids'] global macros ids 39 * @param array $options['templateids'] template ids 40 * @param boolean $options['globalmacro'] only global macros 41 * @param boolean $options['selectGroups'] select groups 42 * @param boolean $options['selectHosts'] select hosts 43 * @param boolean $options['selectTemplates'] select templates 44 * 45 * @return array|boolean UserMacros data as array or false if error 46 */ 47 public function get($options = []) { 48 $result = []; 49 $userid = self::$userData['userid']; 50 51 $sqlParts = [ 52 'select' => ['macros' => 'hm.hostmacroid'], 53 'from' => ['hostmacro hm'], 54 'where' => [], 55 'order' => [], 56 'limit' => null 57 ]; 58 59 $sqlPartsGlobal = [ 60 'select' => ['macros' => 'gm.globalmacroid'], 61 'from' => ['globalmacro gm'], 62 'where' => [], 63 'order' => [], 64 'limit' => null 65 ]; 66 67 $defOptions = [ 68 'groupids' => null, 69 'hostids' => null, 70 'hostmacroids' => null, 71 'globalmacroids' => null, 72 'templateids' => null, 73 'globalmacro' => null, 74 'editable' => false, 75 'nopermissions' => null, 76 // filter 77 'filter' => null, 78 'search' => null, 79 'searchByAny' => null, 80 'startSearch' => false, 81 'excludeSearch' => false, 82 'searchWildcardsEnabled' => null, 83 // output 84 'output' => API_OUTPUT_EXTEND, 85 'selectGroups' => null, 86 'selectHosts' => null, 87 'selectTemplates' => null, 88 'countOutput' => false, 89 'preservekeys' => false, 90 'sortfield' => '', 91 'sortorder' => '', 92 'limit' => null 93 ]; 94 $options = zbx_array_merge($defOptions, $options); 95 96 // editable + PERMISSION CHECK 97 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 98 if ($options['editable'] && !is_null($options['globalmacro'])) { 99 return []; 100 } 101 else { 102 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 103 104 $userGroups = getUserGroupsByUserId($userid); 105 106 $sqlParts['where'][] = 'EXISTS ('. 107 'SELECT NULL'. 108 ' FROM hosts_groups hgg'. 109 ' JOIN rights r'. 110 ' ON r.id=hgg.groupid'. 111 ' AND '.dbConditionInt('r.groupid', $userGroups). 112 ' WHERE hm.hostid=hgg.hostid'. 113 ' GROUP BY hgg.hostid'. 114 ' HAVING MIN(r.permission)>'.PERM_DENY. 115 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 116 ')'; 117 } 118 } 119 120 // global macro 121 if (!is_null($options['globalmacro'])) { 122 $options['groupids'] = null; 123 $options['hostmacroids'] = null; 124 $options['triggerids'] = null; 125 $options['hostids'] = null; 126 $options['itemids'] = null; 127 $options['selectGroups'] = null; 128 $options['selectTemplates'] = null; 129 $options['selectHosts'] = null; 130 } 131 132 // globalmacroids 133 if (!is_null($options['globalmacroids'])) { 134 zbx_value2array($options['globalmacroids']); 135 $sqlPartsGlobal['where'][] = dbConditionInt('gm.globalmacroid', $options['globalmacroids']); 136 } 137 138 // hostmacroids 139 if (!is_null($options['hostmacroids'])) { 140 zbx_value2array($options['hostmacroids']); 141 $sqlParts['where'][] = dbConditionInt('hm.hostmacroid', $options['hostmacroids']); 142 } 143 144 // groupids 145 if (!is_null($options['groupids'])) { 146 zbx_value2array($options['groupids']); 147 148 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 149 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 150 $sqlParts['where']['hgh'] = 'hg.hostid=hm.hostid'; 151 } 152 153 // hostids 154 if (!is_null($options['hostids'])) { 155 zbx_value2array($options['hostids']); 156 157 $sqlParts['where'][] = dbConditionInt('hm.hostid', $options['hostids']); 158 } 159 160 // templateids 161 if (!is_null($options['templateids'])) { 162 zbx_value2array($options['templateids']); 163 164 $sqlParts['from']['macros_templates'] = 'hosts_templates ht'; 165 $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['templateids']); 166 $sqlParts['where']['hht'] = 'hm.hostid=ht.hostid'; 167 } 168 169 // search 170 if (is_array($options['search'])) { 171 zbx_db_search('hostmacro hm', $options, $sqlParts); 172 zbx_db_search('globalmacro gm', $options, $sqlPartsGlobal); 173 } 174 175 // filter 176 if (is_array($options['filter'])) { 177 if (isset($options['filter']['macro'])) { 178 zbx_value2array($options['filter']['macro']); 179 180 $sqlParts['where'][] = dbConditionString('hm.macro', $options['filter']['macro']); 181 $sqlPartsGlobal['where'][] = dbConditionString('gm.macro', $options['filter']['macro']); 182 } 183 } 184 185 // sorting 186 $sqlParts = $this->applyQuerySortOptions('hostmacro', 'hm', $options, $sqlParts); 187 $sqlPartsGlobal = $this->applyQuerySortOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 188 189 // limit 190 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 191 $sqlParts['limit'] = $options['limit']; 192 $sqlPartsGlobal['limit'] = $options['limit']; 193 } 194 195 // init GLOBALS 196 if (!is_null($options['globalmacro'])) { 197 $sqlPartsGlobal = $this->applyQueryOutputOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 198 $res = DBselect(self::createSelectQueryFromParts($sqlPartsGlobal), $sqlPartsGlobal['limit']); 199 while ($macro = DBfetch($res)) { 200 if ($options['countOutput']) { 201 $result = $macro['rowscount']; 202 } 203 else { 204 $result[$macro['globalmacroid']] = $macro; 205 } 206 } 207 } 208 // init HOSTS 209 else { 210 $sqlParts = $this->applyQueryOutputOptions('hostmacro', 'hm', $options, $sqlParts); 211 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 212 while ($macro = DBfetch($res)) { 213 if ($options['countOutput']) { 214 $result = $macro['rowscount']; 215 } 216 else { 217 $result[$macro['hostmacroid']] = $macro; 218 } 219 } 220 } 221 222 if ($options['countOutput']) { 223 return $result; 224 } 225 226 if ($result) { 227 $result = $this->addRelatedObjects($options, $result); 228 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 229 } 230 231 // removing keys (hash -> array) 232 if (!$options['preservekeys']) { 233 $result = zbx_cleanHashes($result); 234 } 235 236 return $result; 237 } 238 239 /** 240 * @param array $globalmacros 241 * 242 * @return array 243 */ 244 public function createGlobal(array $globalmacros) { 245 $this->validateCreateGlobal($globalmacros); 246 247 $globalmacroids = DB::insertBatch('globalmacro', $globalmacros); 248 249 foreach ($globalmacros as $index => &$globalmacro) { 250 $globalmacro['globalmacroid'] = $globalmacroids[$index]; 251 } 252 unset($globalmacro); 253 254 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MACRO, $globalmacros); 255 256 return ['globalmacroids' => $globalmacroids]; 257 } 258 259 /** 260 * @param array $globalmacros 261 * 262 * @throws APIException if the input is invalid. 263 */ 264 private function validateCreateGlobal(array &$globalmacros) { 265 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 266 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 267 } 268 269 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 270 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', 'macro')], 271 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', 'value')] 272 ]]; 273 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 274 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 275 } 276 277 $this->checkDuplicates(zbx_objectValues($globalmacros, 'macro')); 278 } 279 280 /** 281 * @param array $globalmacros 282 * 283 * @return array 284 */ 285 public function updateGlobal(array $globalmacros) { 286 $this->validateUpdateGlobal($globalmacros, $db_globalmacros); 287 288 $upd_globalmacros = []; 289 290 foreach ($globalmacros as $globalmacro) { 291 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 292 293 $upd_globalmacro = []; 294 295 // strings 296 foreach (['macro', 'value'] as $field_name) { 297 if (array_key_exists($field_name, $globalmacro) 298 && $globalmacro[$field_name] !== $db_globalmacro[$field_name]) { 299 $upd_globalmacro[$field_name] = $globalmacro[$field_name]; 300 } 301 } 302 303 if ($upd_globalmacro) { 304 $upd_globalmacros[] = [ 305 'values'=> $upd_globalmacro, 306 'where'=> ['globalmacroid' => $globalmacro['globalmacroid']] 307 ]; 308 } 309 } 310 311 if ($upd_globalmacros) { 312 DB::update('globalmacro', $upd_globalmacros); 313 } 314 315 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MACRO, $globalmacros, $db_globalmacros); 316 317 return ['globalmacroids' => zbx_objectValues($globalmacros, 'globalmacroid')]; 318 } 319 320 /** 321 * Returns macro without spaces and curly braces. 322 * 323 * @param string $macro 324 * 325 * @return string 326 */ 327 private function trimMacro($macro) { 328 $user_macro_parser = new CUserMacroParser(); 329 330 $user_macro_parser->parse($macro); 331 332 $macro = $user_macro_parser->getMacro(); 333 $context = $user_macro_parser->getContext(); 334 335 if ($context !== null) { 336 $macro .= ':'.$context; 337 } 338 339 return $macro; 340 } 341 342 /** 343 * @param array $globalmacros 344 * @param array $db_globalmacros 345 * 346 * @throws APIException if the input is invalid 347 */ 348 private function validateUpdateGlobal(array &$globalmacros, array &$db_globalmacros = null) { 349 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 350 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 351 } 352 353 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['globalmacroid'], ['macro']], 'fields' => [ 354 'globalmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], 355 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('globalmacro', 'macro')], 356 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'value')] 357 ]]; 358 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 359 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 360 } 361 362 $db_globalmacros = DB::select('globalmacro', [ 363 'output' => ['globalmacroid', 'macro', 'value'], 364 'globalmacroids' => zbx_objectValues($globalmacros, 'globalmacroid'), 365 'preservekeys' => true 366 ]); 367 368 $macros = []; 369 370 foreach ($globalmacros as $globalmacro) { 371 if (!array_key_exists($globalmacro['globalmacroid'], $db_globalmacros)) { 372 self::exception(ZBX_API_ERROR_PERMISSIONS, 373 _('No permissions to referred object or it does not exist!') 374 ); 375 } 376 377 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 378 379 if (array_key_exists('macro', $globalmacro) 380 && $this->trimMacro($globalmacro['macro']) !== $this->trimMacro($db_globalmacro['macro'])) { 381 $macros[] = $globalmacro['macro']; 382 } 383 } 384 385 if ($macros) { 386 $this->checkDuplicates($macros); 387 } 388 } 389 390 /** 391 * Check for duplicated macros. 392 * 393 * @param array $macros 394 * 395 * @throws APIException if macros already exists. 396 */ 397 private function checkDuplicates(array $macros) { 398 $user_macro_parser = new CUserMacroParser(); 399 400 $db_globalmacros = DB::select('globalmacro', [ 401 'output' => ['macro'] 402 ]); 403 404 $uniq_macros = []; 405 406 foreach ($db_globalmacros as $db_globalmacro) { 407 $uniq_macros[$this->trimMacro($db_globalmacro['macro'])] = true; 408 } 409 410 foreach ($macros as $macro) { 411 $macro_orig = $macro; 412 $macro = $this->trimMacro($macro); 413 414 if (array_key_exists($macro, $uniq_macros)) { 415 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists.', $macro_orig)); 416 } 417 $uniq_macros[$macro] = true; 418 } 419 } 420 421 /** 422 * @param array $globalmacroids 423 * 424 * @return array 425 */ 426 public function deleteGlobal(array $globalmacroids) { 427 $this->validateDeleteGlobal($globalmacroids, $db_globalmacros); 428 429 DB::delete('globalmacro', ['globalmacroid' => $globalmacroids]); 430 431 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MACRO, $db_globalmacros); 432 433 return ['globalmacroids' => $globalmacroids]; 434 } 435 436 /** 437 * @param array $globalmacroids 438 * 439 * @throws APIException if the input is invalid. 440 */ 441 private function validateDeleteGlobal(array &$globalmacroids, array &$db_globalmacros = null) { 442 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 443 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 444 } 445 446 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 447 if (!CApiInputValidator::validate($api_input_rules, $globalmacroids, '/', $error)) { 448 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 449 } 450 451 $db_globalmacros = DB::select('globalmacro', [ 452 'output' => ['globalmacroid', 'macro'], 453 'globalmacroids' => $globalmacroids, 454 'preservekeys' => true 455 ]); 456 457 foreach ($globalmacroids as $globalmacroid) { 458 if (!array_key_exists($globalmacroid, $db_globalmacros)) { 459 self::exception(ZBX_API_ERROR_PERMISSIONS, 460 _('No permissions to referred object or it does not exist!') 461 ); 462 } 463 } 464 } 465 466 /** 467 * Validates the input parameters for the create() method. 468 * 469 * @param array $hostmacros 470 * 471 * @throws APIException if the input is invalid. 472 */ 473 protected function validateCreate(array $hostmacros) { 474 // Check the data required for authorization first. 475 foreach ($hostmacros as $hostmacro) { 476 $this->checkHostId($hostmacro); 477 } 478 479 $this->checkHostPermissions(array_unique(zbx_objectValues($hostmacros, 'hostid'))); 480 481 foreach ($hostmacros as $hostmacro) { 482 $this->checkMacro($hostmacro); 483 $this->checkUnsupportedFields('hostmacro', $hostmacro, 484 _s('Wrong fields for macro "%1$s".', $hostmacro['macro'])); 485 } 486 487 $this->checkDuplicateMacros($hostmacros); 488 $this->checkIfHostMacrosDontRepeat($hostmacros); 489 } 490 491 /** 492 * Add new host macros. 493 * 494 * @param array $hostmacros an array of host macros 495 * 496 * @return array 497 */ 498 public function create(array $hostmacros) { 499 $hostmacros = zbx_toArray($hostmacros); 500 501 $this->validateCreate($hostmacros); 502 503 $hostmacroids = DB::insert('hostmacro', $hostmacros); 504 505 return ['hostmacroids' => $hostmacroids]; 506 } 507 508 /** 509 * Validates the input parameters for the update() method. 510 * 511 * @param array $hostmacros 512 * 513 * @throws APIException if the input is invalid. 514 */ 515 protected function validateUpdate(array $hostmacros) { 516 $required_fields = ['hostmacroid']; 517 518 foreach ($hostmacros as $hostmacro) { 519 $missing_keys = array_diff($required_fields, array_keys($hostmacro)); 520 521 if ($missing_keys) { 522 self::exception(ZBX_API_ERROR_PARAMETERS, 523 _s('User macro missing parameters: %1$s', implode(', ', $missing_keys)) 524 ); 525 } 526 } 527 528 // Make sure we have all the data we need. 529 $hostmacros = $this->extendObjects($this->tableName(), $hostmacros, ['macro', 'hostid']); 530 531 $db_hostmacros = API::getApiService()->select($this->tableName(), [ 532 'output' => ['hostmacroid', 'hostid', 'macro'], 533 'hostmacroids' => zbx_objectValues($hostmacros, 'hostmacroid') 534 ]); 535 536 // Check if the macros exist in host. 537 $this->checkIfHostMacrosExistIn(zbx_objectValues($hostmacros, 'hostmacroid'), $db_hostmacros); 538 539 // Check the data required for authorization first. 540 foreach ($hostmacros as $hostmacro) { 541 $this->checkHostId($hostmacro); 542 } 543 544 // Check permissions for all affected hosts. 545 $affected_hostids = array_merge(zbx_objectValues($db_hostmacros, 'hostid'), 546 zbx_objectValues($hostmacros, 'hostid') 547 ); 548 $affected_hostids = array_unique($affected_hostids); 549 $this->checkHostPermissions($affected_hostids); 550 551 foreach ($hostmacros as $hostmacro) { 552 $this->checkMacro($hostmacro); 553 $this->checkUnsupportedFields('hostmacro', $hostmacro, 554 _s('Wrong fields for macro "%1$s".', $hostmacro['macro']) 555 ); 556 } 557 558 $this->checkDuplicateMacros($hostmacros); 559 $this->checkIfHostMacrosDontRepeat($hostmacros); 560 } 561 562 /** 563 * Update host macros. 564 * 565 * @param array $hostmacros an array of host macros 566 * 567 * @return array 568 */ 569 public function update($hostmacros) { 570 $hostmacros = zbx_toArray($hostmacros); 571 572 $this->validateUpdate($hostmacros); 573 574 $data = []; 575 576 foreach ($hostmacros as $macro) { 577 $hostmacroid = $macro['hostmacroid']; 578 unset($macro['hostmacroid']); 579 580 $data[] = [ 581 'values' => $macro, 582 'where' => ['hostmacroid' => $hostmacroid] 583 ]; 584 } 585 586 DB::update('hostmacro', $data); 587 588 return ['hostmacroids' => zbx_objectValues($hostmacros, 'hostmacroid')]; 589 } 590 591 /** 592 * Validates the input parameters for the delete() method. 593 * 594 * @param array $hostmacroids 595 * 596 * @throws APIException if the input is invalid. 597 */ 598 protected function validateDelete(array $hostmacroids) { 599 if (!$hostmacroids) { 600 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 601 } 602 603 $db_hostmacros = API::getApiService()->select('hostmacro', [ 604 'output' => ['hostid', 'hostmacroid'], 605 'hostmacroids' => $hostmacroids 606 ]); 607 608 // Check permissions for all affected hosts. 609 $this->checkHostPermissions(array_unique(zbx_objectValues($db_hostmacros, 'hostid'))); 610 611 // Check if the macros exist in host. 612 $this->checkIfHostMacrosExistIn($hostmacroids, $db_hostmacros); 613 } 614 615 /** 616 * Remove macros from hosts. 617 * 618 * @param array $hostmacroids 619 * 620 * @return array 621 */ 622 public function delete(array $hostmacroids) { 623 $this->validateDelete($hostmacroids); 624 625 DB::delete('hostmacro', ['hostmacroid' => $hostmacroids]); 626 627 return ['hostmacroids' => $hostmacroids]; 628 } 629 630 /** 631 * Replace macros on hosts/templates. 632 * $macros input array has hostid as key and array of that host macros as value. 633 * 634 * @param array $macros 635 */ 636 public function replaceMacros(array $macros) { 637 $hostids = array_keys($macros); 638 639 $this->checkHostPermissions($hostids); 640 641 $db_hosts = API::Host()->get([ 642 'output' => ['hostmacroid'], 643 'hostids' => $hostids, 644 'selectMacros' => API_OUTPUT_EXTEND, 645 'templated_hosts' => true, 646 'preservekeys' => true 647 ]); 648 649 $hostmacroids_to_delete = []; 650 $hostmacros_to_update = []; 651 $hostmacros_to_add = []; 652 653 foreach ($macros as $hostid => $hostmacros) { 654 $db_hostmacros = zbx_toHash($db_hosts[$hostid]['macros'], 'hostmacroid'); 655 656 /* 657 * Look for db macros which hostmacroids are not in list of new macros. If there are any, 658 * they should be deleted. 659 */ 660 $hostmacroids = zbx_toHash($hostmacros, 'hostmacroid'); 661 662 foreach ($db_hostmacros as $db_hostmacro) { 663 if (!array_key_exists($db_hostmacro['hostmacroid'], $hostmacroids)) { 664 $hostmacroids_to_delete[] = $db_hostmacro['hostmacroid']; 665 } 666 } 667 668 // if macro has hostmacroid it should be updated otherwise created as new 669 foreach ($hostmacros as $hostmacro) { 670 if (array_key_exists('hostmacroid', $hostmacro) 671 && array_key_exists($hostmacro['hostmacroid'], $db_hostmacros)) { 672 $hostmacros_to_update[] = $hostmacro; 673 } 674 else { 675 $hostmacro['hostid'] = $hostid; 676 $hostmacros_to_add[] = $hostmacro; 677 } 678 } 679 } 680 681 if ($hostmacroids_to_delete) { 682 $this->delete($hostmacroids_to_delete); 683 } 684 685 if ($hostmacros_to_add) { 686 $this->create($hostmacros_to_add); 687 } 688 689 if ($hostmacros_to_update) { 690 $this->update($hostmacros_to_update); 691 } 692 } 693 694 /** 695 * Validates the "macro" field. 696 * 697 * @param array $macro 698 * @param string $macro['macro'] 699 * 700 * @throws APIException if the field is not valid. 701 */ 702 protected function checkMacro(array $macro) { 703 $missing_keys = array_diff(['macro'], array_keys($macro)); 704 705 if ($missing_keys) { 706 self::exception(ZBX_API_ERROR_PARAMETERS, 707 _s('User macro missing parameters: %1$s', implode(', ', $missing_keys)) 708 ); 709 } 710 711 $user_macro_parser = new CUserMacroParser(); 712 713 if ($user_macro_parser->parse($macro['macro']) != CParser::PARSE_SUCCESS) { 714 self::exception(ZBX_API_ERROR_PARAMETERS, 715 _s('Invalid macro "%1$s": %2$s.', $macro['macro'], $user_macro_parser->getError()) 716 ); 717 } 718 } 719 720 /** 721 * Validates the "hostid" field. 722 * 723 * @param array $hostmacro 724 * 725 * @throws APIException if the field is empty. 726 */ 727 protected function checkHostId(array $hostmacro) { 728 if (!array_key_exists('hostid', $hostmacro) || zbx_empty($hostmacro['hostid'])) { 729 self::exception(ZBX_API_ERROR_PARAMETERS, _s('No host given for macro "%1$s".', $hostmacro['macro'])); 730 } 731 732 if (!is_numeric($hostmacro['hostid'])) { 733 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid hostid for macro "%1$s".', $hostmacro['macro'])); 734 } 735 } 736 737 /** 738 * Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid. 739 * 740 * @param array $hostids an array of host or template IDs 741 * 742 * @throws APIException if the user doesn't have write permissions for the given hosts. 743 */ 744 protected function checkHostPermissions(array $hostids) { 745 if ($hostids) { 746 $hostids = array_unique($hostids); 747 748 $count = API::Host()->get([ 749 'countOutput' => true, 750 'hostids' => $hostids, 751 'filter' => [ 752 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 753 ], 754 'editable' => true 755 ]); 756 757 if ($count == count($hostids)) { 758 return; 759 } 760 761 $count += API::Template()->get([ 762 'countOutput' => true, 763 'templateids' => $hostids, 764 'filter' => [ 765 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 766 ], 767 'editable' => true 768 ]); 769 770 if ($count != count($hostids)) { 771 self::exception(ZBX_API_ERROR_PERMISSIONS, 772 _('No permissions to referred object or it does not exist!') 773 ); 774 } 775 } 776 } 777 778 /** 779 * Checks if the given macros contain duplicates. Assumes the "macro" field is valid. 780 * 781 * @param array $macros 782 * 783 * @throws APIException if the given macros contain duplicates. 784 */ 785 protected function checkDuplicateMacros(array $macros) { 786 if (count($macros) <= 1) { 787 return; 788 } 789 790 $existing_macros = []; 791 $user_macro_parser = new CUserMacroParser(); 792 793 foreach ($macros as $macro) { 794 // Global macros don't have a 'hostid'. 795 $hostid = array_key_exists('hostid', $macro) ? $macro['hostid'] : 1; 796 797 $user_macro_parser->parse($macro['macro']); 798 799 $macro_name = $user_macro_parser->getMacro(); 800 $context = $user_macro_parser->getContext(); 801 802 /* 803 * Macros with same name can have different contexts. A macro with no context is not the same 804 * as a macro with an empty context. 805 */ 806 if (array_key_exists($hostid, $existing_macros) && array_key_exists($macro_name, $existing_macros[$hostid]) 807 && in_array($context, $existing_macros[$hostid][$macro_name], true)) { 808 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" is not unique.', $macro['macro'])); 809 } 810 811 $existing_macros[$hostid][$macro_name][] = $context; 812 } 813 } 814 815 /** 816 * Checks if any of the given host macros already exist on the corresponding hosts. If the macros are updated and 817 * the "hostmacroid" field is set, the method will only fail, if a macro with a different hostmacroid exists. 818 * Assumes the "macro", "hostid" and "hostmacroid" fields are valid. 819 * 820 * @param array $hostmacros 821 * @param int $hostmacros[]['hostmacroid'] 822 * @param int $hostmacros[]['hostid'] 823 * @paramt string $hostmacros['macro'] 824 * 825 * @throws APIException if any of the given macros already exist. 826 */ 827 protected function checkIfHostMacrosDontRepeat(array $hostmacros) { 828 if (!$hostmacros) { 829 return; 830 } 831 832 $macro_names = []; 833 $user_macro_parser = new CUserMacroParser(); 834 835 // Parse each macro, get unique names and, if context exists, narrow down the search. 836 foreach ($hostmacros as $hostmacro) { 837 $user_macro_parser->parse($hostmacro['macro']); 838 839 $macro_name = $user_macro_parser->getMacro(); 840 $context = $user_macro_parser->getContext(); 841 842 if ($context === null) { 843 $macro_names['{$'.$macro_name] = true; 844 } 845 else { 846 // Narrow down the search for macros with contexts. 847 $macro_names['{$'.$macro_name.':'] = true; 848 } 849 } 850 851 // When updating with empty array, don't select any data from database. 852 $db_hostmacros = API::getApiService()->select($this->tableName(), [ 853 'output' => ['hostmacroid', 'hostid', 'macro'], 854 'filter' => ['hostid' => array_unique(zbx_objectValues($hostmacros, 'hostid'))], 855 'search' => ['macro' => array_keys($macro_names)], 856 'searchByAny' => true 857 ]); 858 859 $existing_macros = []; 860 861 // Collect existing unique macro names and their contexts for each host. 862 foreach ($db_hostmacros as $db_hostmacro) { 863 $user_macro_parser->parse($db_hostmacro['macro']); 864 865 $macro_name = $user_macro_parser->getMacro(); 866 $context = $user_macro_parser->getContext(); 867 868 $existing_macros[$db_hostmacro['hostid']][$macro_name][$db_hostmacro['hostmacroid']] = $context; 869 } 870 871 // Compare each macro name and context to existing one. 872 foreach ($hostmacros as $hostmacro) { 873 $hostid = $hostmacro['hostid']; 874 875 $user_macro_parser->parse($hostmacro['macro']); 876 877 $macro_name = $user_macro_parser->getMacro(); 878 $context = $user_macro_parser->getContext(); 879 880 if (array_key_exists($hostid, $existing_macros) && array_key_exists($macro_name, $existing_macros[$hostid]) 881 && in_array($context, $existing_macros[$hostid][$macro_name], true)) { 882 foreach ($existing_macros[$hostid][$macro_name] as $hostmacroid => $existing_macro_context) { 883 if ((!array_key_exists('hostmacroid', $hostmacro) 884 || bccomp($hostmacro['hostmacroid'], $hostmacroid) != 0) 885 && $context === $existing_macro_context) { 886 $hosts = API::getApiService()->select('hosts', [ 887 'output' => ['name'], 888 'hostids' => $hostmacro['hostid'] 889 ]); 890 891 self::exception(ZBX_API_ERROR_PARAMETERS, 892 _s('Macro "%1$s" already exists on "%2$s".', $hostmacro['macro'], $hosts[0]['name']) 893 ); 894 } 895 } 896 } 897 } 898 } 899 900 /** 901 * Checks if all of the host macros with hostmacrosids given in $hostmacrosids are present in $db_hostmacros. 902 * Assumes the "hostmacroid" field is valid. 903 * 904 * @param array $hostmacroids 905 * @param array $db_hostmacros 906 * 907 * @throws APIException if any of the host macros is not present in $db_hostmacros. 908 */ 909 protected function checkIfHostMacrosExistIn(array $hostmacroids, array $db_hostmacros) { 910 $db_hostmacros = zbx_toHash($db_hostmacros, 'hostmacroid'); 911 912 foreach ($hostmacroids as $hostmacroid) { 913 if (!array_key_exists($hostmacroid, $db_hostmacros)) { 914 self::exception(ZBX_API_ERROR_PARAMETERS, 915 _s('Macro with hostmacroid "%1$s" does not exist.', $hostmacroid) 916 ); 917 } 918 } 919 } 920 921 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 922 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 923 924 if ($options['output'] != API_OUTPUT_COUNT && $options['globalmacro'] === null) { 925 if ($options['selectGroups'] !== null || $options['selectHosts'] !== null || $options['selectTemplates'] !== null) { 926 $sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts); 927 } 928 } 929 930 return $sqlParts; 931 } 932 933 protected function addRelatedObjects(array $options, array $result) { 934 $result = parent::addRelatedObjects($options, $result); 935 936 if ($options['globalmacro'] === null) { 937 $hostMacroIds = array_keys($result); 938 939 /* 940 * Adding objects 941 */ 942 // adding groups 943 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 944 $res = DBselect( 945 'SELECT hm.hostmacroid,hg.groupid'. 946 ' FROM hostmacro hm,hosts_groups hg'. 947 ' WHERE '.dbConditionInt('hm.hostmacroid', $hostMacroIds). 948 ' AND hm.hostid=hg.hostid' 949 ); 950 $relationMap = new CRelationMap(); 951 while ($relation = DBfetch($res)) { 952 $relationMap->addRelation($relation['hostmacroid'], $relation['groupid']); 953 } 954 955 $groups = API::HostGroup()->get([ 956 'output' => $options['selectGroups'], 957 'groupids' => $relationMap->getRelatedIds(), 958 'preservekeys' => true 959 ]); 960 $result = $relationMap->mapMany($result, $groups, 'groups'); 961 } 962 963 // adding templates 964 if ($options['selectTemplates'] !== null && $options['selectTemplates'] != API_OUTPUT_COUNT) { 965 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 966 $templates = API::Template()->get([ 967 'output' => $options['selectTemplates'], 968 'templateids' => $relationMap->getRelatedIds(), 969 'preservekeys' => true 970 ]); 971 $result = $relationMap->mapMany($result, $templates, 'templates'); 972 } 973 974 // adding templates 975 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 976 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 977 $templates = API::Host()->get([ 978 'output' => $options['selectHosts'], 979 'hostids' => $relationMap->getRelatedIds(), 980 'preservekeys' => true 981 ]); 982 $result = $relationMap->mapMany($result, $templates, 'hosts'); 983 } 984 } 985 986 return $result; 987 } 988} 989