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 template. 24 */ 25class CTemplate extends CHostGeneral { 26 27 protected $sortColumns = ['hostid', 'host', 'name']; 28 29 /** 30 * Overrides the parent function so that templateids will be used instead of hostids for the template API. 31 */ 32 public function pkOption($tableName = null) { 33 if ($tableName && $tableName != $this->tableName()) { 34 return parent::pkOption($tableName); 35 } 36 else { 37 return 'templateids'; 38 } 39 } 40 41 /** 42 * Get template data. 43 * 44 * @param array $options 45 * 46 * @return array 47 */ 48 public function get($options = []) { 49 $result = []; 50 51 $sqlParts = [ 52 'select' => ['templates' => 'h.hostid'], 53 'from' => ['hosts' => 'hosts h'], 54 'where' => ['h.status='.HOST_STATUS_TEMPLATE], 55 'group' => [], 56 'order' => [], 57 'limit' => null 58 ]; 59 60 $defOptions = [ 61 'groupids' => null, 62 'templateids' => null, 63 'parentTemplateids' => null, 64 'hostids' => null, 65 'graphids' => null, 66 'itemids' => null, 67 'triggerids' => null, 68 'with_items' => null, 69 'with_triggers' => null, 70 'with_graphs' => null, 71 'with_httptests' => null, 72 'editable' => false, 73 'nopermissions' => null, 74 // filter 75 'evaltype' => TAG_EVAL_TYPE_AND_OR, 76 'tags' => null, 77 'filter' => null, 78 'search' => '', 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 'selectParentTemplates' => null, 89 'selectItems' => null, 90 'selectDiscoveries' => null, 91 'selectTriggers' => null, 92 'selectGraphs' => null, 93 'selectApplications' => null, 94 'selectMacros' => null, 95 'selectScreens' => null, 96 'selectHttpTests' => null, 97 'selectTags' => null, 98 'countOutput' => false, 99 'groupCount' => false, 100 'preservekeys' => false, 101 'sortfield' => '', 102 'sortorder' => '', 103 'limit' => null, 104 'limitSelects' => null 105 ]; 106 $options = zbx_array_merge($defOptions, $options); 107 108 // editable + PERMISSION CHECK 109 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 110 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 111 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 112 113 $sqlParts['where'][] = 'EXISTS ('. 114 'SELECT NULL'. 115 ' FROM hosts_groups hgg'. 116 ' JOIN rights r'. 117 ' ON r.id=hgg.groupid'. 118 ' AND '.dbConditionInt('r.groupid', $userGroups). 119 ' WHERE h.hostid=hgg.hostid'. 120 ' GROUP BY hgg.hostid'. 121 ' HAVING MIN(r.permission)>'.PERM_DENY. 122 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 123 ')'; 124 } 125 126 // groupids 127 if (!is_null($options['groupids'])) { 128 zbx_value2array($options['groupids']); 129 130 $sqlParts['from']['hosts_groups'] = 'hosts_groups hg'; 131 $sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']); 132 $sqlParts['where']['hgh'] = 'hg.hostid=h.hostid'; 133 134 if ($options['groupCount']) { 135 $sqlParts['group']['hg'] = 'hg.groupid'; 136 } 137 } 138 139 // templateids 140 if (!is_null($options['templateids'])) { 141 zbx_value2array($options['templateids']); 142 143 $sqlParts['where']['templateid'] = dbConditionInt('h.hostid', $options['templateids']); 144 } 145 146 // parentTemplateids 147 if (!is_null($options['parentTemplateids'])) { 148 zbx_value2array($options['parentTemplateids']); 149 150 $sqlParts['from']['hosts_templates'] = 'hosts_templates ht'; 151 $sqlParts['where'][] = dbConditionInt('ht.templateid', $options['parentTemplateids']); 152 $sqlParts['where']['hht'] = 'h.hostid=ht.hostid'; 153 154 if ($options['groupCount']) { 155 $sqlParts['group']['templateid'] = 'ht.templateid'; 156 } 157 } 158 159 // hostids 160 if (!is_null($options['hostids'])) { 161 zbx_value2array($options['hostids']); 162 163 $sqlParts['from']['hosts_templates'] = 'hosts_templates ht'; 164 $sqlParts['where'][] = dbConditionInt('ht.hostid', $options['hostids']); 165 $sqlParts['where']['hht'] = 'h.hostid=ht.templateid'; 166 167 if ($options['groupCount']) { 168 $sqlParts['group']['ht'] = 'ht.hostid'; 169 } 170 } 171 172 // itemids 173 if (!is_null($options['itemids'])) { 174 zbx_value2array($options['itemids']); 175 176 $sqlParts['from']['items'] = 'items i'; 177 $sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']); 178 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 179 } 180 181 // triggerids 182 if (!is_null($options['triggerids'])) { 183 zbx_value2array($options['triggerids']); 184 185 $sqlParts['from']['functions'] = 'functions f'; 186 $sqlParts['from']['items'] = 'items i'; 187 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 188 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 189 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 190 } 191 192 // graphids 193 if (!is_null($options['graphids'])) { 194 zbx_value2array($options['graphids']); 195 196 $sqlParts['from']['graphs_items'] = 'graphs_items gi'; 197 $sqlParts['from']['items'] = 'items i'; 198 $sqlParts['where'][] = dbConditionInt('gi.graphid', $options['graphids']); 199 $sqlParts['where']['igi'] = 'i.itemid=gi.itemid'; 200 $sqlParts['where']['hi'] = 'h.hostid=i.hostid'; 201 } 202 203 // with_items 204 if (!is_null($options['with_items'])) { 205 $sqlParts['where'][] = 'EXISTS ('. 206 'SELECT NULL'. 207 ' FROM items i'. 208 ' WHERE h.hostid=i.hostid'. 209 ' AND i.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 210 ')'; 211 } 212 213 // with_triggers 214 if (!is_null($options['with_triggers'])) { 215 $sqlParts['where'][] = 'EXISTS ('. 216 'SELECT NULL'. 217 ' FROM items i,functions f,triggers t'. 218 ' WHERE i.hostid=h.hostid'. 219 ' AND i.itemid=f.itemid'. 220 ' AND f.triggerid=t.triggerid'. 221 ' AND t.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 222 ')'; 223 } 224 225 // with_graphs 226 if (!is_null($options['with_graphs'])) { 227 $sqlParts['where'][] = 'EXISTS ('. 228 'SELECT NULL'. 229 ' FROM items i,graphs_items gi,graphs g'. 230 ' WHERE i.hostid=h.hostid'. 231 ' AND i.itemid=gi.itemid'. 232 ' AND gi.graphid=g.graphid'. 233 ' AND g.flags IN ('.ZBX_FLAG_DISCOVERY_NORMAL.','.ZBX_FLAG_DISCOVERY_CREATED.')'. 234 ')'; 235 } 236 237 // with_httptests 238 if (!empty($options['with_httptests'])) { 239 $sqlParts['where'][] = 'EXISTS (SELECT ht.httptestid FROM httptest ht WHERE ht.hostid=h.hostid)'; 240 } 241 242 // tags 243 if ($options['tags'] !== null && $options['tags']) { 244 $sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'h', 245 'host_tag', 'hostid' 246 ); 247 } 248 249 // filter 250 if (is_array($options['filter'])) { 251 $this->dbFilter('hosts h', $options, $sqlParts); 252 } 253 254 // search 255 if (is_array($options['search'])) { 256 zbx_db_search('hosts h', $options, $sqlParts); 257 } 258 259 // limit 260 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 261 $sqlParts['limit'] = $options['limit']; 262 } 263 264 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 265 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 266 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 267 while ($template = DBfetch($res)) { 268 if ($options['countOutput']) { 269 if ($options['groupCount']) { 270 $result[] = $template; 271 } 272 else { 273 $result = $template['rowscount']; 274 } 275 } 276 else { 277 $template['templateid'] = $template['hostid']; 278 // Templates share table with hosts and host prototypes. Therefore remove template unrelated fields. 279 unset($template['hostid'], $template['discover']); 280 281 $result[$template['templateid']] = $template; 282 } 283 284 } 285 286 if ($options['countOutput']) { 287 return $result; 288 } 289 290 if ($result) { 291 $result = $this->addRelatedObjects($options, $result); 292 } 293 294 // removing keys (hash -> array) 295 if (!$options['preservekeys']) { 296 $result = zbx_cleanHashes($result); 297 } 298 299 return $result; 300 } 301 302 /** 303 * Add template. 304 * 305 * @param array $templates 306 * 307 * @return array 308 */ 309 public function create(array $templates) { 310 $templates = zbx_toArray($templates); 311 312 $this->validateCreate($templates); 313 314 $ins_templates = []; 315 316 foreach ($templates as &$template) { 317 // If visible name is not given or empty it should be set to host name. 318 if (!array_key_exists('name', $template) || trim($template['name']) === '') { 319 $template['name'] = $template['host']; 320 } 321 322 $ins_templates[] = array_intersect_key($template, array_flip(['host', 'name', 'description'])) + 323 ['status' => HOST_STATUS_TEMPLATE]; 324 } 325 unset($template); 326 327 $hosts_groups = []; 328 $hosts_tags = []; 329 $hosts_macros = []; 330 $templates_hostids = []; 331 $hostids = []; 332 333 $templateids = DB::insert('hosts', $ins_templates); 334 335 foreach ($templates as $index => &$template) { 336 $template['templateid'] = $templateids[$index]; 337 338 foreach (zbx_toArray($template['groups']) as $group) { 339 $hosts_groups[] = [ 340 'hostid' => $template['templateid'], 341 'groupid' => $group['groupid'] 342 ]; 343 } 344 345 if (array_key_exists('tags', $template)) { 346 foreach (zbx_toArray($template['tags']) as $tag) { 347 $hosts_tags[] = ['hostid' => $template['templateid']] + $tag; 348 } 349 } 350 351 if (array_key_exists('macros', $template)) { 352 foreach (zbx_toArray($template['macros']) as $macro) { 353 $hosts_macros[] = ['hostid' => $template['templateid']] + $macro; 354 } 355 } 356 357 if (array_key_exists('templates', $template)) { 358 foreach (zbx_toArray($template['templates']) as $link_template) { 359 $templates_hostids[$link_template['templateid']][] = $template['templateid']; 360 } 361 } 362 363 if (array_key_exists('hosts', $template)) { 364 foreach (zbx_toArray($template['hosts']) as $host) { 365 $templates_hostids[$template['templateid']][] = $host['hostid']; 366 $hostids[$host['hostid']] = true; 367 } 368 } 369 } 370 unset($template); 371 372 DB::insertBatch('hosts_groups', $hosts_groups); 373 374 if ($hosts_tags) { 375 DB::insert('host_tag', $hosts_tags); 376 } 377 378 if ($hosts_macros) { 379 API::UserMacro()->create($hosts_macros); 380 } 381 382 if ($hostids) { 383 $this->checkHostPermissions(array_keys($hostids)); 384 } 385 386 while ($templates_hostids) { 387 $templateid = key($templates_hostids); 388 $link_hostids = reset($templates_hostids); 389 $link_templateids = [$templateid]; 390 unset($templates_hostids[$templateid]); 391 392 foreach ($templates_hostids as $templateid => $hostids) { 393 if ($link_hostids === $hostids) { 394 $link_templateids[] = $templateid; 395 unset($templates_hostids[$templateid]); 396 } 397 } 398 399 $this->link($link_templateids, $link_hostids); 400 } 401 402 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_TEMPLATE, $templates); 403 404 return ['templateids' => array_column($templates, 'templateid')]; 405 } 406 407 /** 408 * Validate create template. 409 * 410 * @param array $templates 411 * 412 * @throws APIException if the input is invalid. 413 */ 414 protected function validateCreate(array $templates) { 415 $groupIds = []; 416 417 foreach ($templates as &$template) { 418 // check if hosts have at least 1 group 419 if (!isset($template['groups']) || !$template['groups']) { 420 self::exception(ZBX_API_ERROR_PARAMETERS, 421 _s('Template "%1$s" cannot be without host group.', $template['host']) 422 ); 423 } 424 425 $template['groups'] = zbx_toArray($template['groups']); 426 427 foreach ($template['groups'] as $group) { 428 if (!is_array($group) || (is_array($group) && !array_key_exists('groupid', $group))) { 429 self::exception(ZBX_API_ERROR_PARAMETERS, 430 _s('Incorrect value for field "%1$s": %2$s.', 'groups', 431 _s('the parameter "%1$s" is missing', 'groupid') 432 ) 433 ); 434 } 435 436 $groupIds[$group['groupid']] = $group['groupid']; 437 } 438 } 439 unset($template); 440 441 $dbHostGroups = API::HostGroup()->get([ 442 'output' => ['groupid'], 443 'groupids' => $groupIds, 444 'editable' => true, 445 'preservekeys' => true 446 ]); 447 448 foreach ($groupIds as $groupId) { 449 if (!isset($dbHostGroups[$groupId])) { 450 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 451 } 452 } 453 454 $templateDbFields = ['host' => null]; 455 456 $host_name_parser = new CHostNameParser(); 457 458 foreach ($templates as $template) { 459 // if visible name is not given or empty it should be set to host name 460 if ((!isset($template['name']) || zbx_empty(trim($template['name']))) && isset($template['host'])) { 461 $template['name'] = $template['host']; 462 } 463 464 if (!check_db_fields($templateDbFields, $template)) { 465 self::exception(ZBX_API_ERROR_PARAMETERS, _('Field "host" is mandatory.')); 466 } 467 468 // Property 'auto_compress' is not supported for templates. 469 if (array_key_exists('auto_compress', $template)) { 470 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 471 } 472 473 if ($host_name_parser->parse($template['host']) != CParser::PARSE_SUCCESS) { 474 self::exception(ZBX_API_ERROR_PARAMETERS, 475 _s('Incorrect characters used for template name "%1$s".', $template['host']) 476 ); 477 } 478 479 if (isset($template['host'])) { 480 $templateExists = API::Template()->get([ 481 'output' => ['templateid'], 482 'filter' => ['host' => $template['host']], 483 'nopermissions' => true, 484 'limit' => 1 485 ]); 486 if ($templateExists) { 487 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Template "%1$s" already exists.', $template['host'])); 488 } 489 490 $hostExists = API::Host()->get([ 491 'output' => ['hostid'], 492 'filter' => ['host' => $template['host']], 493 'nopermissions' => true, 494 'limit' => 1 495 ]); 496 if ($hostExists) { 497 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" already exists.', $template['host'])); 498 } 499 } 500 501 if (isset($template['name'])) { 502 $templateExists = API::Template()->get([ 503 'output' => ['templateid'], 504 'filter' => ['name' => $template['name']], 505 'nopermissions' => true, 506 'limit' => 1 507 ]); 508 if ($templateExists) { 509 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 510 'Template with the same visible name "%1$s" already exists.', 511 $template['name'] 512 )); 513 } 514 515 $hostExists = API::Host()->get([ 516 'output' => ['hostid'], 517 'filter' => ['name' => $template['name']], 518 'nopermissions' => true, 519 'limit' => 1 520 ]); 521 if ($hostExists) { 522 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 523 'Host with the same visible name "%1$s" already exists.', 524 $template['name'] 525 )); 526 } 527 } 528 529 // Validate tags. 530 if (array_key_exists('tags', $template)) { 531 $this->validateTags($template); 532 } 533 } 534 } 535 536 /** 537 * Update template. 538 * 539 * @param array $templates 540 * 541 * @return array 542 */ 543 public function update(array $templates) { 544 $templates = zbx_toArray($templates); 545 546 $this->validateUpdate($templates); 547 548 $macros = []; 549 foreach ($templates as &$template) { 550 if (isset($template['macros'])) { 551 $macros[$template['templateid']] = zbx_toArray($template['macros']); 552 553 unset($template['macros']); 554 } 555 556 if (array_key_exists('tags', $template)) { 557 $template['tags'] = zbx_toArray($template['tags']); 558 } 559 } 560 unset($template); 561 562 if ($macros) { 563 API::UserMacro()->replaceMacros($macros); 564 } 565 566 foreach ($templates as $template) { 567 // if visible name is not given or empty it should be set to host name 568 if ((!isset($template['name']) || zbx_empty(trim($template['name']))) && isset($template['host'])) { 569 $template['name'] = $template['host']; 570 } 571 572 $templateCopy = $template; 573 574 $template['templates_link'] = array_key_exists('templates', $template) ? $template['templates'] : null; 575 576 unset($template['templates'], $template['templateid'], $templateCopy['templates']); 577 $template['templates'] = [$templateCopy]; 578 579 if (!$this->massUpdate($template)) { 580 self::exception(ZBX_API_ERROR_PARAMETERS, _('Failed to update template.')); 581 } 582 } 583 584 $this->updateTags($templates, 'templateid'); 585 586 return ['templateids' => zbx_objectValues($templates, 'templateid')]; 587 } 588 589 /** 590 * Validate update template. 591 * 592 * @param array $templates 593 * 594 * @throws APIException if the input is invalid. 595 */ 596 protected function validateUpdate(array $templates) { 597 $dbTemplates = $this->get([ 598 'output' => ['templateid'], 599 'templateids' => zbx_objectValues($templates, 'templateid'), 600 'editable' => true, 601 'preservekeys' => true 602 ]); 603 604 foreach ($templates as $template) { 605 if (!isset($dbTemplates[$template['templateid']])) { 606 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 607 } 608 609 // Property 'auto_compress' is not supported for templates. 610 if (array_key_exists('auto_compress', $template)) { 611 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 612 } 613 614 // Validate tags. 615 if (array_key_exists('tags', $template)) { 616 $this->validateTags($template); 617 } 618 } 619 } 620 621 /** 622 * Delete template. 623 * 624 * @param array $templateids 625 * @param array $templateids['templateids'] 626 * 627 * @return array 628 */ 629 public function delete(array $templateids) { 630 if (empty($templateids)) { 631 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 632 } 633 634 $db_templates = $this->get([ 635 'output' => ['templateid', 'name'], 636 'templateids' => $templateids, 637 'editable' => true, 638 'preservekeys' => true 639 ]); 640 641 if (array_diff_key(array_flip($templateids), $db_templates)) { 642 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 643 } 644 645 API::Template()->unlink($templateids, null, true); 646 647 // delete the discovery rules first 648 $del_rules = API::DiscoveryRule()->get([ 649 'output' => [], 650 'hostids' => $templateids, 651 'nopermissions' => true, 652 'preservekeys' => true 653 ]); 654 if ($del_rules) { 655 CDiscoveryRuleManager::delete(array_keys($del_rules)); 656 } 657 658 // delete the items 659 $del_items = API::Item()->get([ 660 'output' => [], 661 'templateids' => $templateids, 662 'nopermissions' => true, 663 'preservekeys' => true 664 ]); 665 if ($del_items) { 666 CItemManager::delete(array_keys($del_items)); 667 } 668 669 // delete host from maps 670 if (!empty($templateids)) { 671 DB::delete('sysmaps_elements', ['elementtype' => SYSMAP_ELEMENT_TYPE_HOST, 'elementid' => $templateids]); 672 } 673 674 // disable actions 675 // actions from conditions 676 $actionids = []; 677 $sql = 'SELECT DISTINCT actionid'. 678 ' FROM conditions'. 679 ' WHERE conditiontype='.CONDITION_TYPE_TEMPLATE. 680 ' AND '.dbConditionString('value', $templateids); 681 $dbActions = DBselect($sql); 682 while ($dbAction = DBfetch($dbActions)) { 683 $actionids[$dbAction['actionid']] = $dbAction['actionid']; 684 } 685 686 // actions from operations 687 $sql = 'SELECT DISTINCT o.actionid'. 688 ' FROM operations o,optemplate ot'. 689 ' WHERE o.operationid=ot.operationid'. 690 ' AND '.dbConditionInt('ot.templateid', $templateids); 691 $dbActions = DBselect($sql); 692 while ($dbAction = DBfetch($dbActions)) { 693 $actionids[$dbAction['actionid']] = $dbAction['actionid']; 694 } 695 696 if (!empty($actionids)) { 697 DB::update('actions', [ 698 'values' => ['status' => ACTION_STATUS_DISABLED], 699 'where' => ['actionid' => $actionids] 700 ]); 701 } 702 703 // delete action conditions 704 DB::delete('conditions', [ 705 'conditiontype' => CONDITION_TYPE_TEMPLATE, 706 'value' => $templateids 707 ]); 708 709 // delete action operation commands 710 $operationids = []; 711 $sql = 'SELECT DISTINCT ot.operationid'. 712 ' FROM optemplate ot'. 713 ' WHERE '.dbConditionInt('ot.templateid', $templateids); 714 $dbOperations = DBselect($sql); 715 while ($dbOperation = DBfetch($dbOperations)) { 716 $operationids[$dbOperation['operationid']] = $dbOperation['operationid']; 717 } 718 719 DB::delete('optemplate', [ 720 'templateid'=>$templateids 721 ]); 722 723 // delete empty operations 724 $delOperationids = []; 725 $sql = 'SELECT DISTINCT o.operationid'. 726 ' FROM operations o'. 727 ' WHERE '.dbConditionInt('o.operationid', $operationids). 728 ' AND NOT EXISTS(SELECT NULL FROM optemplate ot WHERE ot.operationid=o.operationid)'; 729 $dbOperations = DBselect($sql); 730 while ($dbOperation = DBfetch($dbOperations)) { 731 $delOperationids[$dbOperation['operationid']] = $dbOperation['operationid']; 732 } 733 734 DB::delete('operations', [ 735 'operationid'=>$delOperationids 736 ]); 737 738 // http tests 739 $delHttpTests = API::HttpTest()->get([ 740 'templateids' => $templateids, 741 'output' => ['httptestid'], 742 'nopermissions' => 1, 743 'preservekeys' => true 744 ]); 745 if (!empty($delHttpTests)) { 746 API::HttpTest()->delete(array_keys($delHttpTests), true); 747 } 748 749 // Applications 750 $delApplications = API::Application()->get([ 751 'templateids' => $templateids, 752 'output' => ['applicationid'], 753 'nopermissions' => 1, 754 'preservekeys' => true 755 ]); 756 if (!empty($delApplications)) { 757 API::Application()->delete(array_keys($delApplications), true); 758 } 759 760 // Get host prototype operations from LLD overrides where this template is linked. 761 $lld_override_operationids = []; 762 763 $db_lld_override_operationids = DBselect( 764 'SELECT loo.lld_override_operationid'. 765 ' FROM lld_override_operation loo'. 766 ' WHERE EXISTS('. 767 'SELECT NULL'. 768 ' FROM lld_override_optemplate lot'. 769 ' WHERE lot.lld_override_operationid=loo.lld_override_operationid'. 770 ' AND '.dbConditionId('lot.templateid', $templateids). 771 ')' 772 ); 773 while ($db_lld_override_operationid = DBfetch($db_lld_override_operationids)) { 774 $lld_override_operationids[] = $db_lld_override_operationid['lld_override_operationid']; 775 } 776 777 if ($lld_override_operationids) { 778 DB::delete('lld_override_optemplate', ['templateid' => $templateids]); 779 780 // Make sure there no other operations left to safely delete the operation. 781 $delete_lld_override_operationids = []; 782 783 $db_delete_lld_override_operationids = DBselect( 784 'SELECT loo.lld_override_operationid'. 785 ' FROM lld_override_operation loo'. 786 ' WHERE NOT EXISTS ('. 787 'SELECT NULL'. 788 ' FROM lld_override_opstatus los'. 789 ' WHERE los.lld_override_operationid=loo.lld_override_operationid'. 790 ')'. 791 ' AND NOT EXISTS ('. 792 'SELECT NULL'. 793 ' FROM lld_override_opdiscover lod'. 794 ' WHERE lod.lld_override_operationid=loo.lld_override_operationid'. 795 ')'. 796 ' AND NOT EXISTS ('. 797 'SELECT NULL'. 798 ' FROM lld_override_opinventory loi'. 799 ' WHERE loi.lld_override_operationid=loo.lld_override_operationid'. 800 ')'. 801 ' AND NOT EXISTS ('. 802 'SELECT NULL'. 803 ' FROM lld_override_optemplate lot'. 804 ' WHERE lot.lld_override_operationid=loo.lld_override_operationid'. 805 ')'. 806 ' AND '.dbConditionId('loo.lld_override_operationid', $lld_override_operationids) 807 ); 808 809 while ($db_delete_lld_override_operationid = DBfetch($db_delete_lld_override_operationids)) { 810 $delete_lld_override_operationids[] = $db_delete_lld_override_operationid['lld_override_operationid']; 811 } 812 813 if ($delete_lld_override_operationids) { 814 DB::delete('lld_override_operation', ['lld_override_operationid' => $delete_lld_override_operationids]); 815 } 816 } 817 818 // Finally delete the template. 819 DB::delete('hosts', ['hostid' => $templateids]); 820 821 // TODO: remove info from API 822 foreach ($db_templates as $db_template) { 823 info(_s('Deleted: Template "%1$s".', $db_template['name'])); 824 } 825 826 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_TEMPLATE, $db_templates); 827 828 return ['templateids' => $templateids]; 829 } 830 831 /** 832 * Additionally allows to link templates to hosts and other templates. 833 * 834 * Checks write permissions for templates. 835 * 836 * Additional supported $data parameters are: 837 * - hosts - an array of hosts or templates to link the given templates to 838 * 839 * @param array $data 840 * 841 * @return array 842 */ 843 public function massAdd(array $data) { 844 $templates = isset($data['templates']) ? zbx_toArray($data['templates']) : []; 845 $templateIds = zbx_objectValues($templates, 'templateid'); 846 847 $this->checkPermissions($templateIds, _('No permissions to referred object or it does not exist!')); 848 849 // link hosts to the given templates 850 if (isset($data['hosts']) && !empty($data['hosts'])) { 851 $hostIds = zbx_objectValues($data['hosts'], 'hostid'); 852 853 $this->checkHostPermissions($hostIds); 854 855 // check if any of the hosts are discovered 856 $this->checkValidator($hostIds, new CHostNormalValidator([ 857 'message' => _('Cannot update templates on discovered host "%1$s".') 858 ])); 859 860 $this->link($templateIds, $hostIds); 861 } 862 863 $data['hosts'] = []; 864 865 return parent::massAdd($data); 866 } 867 868 /** 869 * Mass update. 870 * 871 * @param string $data['host'] 872 * @param string $data['name'] 873 * @param string $data['description'] 874 * @param array $data['templates'] 875 * @param array $data['templates_clear'] 876 * @param array $data['templates_link'] 877 * @param array $data['groups'] 878 * @param array $data['hosts'] 879 * @param array $data['macros'] 880 * 881 * @return array 882 */ 883 public function massUpdate(array $data) { 884 if (!array_key_exists('templates', $data) || !is_array($data['templates'])) { 885 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'templates')); 886 } 887 888 $this->validateMassUpdate($data); 889 890 $templates = zbx_toArray($data['templates']); 891 $templateIds = zbx_objectValues($templates, 'templateid'); 892 893 $fieldsToUpdate = []; 894 895 if (isset($data['host'])) { 896 $fieldsToUpdate[] = 'host='.zbx_dbstr($data['host']); 897 } 898 899 if (isset($data['name'])) { 900 // if visible name is empty replace it with host name 901 if (zbx_empty(trim($data['name'])) && isset($data['host'])) { 902 $fieldsToUpdate[] = 'name='.zbx_dbstr($data['host']); 903 } 904 // we cannot have empty visible name 905 elseif (zbx_empty(trim($data['name'])) && !isset($data['host'])) { 906 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot have empty visible template name.')); 907 } 908 else { 909 $fieldsToUpdate[] = 'name='.zbx_dbstr($data['name']); 910 } 911 } 912 913 if (isset($data['description'])) { 914 $fieldsToUpdate[] = 'description='.zbx_dbstr($data['description']); 915 } 916 917 if ($fieldsToUpdate) { 918 DBexecute('UPDATE hosts SET '.implode(', ', $fieldsToUpdate).' WHERE '.dbConditionInt('hostid', $templateIds)); 919 } 920 921 $data['templates_clear'] = isset($data['templates_clear']) ? zbx_toArray($data['templates_clear']) : []; 922 $templateIdsClear = zbx_objectValues($data['templates_clear'], 'templateid'); 923 924 if ($data['templates_clear']) { 925 $this->massRemove([ 926 'templateids' => $templateIds, 927 'templateids_clear' => $templateIdsClear 928 ]); 929 } 930 931 // update template linkage 932 // firstly need to unlink all things, to correctly check circulars 933 if (isset($data['hosts']) && $data['hosts'] !== null) { 934 /* 935 * Get all currently linked hosts and templates (skip discovered hosts) to these templates 936 * that user has read permissions. 937 */ 938 $templateHosts = API::Host()->get([ 939 'output' => ['hostid'], 940 'templateids' => $templateIds, 941 'templated_hosts' => true, 942 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL] 943 ]); 944 $templateHostIds = zbx_objectValues($templateHosts, 'hostid'); 945 $newHostIds = zbx_objectValues($data['hosts'], 'hostid'); 946 947 $hostsToDelete = array_diff($templateHostIds, $newHostIds); 948 $hostIdsToDelete = array_diff($hostsToDelete, $templateIdsClear); 949 $hostIdsToAdd = array_diff($newHostIds, $templateHostIds); 950 951 if ($hostIdsToDelete) { 952 $result = $this->massRemove([ 953 'hostids' => $hostIdsToDelete, 954 'templateids' => $templateIds 955 ]); 956 957 if (!$result) { 958 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot unlink template.')); 959 } 960 } 961 } 962 963 if (isset($data['templates_link']) && $data['templates_link'] !== null) { 964 $templateTemplates = API::Template()->get([ 965 'output' => ['templateid'], 966 'hostids' => $templateIds 967 ]); 968 $templateTemplateIds = zbx_objectValues($templateTemplates, 'templateid'); 969 $newTemplateIds = zbx_objectValues($data['templates_link'], 'templateid'); 970 971 $templatesToDelete = array_diff($templateTemplateIds, $newTemplateIds); 972 $templateIdsToDelete = array_diff($templatesToDelete, $templateIdsClear); 973 974 if ($templateIdsToDelete) { 975 $result = $this->massRemove([ 976 'templateids' => $templateIds, 977 'templateids_link' => $templateIdsToDelete 978 ]); 979 980 if (!$result) { 981 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot unlink template.')); 982 } 983 } 984 } 985 986 if (isset($data['hosts']) && $data['hosts'] !== null && $hostIdsToAdd) { 987 $result = $this->massAdd([ 988 'templates' => $templates, 989 'hosts' => $hostIdsToAdd 990 ]); 991 992 if (!$result) { 993 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot link template.')); 994 } 995 } 996 997 if (isset($data['templates_link']) && $data['templates_link'] !== null) { 998 $templatesToAdd = array_diff($newTemplateIds, $templateTemplateIds); 999 1000 if ($templatesToAdd) { 1001 $result = $this->massAdd([ 1002 'templates' => $templates, 1003 'templates_link' => $templatesToAdd 1004 ]); 1005 1006 if (!$result) { 1007 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot link template.')); 1008 } 1009 } 1010 } 1011 1012 // macros 1013 if (isset($data['macros'])) { 1014 DB::delete('hostmacro', ['hostid' => $templateIds]); 1015 1016 $this->massAdd([ 1017 'templates' => $templates, 1018 'macros' => $data['macros'] 1019 ]); 1020 } 1021 1022 /* 1023 * Update template and host group linkage. This procedure should be done the last because user can unlink 1024 * him self from a group with write permissions leaving only read premissions. Thus other procedures, like 1025 * host-template linking, macros update, must be done before this. 1026 */ 1027 if (isset($data['groups']) && $data['groups'] !== null && is_array($data['groups'])) { 1028 $updateGroups = zbx_toArray($data['groups']); 1029 1030 $templateGroups = API::HostGroup()->get([ 1031 'output' => ['groupid'], 1032 'templateids' => $templateIds 1033 ]); 1034 $templateGroupIds = zbx_objectValues($templateGroups, 'groupid'); 1035 $newGroupIds = zbx_objectValues($updateGroups, 'groupid'); 1036 1037 $groupsToAdd = array_diff($newGroupIds, $templateGroupIds); 1038 if ($groupsToAdd) { 1039 $this->massAdd([ 1040 'templates' => $templates, 1041 'groups' => zbx_toObject($groupsToAdd, 'groupid') 1042 ]); 1043 } 1044 1045 $groupIdsToDelete = array_diff($templateGroupIds, $newGroupIds); 1046 if ($groupIdsToDelete) { 1047 $this->massRemove([ 1048 'templateids' => $templateIds, 1049 'groupids' => $groupIdsToDelete 1050 ]); 1051 } 1052 } 1053 1054 return ['templateids' => $templateIds]; 1055 } 1056 1057 /** 1058 * Validate mass update. 1059 * 1060 * @param string $data['host'] 1061 * @param string $data['name'] 1062 * @param array $data['templates'] 1063 * @param array $data['groups'] 1064 * @param array $data['hosts'] 1065 * 1066 * @return array 1067 */ 1068 protected function validateMassUpdate(array $data) { 1069 $templates = zbx_toArray($data['templates']); 1070 1071 $dbTemplates = $this->get([ 1072 'output' => ['templateid', 'host'], 1073 'templateids' => zbx_objectValues($templates, 'templateid'), 1074 'editable' => true, 1075 'preservekeys' => true 1076 ]); 1077 1078 // check permissions 1079 foreach ($templates as $template) { 1080 if (!isset($dbTemplates[$template['templateid']])) { 1081 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 1082 } 1083 } 1084 1085 if (array_key_exists('groups', $data) && !$data['groups'] && $dbTemplates) { 1086 $template = reset($dbTemplates); 1087 1088 self::exception(ZBX_API_ERROR_PARAMETERS, 1089 _s('Template "%1$s" cannot be without host group.', $template['host']) 1090 ); 1091 } 1092 1093 // check name 1094 if (isset($data['name'])) { 1095 if (count($templates) > 1) { 1096 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update visible template name.')); 1097 } 1098 1099 $template = reset($templates); 1100 1101 $templateExists = $this->get([ 1102 'output' => ['templateid'], 1103 'filter' => ['name' => $data['name']], 1104 'nopermissions' => true 1105 ]); 1106 $templateExist = reset($templateExists); 1107 if ($templateExist && bccomp($templateExist['templateid'], $template['templateid']) != 0) { 1108 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1109 'Template with the same visible name "%1$s" already exists.', 1110 $data['name'] 1111 )); 1112 } 1113 1114 // can't set the same name as existing host 1115 $hostExists = API::Host()->get([ 1116 'output' => ['hostid'], 1117 'filter' => ['name' => $data['name']], 1118 'nopermissions' => true 1119 ]); 1120 if ($hostExists) { 1121 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1122 'Host with the same visible name "%1$s" already exists.', 1123 $data['name'] 1124 )); 1125 } 1126 } 1127 1128 // check host 1129 if (isset($data['host'])) { 1130 if (count($templates) > 1) { 1131 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot mass update template name.')); 1132 } 1133 1134 $template = reset($templates); 1135 1136 $templateExists = $this->get([ 1137 'output' => ['templateid'], 1138 'filter' => ['host' => $data['host']], 1139 'nopermissions' => true 1140 ]); 1141 $templateExist = reset($templateExists); 1142 if ($templateExist && bccomp($templateExist['templateid'], $template['templateid']) != 0) { 1143 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1144 'Template with the same name "%1$s" already exists.', 1145 $template['host'] 1146 )); 1147 } 1148 1149 // can't set the same name as existing host 1150 $hostExists = API::Host()->get([ 1151 'output' => ['hostid'], 1152 'filter' => ['host' => $template['host']], 1153 'nopermissions' => true 1154 ]); 1155 if ($hostExists) { 1156 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1157 'Host with the same name "%1$s" already exists.', 1158 $template['host'] 1159 )); 1160 } 1161 } 1162 1163 $host_name_parser = new CHostNameParser(); 1164 1165 if (array_key_exists('host', $data) && $host_name_parser->parse($data['host']) != CParser::PARSE_SUCCESS) { 1166 self::exception(ZBX_API_ERROR_PARAMETERS, 1167 _s('Incorrect characters used for template name "%1$s".', $data['host']) 1168 ); 1169 } 1170 } 1171 1172 /** 1173 * Additionally allows to unlink templates from hosts and other templates. 1174 * 1175 * Checks write permissions for templates. 1176 * 1177 * Additional supported $data parameters are: 1178 * - hostids - an array of host or template IDs to unlink the given templates from 1179 * 1180 * @param array $data 1181 * 1182 * @return array 1183 */ 1184 public function massRemove(array $data) { 1185 $templateids = zbx_toArray($data['templateids']); 1186 1187 $this->checkPermissions($templateids, _('You do not have permission to perform this operation.')); 1188 1189 if (isset($data['hostids'])) { 1190 // check if any of the hosts are discovered 1191 $this->checkValidator($data['hostids'], new CHostNormalValidator([ 1192 'message' => _('Cannot update templates on discovered host "%1$s".') 1193 ])); 1194 1195 API::Template()->unlink($templateids, zbx_toArray($data['hostids'])); 1196 } 1197 1198 $data['hostids'] = []; 1199 1200 return parent::massRemove($data); 1201 } 1202 1203 /** 1204 * Check if user has write permissions for templates. 1205 * 1206 * @param array $templateids 1207 * @param string $error 1208 * 1209 * @return bool 1210 */ 1211 private function checkPermissions(array $templateids, $error) { 1212 if ($templateids) { 1213 $templateids = array_unique($templateids); 1214 1215 $count = $this->get([ 1216 'countOutput' => true, 1217 'templateids' => $templateids, 1218 'editable' => true 1219 ]); 1220 1221 if ($count != count($templateids)) { 1222 self::exception(ZBX_API_ERROR_PERMISSIONS, $error); 1223 } 1224 } 1225 } 1226 1227 protected function addRelatedObjects(array $options, array $result) { 1228 $result = parent::addRelatedObjects($options, $result); 1229 1230 $templateids = array_keys($result); 1231 1232 // Adding Templates 1233 if ($options['selectTemplates'] !== null) { 1234 if ($options['selectTemplates'] != API_OUTPUT_COUNT) { 1235 $templates = []; 1236 $relationMap = $this->createRelationMap($result, 'templateid', 'hostid', 'hosts_templates'); 1237 $related_ids = $relationMap->getRelatedIds(); 1238 1239 if ($related_ids) { 1240 $templates = API::Template()->get([ 1241 'output' => $options['selectTemplates'], 1242 'templateids' => $related_ids, 1243 'preservekeys' => true 1244 ]); 1245 if (!is_null($options['limitSelects'])) { 1246 order_result($templates, 'host'); 1247 } 1248 } 1249 1250 $result = $relationMap->mapMany($result, $templates, 'templates', $options['limitSelects']); 1251 } 1252 else { 1253 $templates = API::Template()->get([ 1254 'parentTemplateids' => $templateids, 1255 'countOutput' => true, 1256 'groupCount' => true 1257 ]); 1258 $templates = zbx_toHash($templates, 'templateid'); 1259 foreach ($result as $templateid => $template) { 1260 $result[$templateid]['templates'] = array_key_exists($templateid, $templates) 1261 ? $templates[$templateid]['rowscount'] 1262 : '0'; 1263 } 1264 } 1265 } 1266 1267 // Adding Hosts 1268 if ($options['selectHosts'] !== null) { 1269 if ($options['selectHosts'] != API_OUTPUT_COUNT) { 1270 $hosts = []; 1271 $relationMap = $this->createRelationMap($result, 'templateid', 'hostid', 'hosts_templates'); 1272 $related_ids = $relationMap->getRelatedIds(); 1273 1274 if ($related_ids) { 1275 $hosts = API::Host()->get([ 1276 'output' => $options['selectHosts'], 1277 'hostids' => $related_ids, 1278 'preservekeys' => true 1279 ]); 1280 if (!is_null($options['limitSelects'])) { 1281 order_result($hosts, 'host'); 1282 } 1283 } 1284 1285 $result = $relationMap->mapMany($result, $hosts, 'hosts', $options['limitSelects']); 1286 } 1287 else { 1288 $hosts = API::Host()->get([ 1289 'templateids' => $templateids, 1290 'countOutput' => true, 1291 'groupCount' => true 1292 ]); 1293 $hosts = zbx_toHash($hosts, 'templateid'); 1294 foreach ($result as $templateid => $template) { 1295 $result[$templateid]['hosts'] = array_key_exists($templateid, $hosts) 1296 ? $hosts[$templateid]['rowscount'] 1297 : '0'; 1298 } 1299 } 1300 } 1301 1302 // Adding screens 1303 if ($options['selectScreens'] !== null) { 1304 if ($options['selectScreens'] != API_OUTPUT_COUNT) { 1305 $screens = API::TemplateScreen()->get([ 1306 'output' => $this->outputExtend($options['selectScreens'], ['templateid']), 1307 'templateids' => $templateids, 1308 'nopermissions' => true 1309 ]); 1310 if (!is_null($options['limitSelects'])) { 1311 order_result($screens, 'name'); 1312 } 1313 1314 // preservekeys is not supported by templatescreen.get, so we're building a map using array keys 1315 $relationMap = new CRelationMap(); 1316 foreach ($screens as $key => $screen) { 1317 $relationMap->addRelation($screen['templateid'], $key); 1318 } 1319 1320 $screens = $this->unsetExtraFields($screens, ['templateid'], $options['selectScreens']); 1321 $result = $relationMap->mapMany($result, $screens, 'screens', $options['limitSelects']); 1322 } 1323 else { 1324 $screens = API::TemplateScreen()->get([ 1325 'templateids' => $templateids, 1326 'nopermissions' => true, 1327 'countOutput' => true, 1328 'groupCount' => true 1329 ]); 1330 $screens = zbx_toHash($screens, 'templateid'); 1331 foreach ($result as $templateid => $template) { 1332 $result[$templateid]['screens'] = array_key_exists($templateid, $screens) 1333 ? $screens[$templateid]['rowscount'] 1334 : '0'; 1335 } 1336 } 1337 } 1338 1339 return $result; 1340 } 1341} 1342