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 // sorting 170 $sqlParts = $this->applyQuerySortOptions('hostmacro', 'hm', $options, $sqlParts); 171 $sqlPartsGlobal = $this->applyQuerySortOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 172 173 // limit 174 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 175 $sqlParts['limit'] = $options['limit']; 176 $sqlPartsGlobal['limit'] = $options['limit']; 177 } 178 179 // init GLOBALS 180 if (!is_null($options['globalmacro'])) { 181 $sqlPartsGlobal = $this->applyQueryFilterOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 182 $sqlPartsGlobal = $this->applyQueryOutputOptions('globalmacro', 'gm', $options, $sqlPartsGlobal); 183 $res = DBselect(self::createSelectQueryFromParts($sqlPartsGlobal), $sqlPartsGlobal['limit']); 184 while ($macro = DBfetch($res)) { 185 if ($options['countOutput']) { 186 $result = $macro['rowscount']; 187 } 188 else { 189 $result[$macro['globalmacroid']] = $macro; 190 } 191 } 192 } 193 // init HOSTS 194 else { 195 $sqlParts = $this->applyQueryFilterOptions('hostmacro', 'hm', $options, $sqlParts); 196 $sqlParts = $this->applyQueryOutputOptions('hostmacro', 'hm', $options, $sqlParts); 197 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 198 while ($macro = DBfetch($res)) { 199 if ($options['countOutput']) { 200 $result = $macro['rowscount']; 201 } 202 else { 203 $result[$macro['hostmacroid']] = $macro; 204 } 205 } 206 } 207 208 if ($options['countOutput']) { 209 return $result; 210 } 211 212 if ($result) { 213 $result = $this->addRelatedObjects($options, $result); 214 $result = $this->unsetExtraFields($result, ['hostid', 'type'], $options['output']); 215 } 216 217 // removing keys (hash -> array) 218 if (!$options['preservekeys']) { 219 $result = zbx_cleanHashes($result); 220 } 221 222 return $result; 223 } 224 225 /** 226 * @param array $globalmacros 227 * 228 * @return array 229 */ 230 public function createGlobal(array $globalmacros) { 231 $this->validateCreateGlobal($globalmacros); 232 233 $globalmacroids = DB::insertBatch('globalmacro', $globalmacros); 234 235 foreach ($globalmacros as $index => &$globalmacro) { 236 $globalmacro['globalmacroid'] = $globalmacroids[$index]; 237 } 238 unset($globalmacro); 239 240 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MACRO, $globalmacros); 241 242 return ['globalmacroids' => $globalmacroids]; 243 } 244 245 /** 246 * @param array $globalmacros 247 * 248 * @throws APIException if the input is invalid. 249 */ 250 private function validateCreateGlobal(array &$globalmacros) { 251 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 252 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 253 } 254 255 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['macro']], 'fields' => [ 256 'macro' => ['type' => API_USER_MACRO, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', 'macro')], 257 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('globalmacro', 'value')], 258 'type' => ['type' => API_INT32, 'in' => ZBX_MACRO_TYPE_TEXT.','.ZBX_MACRO_TYPE_SECRET], 259 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] 260 ]]; 261 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 262 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 263 } 264 265 $this->checkDuplicates(zbx_objectValues($globalmacros, 'macro')); 266 } 267 268 /** 269 * @param array $globalmacros 270 * 271 * @return array 272 */ 273 public function updateGlobal(array $globalmacros) { 274 $this->validateUpdateGlobal($globalmacros, $db_globalmacros); 275 276 $upd_globalmacros = []; 277 278 foreach ($globalmacros as $globalmacro) { 279 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 280 281 $upd_globalmacro = []; 282 283 // strings 284 foreach (['macro', 'value', 'description'] as $field_name) { 285 if (array_key_exists($field_name, $globalmacro) 286 && $globalmacro[$field_name] !== $db_globalmacro[$field_name]) { 287 $upd_globalmacro[$field_name] = $globalmacro[$field_name]; 288 } 289 } 290 291 // integers 292 if (array_key_exists('type', $globalmacro) && $globalmacro['type'] != $db_globalmacro['type']) { 293 $upd_globalmacro['type'] = $globalmacro['type']; 294 } 295 296 if (array_key_exists('type', $upd_globalmacro) && $db_globalmacro['type'] == ZBX_MACRO_TYPE_SECRET 297 && !array_key_exists('value', $globalmacro)) { 298 $upd_globalmacro['value'] = ''; 299 } 300 301 if ($upd_globalmacro) { 302 $upd_globalmacros[] = [ 303 'values'=> $upd_globalmacro, 304 'where'=> ['globalmacroid' => $globalmacro['globalmacroid']] 305 ]; 306 } 307 } 308 309 if ($upd_globalmacros) { 310 DB::update('globalmacro', $upd_globalmacros); 311 } 312 313 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MACRO, $globalmacros, $db_globalmacros); 314 315 return ['globalmacroids' => zbx_objectValues($globalmacros, 'globalmacroid')]; 316 } 317 318 /** 319 * Returns macro without spaces and curly braces. 320 * 321 * @param string $macro 322 * 323 * @return string 324 */ 325 private function trimMacro($macro) { 326 $user_macro_parser = new CUserMacroParser(); 327 328 $user_macro_parser->parse($macro); 329 330 $macro = $user_macro_parser->getMacro(); 331 $context = $user_macro_parser->getContext(); 332 $regex = $user_macro_parser->getRegex(); 333 334 if ($context !== null) { 335 $macro .= ':'.$context; 336 } 337 elseif ($regex !== null) { 338 $macro .= ':'.CUserMacroParser::REGEX_PREFIX.$regex; 339 } 340 341 return $macro; 342 } 343 344 /** 345 * @param array $globalmacros 346 * @param array $db_globalmacros 347 * 348 * @throws APIException if the input is invalid 349 */ 350 private function validateUpdateGlobal(array &$globalmacros, array &$db_globalmacros = null) { 351 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 352 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 353 } 354 355 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['globalmacroid'], ['macro']], 'fields' => [ 356 'globalmacroid' => ['type' => API_ID, 'flags' => API_REQUIRED], 357 'macro' => ['type' => API_USER_MACRO, 'length' => DB::getFieldLength('globalmacro', 'macro')], 358 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'value')], 359 'type' => ['type' => API_INT32, 'in' => ZBX_MACRO_TYPE_TEXT.','.ZBX_MACRO_TYPE_SECRET], 360 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('globalmacro', 'description')] 361 ]]; 362 if (!CApiInputValidator::validate($api_input_rules, $globalmacros, '/', $error)) { 363 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 364 } 365 366 $db_globalmacros = DB::select('globalmacro', [ 367 'output' => ['globalmacroid', 'macro', 'value', 'description', 'type'], 368 'globalmacroids' => zbx_objectValues($globalmacros, 'globalmacroid'), 369 'preservekeys' => true 370 ]); 371 372 $macros = []; 373 374 foreach ($globalmacros as $globalmacro) { 375 if (!array_key_exists($globalmacro['globalmacroid'], $db_globalmacros)) { 376 self::exception(ZBX_API_ERROR_PERMISSIONS, 377 _('No permissions to referred object or it does not exist!') 378 ); 379 } 380 381 $db_globalmacro = $db_globalmacros[$globalmacro['globalmacroid']]; 382 383 if (array_key_exists('macro', $globalmacro) 384 && $this->trimMacro($globalmacro['macro']) !== $this->trimMacro($db_globalmacro['macro'])) { 385 $macros[] = $globalmacro['macro']; 386 } 387 } 388 389 if ($macros) { 390 $this->checkDuplicates($macros); 391 } 392 } 393 394 /** 395 * Check for duplicated macros. 396 * 397 * @param array $macros 398 * 399 * @throws APIException if macros already exists. 400 */ 401 private function checkDuplicates(array $macros) { 402 $user_macro_parser = new CUserMacroParser(); 403 404 $db_globalmacros = DB::select('globalmacro', [ 405 'output' => ['macro'] 406 ]); 407 408 $uniq_macros = []; 409 410 foreach ($db_globalmacros as $db_globalmacro) { 411 $uniq_macros[$this->trimMacro($db_globalmacro['macro'])] = true; 412 } 413 414 foreach ($macros as $macro) { 415 $macro_orig = $macro; 416 $macro = $this->trimMacro($macro); 417 418 if (array_key_exists($macro, $uniq_macros)) { 419 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" already exists.', $macro_orig)); 420 } 421 $uniq_macros[$macro] = true; 422 } 423 } 424 425 /** 426 * @param array $globalmacroids 427 * 428 * @return array 429 */ 430 public function deleteGlobal(array $globalmacroids) { 431 $this->validateDeleteGlobal($globalmacroids, $db_globalmacros); 432 433 DB::delete('globalmacro', ['globalmacroid' => $globalmacroids]); 434 435 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MACRO, $db_globalmacros); 436 437 return ['globalmacroids' => $globalmacroids]; 438 } 439 440 /** 441 * @param array $globalmacroids 442 * 443 * @throws APIException if the input is invalid. 444 */ 445 private function validateDeleteGlobal(array &$globalmacroids, array &$db_globalmacros = null) { 446 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 447 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 448 } 449 450 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 451 if (!CApiInputValidator::validate($api_input_rules, $globalmacroids, '/', $error)) { 452 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 453 } 454 455 $db_globalmacros = DB::select('globalmacro', [ 456 'output' => ['globalmacroid', 'macro'], 457 'globalmacroids' => $globalmacroids, 458 'preservekeys' => true 459 ]); 460 461 foreach ($globalmacroids as $globalmacroid) { 462 if (!array_key_exists($globalmacroid, $db_globalmacros)) { 463 self::exception(ZBX_API_ERROR_PERMISSIONS, 464 _('No permissions to referred object or it does not exist!') 465 ); 466 } 467 } 468 } 469 470 /** 471 * Validates the input parameters for the create() method. 472 * 473 * @param array $hostmacros 474 * 475 * @throws APIException if the input is invalid. 476 */ 477 protected function validateCreate(array $hostmacros) { 478 if (!$hostmacros) { 479 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 480 } 481 482 // Check the data required for authorization first. 483 foreach ($hostmacros as $hostmacro) { 484 $this->checkHostId($hostmacro); 485 } 486 487 $this->checkHostPermissions(array_unique(zbx_objectValues($hostmacros, 'hostid'))); 488 489 foreach ($hostmacros as $hostmacro) { 490 $this->checkMacro($hostmacro); 491 $this->checkUnsupportedFields('hostmacro', $hostmacro, 492 _s('Wrong fields for macro "%1$s".', $hostmacro['macro'])); 493 $this->checkMacroType($hostmacro); 494 } 495 496 $this->checkDuplicateMacros($hostmacros); 497 $this->checkIfHostMacrosDontRepeat($hostmacros); 498 } 499 500 /** 501 * Add new host macros. 502 * 503 * @param array $hostmacros an array of host macros 504 * 505 * @return array 506 */ 507 public function create(array $hostmacros) { 508 $hostmacros = zbx_toArray($hostmacros); 509 510 $this->validateCreate($hostmacros); 511 512 $hostmacroids = DB::insert('hostmacro', $hostmacros); 513 514 return ['hostmacroids' => $hostmacroids]; 515 } 516 517 /** 518 * Validates the input parameters for the update() method. 519 * 520 * @param array $hostmacros 521 * 522 * @throws APIException if the input is invalid. 523 */ 524 protected function validateUpdate(array $hostmacros) { 525 if (!$hostmacros) { 526 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 527 } 528 529 $required_fields = ['hostmacroid']; 530 531 foreach ($hostmacros as $hostmacro) { 532 $missing_keys = array_diff($required_fields, array_keys($hostmacro)); 533 534 if ($missing_keys) { 535 self::exception(ZBX_API_ERROR_PARAMETERS, 536 _s('User macro missing parameters: %1$s', implode(', ', $missing_keys)) 537 ); 538 } 539 } 540 541 // Make sure we have all the data we need. 542 $hostmacros = $this->extendObjects($this->tableName(), $hostmacros, ['macro', 'hostid']); 543 544 $db_hostmacros = DB::select($this->tableName(), [ 545 'output' => ['hostmacroid', 'hostid', 'macro'], 546 'hostmacroids' => zbx_objectValues($hostmacros, 'hostmacroid') 547 ]); 548 549 // Check if the macros exist in host. 550 $this->checkIfHostMacrosExistIn(zbx_objectValues($hostmacros, 'hostmacroid'), $db_hostmacros); 551 552 // Check the data required for authorization first. 553 foreach ($hostmacros as $hostmacro) { 554 $this->checkHostId($hostmacro); 555 } 556 557 // Check permissions for all affected hosts. 558 $affected_hostids = array_merge(zbx_objectValues($db_hostmacros, 'hostid'), 559 zbx_objectValues($hostmacros, 'hostid') 560 ); 561 $affected_hostids = array_unique($affected_hostids); 562 $this->checkHostPermissions($affected_hostids); 563 564 foreach ($hostmacros as $hostmacro) { 565 $this->checkMacro($hostmacro); 566 $this->checkUnsupportedFields('hostmacro', $hostmacro, 567 _s('Wrong fields for macro "%1$s".', $hostmacro['macro']) 568 ); 569 $this->checkMacroType($hostmacro); 570 } 571 572 $this->checkDuplicateMacros($hostmacros); 573 $this->checkIfHostMacrosDontRepeat($hostmacros); 574 } 575 576 /** 577 * Update host macros. 578 * 579 * @param array $hostmacros an array of host macros 580 * 581 * @return array 582 */ 583 public function update($hostmacros) { 584 $hostmacros = zbx_toArray($hostmacros); 585 586 $this->validateUpdate($hostmacros); 587 588 $db_macros = DB::select('hostmacro', [ 589 'output' => ['type'], 590 'filter' => ['hostmacroid' => array_column($hostmacros, 'hostmacroid')], 591 'preservekeys' => true 592 ]); 593 $data = []; 594 595 foreach ($hostmacros as $macro) { 596 $db_macro = $db_macros[$macro['hostmacroid']]; 597 598 if (array_key_exists('type', $macro) && $macro['type'] != $db_macro['type'] 599 && $db_macro['type'] == ZBX_MACRO_TYPE_SECRET) { 600 $macro += ['value' => '']; 601 } 602 603 $hostmacroid = $macro['hostmacroid']; 604 unset($macro['hostmacroid']); 605 606 $data[] = [ 607 'values' => $macro, 608 'where' => ['hostmacroid' => $hostmacroid] 609 ]; 610 } 611 612 DB::update('hostmacro', $data); 613 614 return ['hostmacroids' => array_column($hostmacros, 'hostmacroid')]; 615 } 616 617 /** 618 * Validates the input parameters for the delete() method. 619 * 620 * @param array $hostmacroids 621 * 622 * @throws APIException if the input is invalid. 623 */ 624 protected function validateDelete(array $hostmacroids) { 625 if (!$hostmacroids) { 626 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 627 } 628 629 $db_hostmacros = API::getApiService()->select('hostmacro', [ 630 'output' => ['hostid', 'hostmacroid'], 631 'hostmacroids' => $hostmacroids 632 ]); 633 634 // Check permissions for all affected hosts. 635 $this->checkHostPermissions(array_unique(zbx_objectValues($db_hostmacros, 'hostid'))); 636 637 // Check if the macros exist in host. 638 $this->checkIfHostMacrosExistIn($hostmacroids, $db_hostmacros); 639 } 640 641 /** 642 * Remove macros from hosts. 643 * 644 * @param array $hostmacroids 645 * 646 * @return array 647 */ 648 public function delete(array $hostmacroids) { 649 $this->validateDelete($hostmacroids); 650 651 DB::delete('hostmacro', ['hostmacroid' => $hostmacroids]); 652 653 return ['hostmacroids' => $hostmacroids]; 654 } 655 656 /** 657 * Replace macros on hosts/templates. 658 * $macros input array has hostid as key and array of that host macros as value. 659 * 660 * @param array $macros 661 */ 662 public function replaceMacros(array $macros) { 663 $hostids = array_keys($macros); 664 665 $this->checkHostPermissions($hostids); 666 667 $db_hosts = API::Host()->get([ 668 'output' => ['hostmacroid'], 669 'hostids' => $hostids, 670 'selectMacros' => API_OUTPUT_EXTEND, 671 'templated_hosts' => true, 672 'preservekeys' => true 673 ]); 674 675 $hostmacroids_to_delete = []; 676 $hostmacros_to_update = []; 677 $hostmacros_to_add = []; 678 679 foreach ($macros as $hostid => $hostmacros) { 680 $db_hostmacros = zbx_toHash($db_hosts[$hostid]['macros'], 'hostmacroid'); 681 682 /* 683 * Look for db macros which hostmacroids are not in list of new macros. If there are any, 684 * they should be deleted. 685 */ 686 $hostmacroids = zbx_toHash($hostmacros, 'hostmacroid'); 687 688 foreach ($db_hostmacros as $db_hostmacro) { 689 if (!array_key_exists($db_hostmacro['hostmacroid'], $hostmacroids)) { 690 $hostmacroids_to_delete[] = $db_hostmacro['hostmacroid']; 691 } 692 } 693 694 // if macro has hostmacroid it should be updated otherwise created as new 695 foreach ($hostmacros as $hostmacro) { 696 if (array_key_exists('hostmacroid', $hostmacro) 697 && array_key_exists($hostmacro['hostmacroid'], $db_hostmacros)) { 698 $hostmacros_to_update[] = $hostmacro; 699 } 700 else { 701 $hostmacro['hostid'] = $hostid; 702 $hostmacros_to_add[] = $hostmacro; 703 } 704 } 705 } 706 707 if ($hostmacroids_to_delete) { 708 $this->delete($hostmacroids_to_delete); 709 } 710 711 if ($hostmacros_to_add) { 712 $this->create($hostmacros_to_add); 713 } 714 715 if ($hostmacros_to_update) { 716 $this->update($hostmacros_to_update); 717 } 718 } 719 720 /** 721 * Validates the "macro" field. 722 * 723 * @param array $macro 724 * @param string $macro['macro'] 725 * 726 * @throws APIException if the field is not valid. 727 */ 728 protected function checkMacro(array $macro) { 729 $missing_keys = array_diff(['macro'], array_keys($macro)); 730 731 if ($missing_keys) { 732 self::exception(ZBX_API_ERROR_PARAMETERS, 733 _s('User macro missing parameters: %1$s', implode(', ', $missing_keys)) 734 ); 735 } 736 737 $user_macro_parser = new CUserMacroParser(); 738 739 if ($user_macro_parser->parse($macro['macro']) != CParser::PARSE_SUCCESS) { 740 self::exception(ZBX_API_ERROR_PARAMETERS, 741 _s('Invalid macro "%1$s": %2$s.', $macro['macro'], $user_macro_parser->getError()) 742 ); 743 } 744 } 745 746 /** 747 * Validate the "type" field. 748 * 749 * @param array $macro 750 */ 751 protected function checkMacroType(array $macro) { 752 if (array_key_exists('type', $macro) && $macro['type'] != ZBX_MACRO_TYPE_TEXT 753 && $macro['type'] != ZBX_MACRO_TYPE_SECRET) { 754 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid type for macro "%1$s".', $macro['macro'])); 755 } 756 } 757 758 /** 759 * Validates the "hostid" field. 760 * 761 * @param array $hostmacro 762 * 763 * @throws APIException if the field is empty. 764 */ 765 protected function checkHostId(array $hostmacro) { 766 if (!array_key_exists('hostid', $hostmacro) || zbx_empty($hostmacro['hostid'])) { 767 self::exception(ZBX_API_ERROR_PARAMETERS, _s('No host given for macro "%1$s".', $hostmacro['macro'])); 768 } 769 770 if (!is_numeric($hostmacro['hostid'])) { 771 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid hostid for macro "%1$s".', $hostmacro['macro'])); 772 } 773 } 774 775 /** 776 * Checks if the current user has access to the given hosts and templates. Assumes the "hostid" field is valid. 777 * 778 * @param array $hostids an array of host or template IDs 779 * 780 * @throws APIException if the user doesn't have write permissions for the given hosts. 781 */ 782 protected function checkHostPermissions(array $hostids) { 783 if ($hostids) { 784 $hostids = array_unique($hostids); 785 786 $count = API::Host()->get([ 787 'countOutput' => true, 788 'hostids' => $hostids, 789 'filter' => [ 790 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 791 ], 792 'editable' => true 793 ]); 794 795 if ($count == count($hostids)) { 796 return; 797 } 798 799 $count += API::Template()->get([ 800 'countOutput' => true, 801 'templateids' => $hostids, 802 'filter' => [ 803 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 804 ], 805 'editable' => true 806 ]); 807 808 if ($count == count($hostids)) { 809 return; 810 } 811 812 $count += API::HostPrototype()->get([ 813 'countOutput' => true, 814 'hostids' => $hostids, 815 'editable' => true 816 ]); 817 818 if ($count != count($hostids)) { 819 self::exception(ZBX_API_ERROR_PERMISSIONS, 820 _('No permissions to referred object or it does not exist!') 821 ); 822 } 823 } 824 } 825 826 /** 827 * Checks if the given macros contain duplicates. Assumes the "macro" field is valid. 828 * 829 * @param array $macros 830 * 831 * @throws APIException if the given macros contain duplicates. 832 */ 833 protected function checkDuplicateMacros(array $macros) { 834 if (count($macros) <= 1) { 835 return; 836 } 837 838 $existing_macros = []; 839 $user_macro_parser = new CUserMacroParser(); 840 841 foreach ($macros as $macro) { 842 // Global macros don't have a 'hostid'. 843 $hostid = array_key_exists('hostid', $macro) ? $macro['hostid'] : 1; 844 845 $user_macro_parser->parse($macro['macro']); 846 847 $macro_name = $user_macro_parser->getMacro(); 848 $context = $user_macro_parser->getContext(); 849 $regex = $user_macro_parser->getRegex(); 850 851 /* 852 * Macros with same name can have different contexts. A macro with no context is not the same 853 * as a macro with an empty context. 854 */ 855 if (array_key_exists($hostid, $existing_macros) 856 && array_key_exists($macro_name, $existing_macros[$hostid])) { 857 $has_context = in_array($context, array_column($existing_macros[$hostid][$macro_name], 'context'), 858 true 859 ); 860 $context_exists = ($context !== null && $has_context); 861 862 $has_regex = in_array($regex, array_column($existing_macros[$hostid][$macro_name], 'regex'), true); 863 $regex_exists = ($regex !== null && $has_regex); 864 865 $is_macro_without_context = ($context === null && $regex === null); 866 867 if (($is_macro_without_context && $has_context && $has_regex) || ($context_exists || $regex_exists)) { 868 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Macro "%1$s" is not unique.', $macro['macro'])); 869 } 870 } 871 872 $existing_macros[$hostid][$macro_name][] = ['context' => $context, 'regex' => $regex]; 873 } 874 } 875 876 /** 877 * Checks if any of the given host macros already exist on the corresponding hosts. If the macros are updated and 878 * the "hostmacroid" field is set, the method will only fail, if a macro with a different hostmacroid exists. 879 * Assumes the "macro", "hostid" and "hostmacroid" fields are valid. 880 * 881 * @param array $hostmacros 882 * @param int $hostmacros[]['hostmacroid'] 883 * @param int $hostmacros[]['hostid'] 884 * @param string $hostmacros['macro'] 885 * 886 * @throws APIException if any of the given macros already exist. 887 */ 888 protected function checkIfHostMacrosDontRepeat(array $hostmacros) { 889 if (!$hostmacros) { 890 return; 891 } 892 893 $macro_names = []; 894 $user_macro_parser = new CUserMacroParser(); 895 896 // Parse each macro, get unique names and, if context exists, narrow down the search. 897 foreach ($hostmacros as $hostmacro) { 898 $user_macro_parser->parse($hostmacro['macro']); 899 900 $macro_name = $user_macro_parser->getMacro(); 901 $context = $user_macro_parser->getContext(); 902 $regex = $user_macro_parser->getRegex(); 903 904 if ($context === null && $regex === null) { 905 $macro_names['{$'.$macro_name] = true; 906 } 907 else { 908 // Narrow down the search for macros with contexts. 909 $macro_names['{$'.$macro_name.':'] = true; 910 } 911 } 912 913 // When updating with empty array, don't select any data from database. 914 $db_hostmacros = API::getApiService()->select($this->tableName(), [ 915 'output' => ['hostmacroid', 'hostid', 'macro'], 916 'filter' => ['hostid' => array_unique(array_column($hostmacros, 'hostid'))], 917 'search' => ['macro' => array_keys($macro_names)], 918 'searchByAny' => true 919 ]); 920 921 $existing_macros = []; 922 923 // Collect existing unique macro names and their contexts for each host. 924 foreach ($db_hostmacros as $db_hostmacro) { 925 $user_macro_parser->parse($db_hostmacro['macro']); 926 927 $macro_name = $user_macro_parser->getMacro(); 928 $context = $user_macro_parser->getContext(); 929 $regex = $user_macro_parser->getRegex(); 930 931 $existing_macros[$db_hostmacro['hostid']][$macro_name][$db_hostmacro['hostmacroid']] = 932 ['context' => $context, 'regex' => $regex]; 933 } 934 935 // Compare each macro name and context to existing one. 936 foreach ($hostmacros as $hostmacro) { 937 $hostid = $hostmacro['hostid']; 938 939 $user_macro_parser->parse($hostmacro['macro']); 940 941 $macro_name = $user_macro_parser->getMacro(); 942 $context = $user_macro_parser->getContext(); 943 $regex = $user_macro_parser->getRegex(); 944 945 if (array_key_exists($hostid, $existing_macros) 946 && array_key_exists($macro_name, $existing_macros[$hostid])) { 947 $has_context = ($context !== null && in_array($context, 948 array_column($existing_macros[$hostid][$macro_name], 'context'), true 949 )); 950 $has_regex = ($regex !== null && in_array($regex, 951 array_column($existing_macros[$hostid][$macro_name], 'regex'), true 952 )); 953 $is_macro_without_context = ($context === null && $regex === null); 954 955 if ($is_macro_without_context || $has_context || $has_regex) { 956 foreach ($existing_macros[$hostid][$macro_name] as $hostmacroid => $macro_details) { 957 if ((!array_key_exists('hostmacroid', $hostmacro) 958 || bccomp($hostmacro['hostmacroid'], $hostmacroid) != 0) 959 && $context === $macro_details['context'] && $regex === $macro_details['regex']) { 960 $hosts = API::getApiService()->select('hosts', [ 961 'output' => ['name'], 962 'hostids' => $hostmacro['hostid'] 963 ]); 964 965 self::exception(ZBX_API_ERROR_PARAMETERS, 966 _s('Macro "%1$s" already exists on "%2$s".', $hostmacro['macro'], $hosts[0]['name']) 967 ); 968 } 969 } 970 } 971 } 972 } 973 } 974 975 /** 976 * Checks if all of the host macros with hostmacrosids given in $hostmacrosids are present in $db_hostmacros. 977 * Assumes the "hostmacroid" field is valid. 978 * 979 * @param array $hostmacroids 980 * @param array $db_hostmacros 981 * 982 * @throws APIException if any of the host macros is not present in $db_hostmacros. 983 */ 984 protected function checkIfHostMacrosExistIn(array $hostmacroids, array $db_hostmacros) { 985 $db_hostmacros = zbx_toHash($db_hostmacros, 'hostmacroid'); 986 987 foreach ($hostmacroids as $hostmacroid) { 988 if (!array_key_exists($hostmacroid, $db_hostmacros)) { 989 self::exception(ZBX_API_ERROR_PARAMETERS, 990 _s('Macro with hostmacroid "%1$s" does not exist.', $hostmacroid) 991 ); 992 } 993 } 994 } 995 996 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 997 // Added type to query because it required to check macro is secret or not. 998 if (!$this->outputIsRequested('type', $options['output'])) { 999 $options['output'][] = 'type'; 1000 } 1001 1002 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 1003 1004 if ($options['output'] != API_OUTPUT_COUNT && $options['globalmacro'] === null) { 1005 if ($options['selectGroups'] !== null || $options['selectHosts'] !== null || $options['selectTemplates'] !== null) { 1006 $sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts); 1007 } 1008 } 1009 1010 return $sqlParts; 1011 } 1012 1013 /** 1014 * @inheritdoc 1015 */ 1016 protected function applyQueryFilterOptions($table, $alias, array $options, $sql_parts) { 1017 if (is_array($options['search'])) { 1018 // Do not allow to search by value for macro of type ZBX_MACRO_TYPE_SECRET. 1019 if (array_key_exists('value', $options['search'])) { 1020 $sql_parts['where']['search'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; 1021 zbx_db_search($table.' '.$alias, [ 1022 'searchByAny' => false, 1023 'search' => ['value' => $options['search']['value']] 1024 ] + $options, $sql_parts 1025 ); 1026 unset($options['search']['value']); 1027 } 1028 1029 if ($options['search']) { 1030 zbx_db_search($table.' '.$alias, $options, $sql_parts); 1031 } 1032 } 1033 1034 if (is_array($options['filter'])) { 1035 // Do not allow to filter by value for macro of type ZBX_MACRO_TYPE_SECRET. 1036 if (array_key_exists('value', $options['filter'])) { 1037 $sql_parts['where']['filter'] = $alias.'.type!='.ZBX_MACRO_TYPE_SECRET; 1038 $this->dbFilter($table.' '.$alias, [ 1039 'searchByAny' => false, 1040 'filter' => ['value' => $options['filter']['value']] 1041 ] + $options, $sql_parts 1042 ); 1043 unset($options['filter']['value']); 1044 } 1045 1046 if ($options['filter']) { 1047 $this->dbFilter($table.' '.$alias, $options, $sql_parts); 1048 } 1049 } 1050 1051 return $sql_parts; 1052 } 1053 1054 protected function addRelatedObjects(array $options, array $result) { 1055 $result = parent::addRelatedObjects($options, $result); 1056 1057 if ($options['globalmacro'] === null) { 1058 $hostMacroIds = array_keys($result); 1059 1060 /* 1061 * Adding objects 1062 */ 1063 // adding groups 1064 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 1065 $res = DBselect( 1066 'SELECT hm.hostmacroid,hg.groupid'. 1067 ' FROM hostmacro hm,hosts_groups hg'. 1068 ' WHERE '.dbConditionInt('hm.hostmacroid', $hostMacroIds). 1069 ' AND hm.hostid=hg.hostid' 1070 ); 1071 $relationMap = new CRelationMap(); 1072 while ($relation = DBfetch($res)) { 1073 $relationMap->addRelation($relation['hostmacroid'], $relation['groupid']); 1074 } 1075 1076 $groups = API::HostGroup()->get([ 1077 'output' => $options['selectGroups'], 1078 'groupids' => $relationMap->getRelatedIds(), 1079 'preservekeys' => true 1080 ]); 1081 $result = $relationMap->mapMany($result, $groups, 'groups'); 1082 } 1083 1084 // adding templates 1085 if ($options['selectTemplates'] !== null && $options['selectTemplates'] != API_OUTPUT_COUNT) { 1086 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 1087 $templates = API::Template()->get([ 1088 'output' => $options['selectTemplates'], 1089 'templateids' => $relationMap->getRelatedIds(), 1090 'preservekeys' => true 1091 ]); 1092 $result = $relationMap->mapMany($result, $templates, 'templates'); 1093 } 1094 1095 // adding templates 1096 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 1097 $relationMap = $this->createRelationMap($result, 'hostmacroid', 'hostid'); 1098 $templates = API::Host()->get([ 1099 'output' => $options['selectHosts'], 1100 'hostids' => $relationMap->getRelatedIds(), 1101 'preservekeys' => true 1102 ]); 1103 $result = $relationMap->mapMany($result, $templates, 'hosts'); 1104 } 1105 } 1106 1107 return $result; 1108 } 1109 1110 protected function unsetExtraFields(array $objects, array $fields, $output) { 1111 foreach ($objects as &$object) { 1112 if ($object['type'] == ZBX_MACRO_TYPE_SECRET) { 1113 unset($object['value']); 1114 } 1115 } 1116 unset($object); 1117 1118 return parent::unsetExtraFields($objects, $fields, $output); 1119 } 1120} 1121