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 22require_once dirname(__FILE__).'/include/config.inc.php'; 23require_once dirname(__FILE__).'/include/hosts.inc.php'; 24require_once dirname(__FILE__).'/include/screens.inc.php'; 25require_once dirname(__FILE__).'/include/forms.inc.php'; 26require_once dirname(__FILE__).'/include/ident.inc.php'; 27 28$page['type'] = detect_page_type(PAGE_TYPE_HTML); 29$page['title'] = _('Configuration of templates'); 30$page['file'] = 'templates.php'; 31$page['scripts'] = ['multiselect.js']; 32 33require_once dirname(__FILE__).'/include/page_header.php'; 34 35// VAR TYPE OPTIONAL FLAGS VALIDATION EXCEPTION 36$fields = [ 37 'groups' => [T_ZBX_STR, O_OPT, null, NOT_EMPTY, 'isset({add}) || isset({update})'], 38 'clear_templates' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 39 'templates' => [T_ZBX_INT, O_OPT, null, DB_ID, null], 40 'add_templates' => [T_ZBX_INT, O_OPT, null, DB_ID, null], 41 'add_template' => [T_ZBX_STR, O_OPT, null, null, null], 42 'templateid' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, 'isset({form}) && {form} == "update"'], 43 'template_name' => [T_ZBX_STR, O_OPT, null, NOT_EMPTY, 'isset({add}) || isset({update})', _('Template name')], 44 'visiblename' => [T_ZBX_STR, O_OPT, null, null, 'isset({add}) || isset({update})'], 45 'groupid' => [T_ZBX_INT, O_OPT, P_SYS, DB_ID, null], 46 'description' => [T_ZBX_STR, O_OPT, null, null, null], 47 'macros' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 48 'show_inherited_macros' => [T_ZBX_INT, O_OPT, null, IN([0,1]), null], 49 // actions 50 'action' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, 51 IN('"template.export","template.massdelete","template.massdeleteclear"'), 52 null 53 ], 54 'unlink' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 55 'unlink_and_clear' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 56 'add' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 57 'update' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 58 'clone' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 59 'full_clone' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 60 'delete' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 61 'delete_and_clear' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], 62 'cancel' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 63 'form' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 64 'form_refresh' => [T_ZBX_INT, O_OPT, null, null, null], 65 // filter 66 'filter_set' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 67 'filter_rst' => [T_ZBX_STR, O_OPT, P_SYS, null, null], 68 'filter_name' => [T_ZBX_STR, O_OPT, null, null, null], 69 'filter_templates' => [T_ZBX_INT, O_OPT, null, DB_ID, null], 70 // sort and sortorder 71 'sort' => [T_ZBX_STR, O_OPT, P_SYS, IN('"name"'), null], 72 'sortorder' => [T_ZBX_STR, O_OPT, P_SYS, IN('"'.ZBX_SORT_DOWN.'","'.ZBX_SORT_UP.'"'), null] 73]; 74check_fields($fields); 75 76/* 77 * Permissions 78 */ 79if (getRequest('groupid') && !isWritableHostGroups([getRequest('groupid')])) { 80 access_deny(); 81} 82if (getRequest('templateid')) { 83 $templates = API::Template()->get([ 84 'output' => [], 85 'templateids' => getRequest('templateid'), 86 'editable' => true 87 ]); 88 89 if (!$templates) { 90 access_deny(); 91 } 92} 93 94$templateIds = getRequest('templates', []); 95 96// remove inherited macros data (actions: 'add', 'update' and 'form') 97if (hasRequest('macros')) { 98 $_REQUEST['macros'] = cleanInheritedMacros($_REQUEST['macros']); 99 100 // remove empty new macro lines 101 foreach ($_REQUEST['macros'] as $idx => $macro) { 102 if (!array_key_exists('hostmacroid', $macro) && $macro['macro'] === '' && $macro['value'] === '') { 103 unset($_REQUEST['macros'][$idx]); 104 } 105 } 106} 107 108/* 109 * Actions 110 */ 111if (isset($_REQUEST['add_template']) && isset($_REQUEST['add_templates'])) { 112 $_REQUEST['templates'] = array_merge($templateIds, $_REQUEST['add_templates']); 113} 114if (hasRequest('unlink') || hasRequest('unlink_and_clear')) { 115 $unlinkTemplates = []; 116 117 if (hasRequest('unlink') && is_array(getRequest('unlink'))) { 118 $unlinkTemplates = array_keys(getRequest('unlink')); 119 } 120 elseif (hasRequest('unlink_and_clear') && is_array(getRequest('unlink_and_clear'))) { 121 $unlinkTemplates = array_keys(getRequest('unlink_and_clear')); 122 $_REQUEST['clear_templates'] = array_merge($unlinkTemplates, getRequest('clear_templates', [])); 123 } 124 125 foreach ($unlinkTemplates as $id) { 126 unset($_REQUEST['templates'][array_search($id, $_REQUEST['templates'])]); 127 } 128} 129elseif ((hasRequest('clone') || hasRequest('full_clone')) && hasRequest('templateid')) { 130 $_REQUEST['form'] = hasRequest('clone') ? 'clone' : 'full_clone'; 131 132 $groups = getRequest('groups', []); 133 $groupids = []; 134 135 // Remove inaccessible groups from request, but leave "new". 136 foreach ($groups as $group) { 137 if (!is_array($group)) { 138 $groupids[] = $group; 139 } 140 } 141 142 if ($groupids) { 143 $groups_allowed = API::HostGroup()->get([ 144 'output' => [], 145 'groupids' => $groupids, 146 'editable' => true, 147 'preservekeys' => true 148 ]); 149 150 foreach ($groups as $idx => $group) { 151 if (!is_array($group) && !array_key_exists($group, $groups_allowed)) { 152 unset($groups[$idx]); 153 } 154 } 155 156 $_REQUEST['groups'] = $groups; 157 } 158 159 if (hasRequest('clone')) { 160 unset($_REQUEST['templateid']); 161 } 162} 163elseif (hasRequest('add') || hasRequest('update')) { 164 try { 165 DBstart(); 166 167 $templateId = getRequest('templateid', 0); 168 $cloneTemplateId = 0; 169 170 if (getRequest('form') === 'full_clone') { 171 $cloneTemplateId = $templateId; 172 $templateId = 0; 173 } 174 175 if ($templateId == 0) { 176 $messageSuccess = _('Template added'); 177 $messageFailed = _('Cannot add template'); 178 } 179 else { 180 $messageSuccess = _('Template updated'); 181 $messageFailed = _('Cannot update template'); 182 } 183 184 // Add new group. 185 $groups = getRequest('groups', []); 186 $new_groups = []; 187 188 foreach ($groups as $idx => $group) { 189 if (is_array($group) && array_key_exists('new', $group)) { 190 $new_groups[] = ['name' => $group['new']]; 191 unset($groups[$idx]); 192 } 193 } 194 195 if ($new_groups) { 196 $new_groupid = API::HostGroup()->create($new_groups); 197 198 if (!$new_groupid) { 199 throw new Exception(); 200 } 201 202 $groups = array_merge($groups, $new_groupid['groupids']); 203 } 204 205 // linked templates 206 $linkedTemplates = getRequest('templates', []); 207 $templates = []; 208 foreach ($linkedTemplates as $linkedTemplateId) { 209 $templates[] = ['templateid' => $linkedTemplateId]; 210 } 211 212 $templatesClear = getRequest('clear_templates', []); 213 $templatesClear = zbx_toObject($templatesClear, 'templateid'); 214 $templateName = getRequest('template_name', ''); 215 216 // create / update template 217 $template = [ 218 'host' => $templateName, 219 'name' => getRequest('visiblename', ''), 220 'groups' => zbx_toObject($groups, 'groupid'), 221 'templates' => $templates, 222 'macros' => getRequest('macros', []), 223 'description' => getRequest('description', '') 224 ]; 225 226 if ($templateId == 0) { 227 $result = API::Template()->create($template); 228 229 if ($result) { 230 $templateId = reset($result['templateids']); 231 } 232 else { 233 throw new Exception(); 234 } 235 } 236 else { 237 $template['templateid'] = $templateId; 238 $template['templates_clear'] = $templatesClear; 239 240 $result = API::Template()->update($template); 241 242 if ($result) { 243 add_audit_ext(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_TEMPLATE, $templateId, $templateName, 'hosts', null, null); 244 } 245 else { 246 throw new Exception(); 247 } 248 } 249 250 // full clone 251 if ($cloneTemplateId != 0 && getRequest('form') === 'full_clone') { 252 if (!copyApplications($cloneTemplateId, $templateId)) { 253 throw new Exception(); 254 } 255 256 /* 257 * First copy web scenarios with web items, so that later regular items can use web item as their master 258 * item. 259 */ 260 if (!copyHttpTests($cloneTemplateId, $templateId)) { 261 throw new Exception(); 262 } 263 264 if (!copyItems($cloneTemplateId, $templateId)) { 265 throw new Exception(); 266 } 267 268 // copy triggers 269 $dbTriggers = API::Trigger()->get([ 270 'output' => ['triggerid'], 271 'hostids' => $cloneTemplateId, 272 'inherited' => false 273 ]); 274 275 if ($dbTriggers) { 276 $result &= copyTriggersToHosts(zbx_objectValues($dbTriggers, 'triggerid'), 277 $templateId, $cloneTemplateId); 278 279 if (!$result) { 280 throw new Exception(); 281 } 282 } 283 284 // copy graphs 285 $dbGraphs = API::Graph()->get([ 286 'output' => ['graphid'], 287 'hostids' => $cloneTemplateId, 288 'inherited' => false 289 ]); 290 291 foreach ($dbGraphs as $dbGraph) { 292 copyGraphToHost($dbGraph['graphid'], $templateId); 293 } 294 295 // copy discovery rules 296 $dbDiscoveryRules = API::DiscoveryRule()->get([ 297 'output' => ['itemid'], 298 'hostids' => $cloneTemplateId, 299 'inherited' => false 300 ]); 301 302 if ($dbDiscoveryRules) { 303 $result &= API::DiscoveryRule()->copy([ 304 'discoveryids' => zbx_objectValues($dbDiscoveryRules, 'itemid'), 305 'hostids' => [$templateId] 306 ]); 307 308 if (!$result) { 309 throw new Exception(); 310 } 311 } 312 313 // copy template screens 314 $dbTemplateScreens = API::TemplateScreen()->get([ 315 'noInheritance' => true, 316 'output' => ['screenid'], 317 'templateids' => $cloneTemplateId, 318 'preservekeys' => true 319 ]); 320 321 if ($dbTemplateScreens) { 322 $result &= API::TemplateScreen()->copy([ 323 'screenIds' => zbx_objectValues($dbTemplateScreens, 'screenid'), 324 'templateIds' => $templateId 325 ]); 326 327 if (!$result) { 328 throw new Exception(); 329 } 330 } 331 } 332 333 unset($_REQUEST['form'], $_REQUEST['templateid']); 334 $result = DBend($result); 335 336 if ($result) { 337 uncheckTableRows(); 338 } 339 show_messages($result, $messageSuccess, $messageFailed); 340 } 341 catch (Exception $e) { 342 DBend(false); 343 show_error_message($messageFailed); 344 } 345} 346elseif (isset($_REQUEST['delete']) && isset($_REQUEST['templateid'])) { 347 DBstart(); 348 349 $result = API::Template()->massUpdate([ 350 'templates' => zbx_toObject($_REQUEST['templateid'], 'templateid'), 351 'hosts' => [] 352 ]); 353 if ($result) { 354 $result = API::Template()->delete([getRequest('templateid')]); 355 } 356 357 $result = DBend($result); 358 359 if ($result) { 360 unset($_REQUEST['form'], $_REQUEST['templateid']); 361 uncheckTableRows(); 362 } 363 unset($_REQUEST['delete']); 364 show_messages($result, _('Template deleted'), _('Cannot delete template')); 365} 366elseif (isset($_REQUEST['delete_and_clear']) && isset($_REQUEST['templateid'])) { 367 DBstart(); 368 369 $result = API::Template()->delete([getRequest('templateid')]); 370 371 $result = DBend($result); 372 373 if ($result) { 374 unset($_REQUEST['form'], $_REQUEST['templateid']); 375 uncheckTableRows(); 376 } 377 unset($_REQUEST['delete']); 378 show_messages($result, _('Template deleted'), _('Cannot delete template')); 379} 380elseif (hasRequest('action') && str_in_array(getRequest('action'), ['template.massdelete', 'template.massdeleteclear']) && hasRequest('templates')) { 381 $templates = getRequest('templates'); 382 383 DBstart(); 384 385 $result = true; 386 387 if (getRequest('action') === 'template.massdelete') { 388 $result = API::Template()->massUpdate([ 389 'templates' => zbx_toObject($templates, 'templateid'), 390 'hosts' => [] 391 ]); 392 } 393 394 if ($result) { 395 $result = API::Template()->delete($templates); 396 } 397 398 $result = DBend($result); 399 400 if ($result) { 401 uncheckTableRows(); 402 } 403 else { 404 $templateids = API::Template()->get([ 405 'output' => [], 406 'templateids' => $templates, 407 'editable' => true 408 ]); 409 uncheckTableRows(null, zbx_objectValues($templateids, 'templateid')); 410 } 411 show_messages($result, _('Template deleted'), _('Cannot delete template')); 412} 413 414/* 415 * Display 416 */ 417$pageFilter = new CPageFilter([ 418 'config' => [ 419 'individual' => 1 420 ], 421 'groups' => [ 422 'templated_hosts' => true, 423 'editable' => true 424 ], 425 'groupid' => getRequest('groupid') 426]); 427$_REQUEST['groupid'] = $pageFilter->groupid; 428 429if (hasRequest('form')) { 430 $data = [ 431 'form' => getRequest('form'), 432 'templateid' => getRequest('templateid', 0), 433 'show_inherited_macros' => getRequest('show_inherited_macros', 0) 434 ]; 435 436 if ($data['templateid'] != 0) { 437 $dbTemplates = API::Template()->get([ 438 'templateids' => $data['templateid'], 439 'selectGroups' => API_OUTPUT_EXTEND, 440 'selectParentTemplates' => ['templateid', 'name'], 441 'selectMacros' => API_OUTPUT_EXTEND, 442 'output' => API_OUTPUT_EXTEND 443 ]); 444 $data['dbTemplate'] = reset($dbTemplates); 445 446 $data['original_templates'] = []; 447 foreach ($data['dbTemplate']['parentTemplates'] as $parentTemplate) { 448 $data['original_templates'][$parentTemplate['templateid']] = $parentTemplate['templateid']; 449 } 450 } 451 else { 452 $data['original_templates'] = []; 453 } 454 455 // description 456 $data['description'] = ($data['templateid'] != 0 && !hasRequest('form_refresh')) 457 ? $data['dbTemplate']['description'] 458 : getRequest('description'); 459 460 $templateIds = getRequest('templates', hasRequest('form_refresh') ? [] : $data['original_templates']); 461 462 // Get linked templates. 463 $data['linkedTemplates'] = API::Template()->get([ 464 'output' => ['templateid', 'name'], 465 'templateids' => $templateIds, 466 'preservekeys' => true 467 ]); 468 469 $data['writable_templates'] = API::Template()->get([ 470 'output' => ['templateid'], 471 'templateids' => $templateIds, 472 'editable' => true, 473 'preservekeys' => true 474 ]); 475 476 CArrayHelper::sort($data['linkedTemplates'], ['name']); 477 478 $groups = []; 479 480 if (!hasRequest('form_refresh')) { 481 if ($data['templateid'] != 0) { 482 $groups = zbx_objectValues($data['dbTemplate']['groups'], 'groupid'); 483 } 484 elseif (getRequest('groupid', 0) != 0) { 485 $groups[] = getRequest('groupid'); 486 } 487 } 488 else { 489 $groups = getRequest('groups', []); 490 } 491 492 $groupids = []; 493 494 foreach ($groups as $group) { 495 if (is_array($group) && array_key_exists('new', $group)) { 496 continue; 497 } 498 499 $groupids[] = $group; 500 } 501 502 // Groups with R and RW permissions. 503 $groups_all = $groupids 504 ? API::HostGroup()->get([ 505 'output' => ['name'], 506 'groupids' => $groupids, 507 'preservekeys' => true 508 ]) 509 : []; 510 511 // Groups with RW permissions. 512 $groups_rw = $groupids && (CWebUser::getType() != USER_TYPE_SUPER_ADMIN) 513 ? API::HostGroup()->get([ 514 'output' => [], 515 'groupids' => $groupids, 516 'editable' => true, 517 'preservekeys' => true 518 ]) 519 : []; 520 521 $data['groups_ms'] = []; 522 523 // Prepare data for multiselect. 524 foreach ($groups as $group) { 525 if (is_array($group) && array_key_exists('new', $group)) { 526 $data['groups_ms'][] = [ 527 'id' => $group['new'], 528 'name' => $group['new'].' ('._x('new', 'new element in multiselect').')', 529 'isNew' => true 530 ]; 531 } 532 elseif (array_key_exists($group, $groups_all)) { 533 $data['groups_ms'][] = [ 534 'id' => $group, 535 'name' => $groups_all[$group]['name'], 536 'disabled' => (CWebUser::getType() != USER_TYPE_SUPER_ADMIN) && !array_key_exists($group, $groups_rw) 537 ]; 538 } 539 } 540 CArrayHelper::sort($data['groups_ms'], ['name']); 541 542 $view = new CView('configuration.template.edit', $data); 543} 544else { 545 $sortField = getRequest('sort', CProfile::get('web.'.$page['file'].'.sort', 'name')); 546 $sortOrder = getRequest('sortorder', CProfile::get('web.'.$page['file'].'.sortorder', ZBX_SORT_UP)); 547 548 CProfile::update('web.'.$page['file'].'.sort', $sortField, PROFILE_TYPE_STR); 549 CProfile::update('web.'.$page['file'].'.sortorder', $sortOrder, PROFILE_TYPE_STR); 550 551 // filter 552 if (hasRequest('filter_set')) { 553 CProfile::update('web.templates.filter_name', getRequest('filter_name', ''), PROFILE_TYPE_STR); 554 CProfile::updateArray('web.templates.filter_templates', getRequest('filter_templates', []), PROFILE_TYPE_ID); 555 } 556 elseif (hasRequest('filter_rst')) { 557 CProfile::delete('web.templates.filter_name'); 558 CProfile::deleteIdx('web.templates.filter_templates'); 559 } 560 561 $filter = [ 562 'name' => CProfile::get('web.templates.filter_name', ''), 563 'templates' => CProfile::getArray('web.templates.filter_templates', null) 564 ]; 565 566 $config = select_config(); 567 568 // get templates 569 $templates = []; 570 571 $filter['templates'] = $filter['templates'] 572 ? CArrayHelper::renameObjectsKeys(API::Template()->get([ 573 'output' => ['templateid', 'name'], 574 'templateids' => $filter['templates'], 575 'preservekeys' => true 576 ]), ['templateid' => 'id']) 577 : []; 578 579 if ($pageFilter->groupsSelected) { 580 $templates = API::Template()->get([ 581 'output' => ['templateid', $sortField], 582 'search' => [ 583 'name' => ($filter['name'] === '') ? null : $filter['name'] 584 ], 585 'parentTemplateids' => $filter['templates'] ? array_keys($filter['templates']) : null, 586 'groupids' => $pageFilter->groupids, 587 'editable' => true, 588 'sortfield' => $sortField, 589 'limit' => $config['search_limit'] + 1 590 ]); 591 } 592 order_result($templates, $sortField, $sortOrder); 593 594 $url = (new CUrl('templates.php')) 595 ->setArgument('groupid', getRequest('groupid', 0)); 596 597 $paging = getPagingLine($templates, $sortOrder, $url); 598 599 $templates = API::Template()->get([ 600 'output' => ['templateid', 'name'], 601 'selectHosts' => ['hostid', 'name', 'status'], 602 'selectTemplates' => ['templateid', 'name', 'status'], 603 'selectParentTemplates' => ['templateid', 'name', 'status'], 604 'selectItems' => API_OUTPUT_COUNT, 605 'selectTriggers' => API_OUTPUT_COUNT, 606 'selectGraphs' => API_OUTPUT_COUNT, 607 'selectApplications' => API_OUTPUT_COUNT, 608 'selectDiscoveries' => API_OUTPUT_COUNT, 609 'selectScreens' => API_OUTPUT_COUNT, 610 'selectHttpTests' => API_OUTPUT_COUNT, 611 'templateids' => zbx_objectValues($templates, 'templateid'), 612 'editable' => true 613 ]); 614 615 order_result($templates, $sortField, $sortOrder); 616 617 // Select writable templates: 618 $linked_template_ids = []; 619 $writable_templates = []; 620 $linked_hostids = []; 621 $writable_hosts = []; 622 foreach ($templates as $template) { 623 $linked_template_ids = array_merge( 624 $linked_template_ids, 625 zbx_objectValues($template['parentTemplates'], 'templateid'), 626 zbx_objectValues($template['templates'], 'templateid'), 627 zbx_objectValues($template['hosts'], 'hostid') 628 ); 629 630 $linked_hostids = array_merge( 631 $linked_hostids, 632 zbx_objectValues($template['hosts'], 'hostid') 633 ); 634 } 635 if ($linked_template_ids) { 636 $linked_template_ids = array_unique($linked_template_ids); 637 $writable_templates = API::Template()->get([ 638 'output' => ['templateid'], 639 'templateids' => $linked_template_ids, 640 'editable' => true, 641 'preservekeys' => true 642 ]); 643 } 644 if ($linked_hostids) { 645 $linked_hostids = array_unique($linked_hostids); 646 $writable_hosts = API::Host()->get([ 647 'output' => ['hostid'], 648 'hostids' => $linked_hostids, 649 'editable' => true, 650 'preservekeys' => true 651 ]); 652 } 653 654 $data = [ 655 'pageFilter' => $pageFilter, 656 'templates' => $templates, 657 'paging' => $paging, 658 'filter' => $filter, 659 'sortField' => $sortField, 660 'sortOrder' => $sortOrder, 661 'config' => [ 662 'max_in_table' => $config['max_in_table'] 663 ], 664 'writable_templates' => $writable_templates, 665 'writable_hosts' => $writable_hosts, 666 'profileIdx' => 'web.templates.filter', 667 'active_tab' => CProfile::get('web.templates.filter.active', 1) 668 ]; 669 670 $view = new CView('configuration.template.list', $data); 671} 672 673$view->render(); 674$view->show(); 675 676require_once dirname(__FILE__).'/include/page_footer.php'; 677