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