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 22class CTemplateImporter extends CImporter { 23 24 /** 25 * @var array a list of template IDs which were created or updated 26 */ 27 protected $processedTemplateIds = []; 28 29 /** 30 * Import templates. 31 * 32 * @throws Exception 33 * 34 * @param array $templates 35 * 36 * @return void 37 */ 38 public function import(array $templates) { 39 $templates = zbx_toHash($templates, 'host'); 40 41 $this->checkCircularTemplateReferences($templates); 42 43 foreach ($templates as &$template) { 44 if (!$this->options['templateLinkage']['createMissing']) { 45 unset($template['templates']); 46 } 47 } 48 unset($template); 49 50 do { 51 $independentTemplates = $this->getIndependentTemplates($templates); 52 53 $templatesToCreate = []; 54 $templatesToUpdate = []; 55 $templateLinkage = []; 56 57 foreach ($independentTemplates as $name) { 58 $template = $templates[$name]; 59 unset($templates[$name]); 60 61 $template = $this->resolveTemplateReferences($template); 62 63 // if we need to add linkages, save linked templates to massAdd later 64 if ($this->options['templateLinkage']['createMissing'] && !empty($template['templates'])) { 65 $templateLinkage[$template['host']] = $template['templates']; 66 unset($template['templates']); 67 } 68 69 if (!empty($template['templateid'])) { 70 $templatesToUpdate[] = $template; 71 } 72 else { 73 $templatesToCreate[] = $template; 74 } 75 } 76 77 if ($this->options['templates']['createMissing'] && $templatesToCreate) { 78 $newTemplateIds = API::Template()->create($templatesToCreate); 79 80 foreach ($templatesToCreate as $num => $createdTemplate) { 81 $templateId = $newTemplateIds['templateids'][$num]; 82 83 $this->referencer->addTemplateRef($createdTemplate['host'], $templateId); 84 $this->processedTemplateIds[$templateId] = $templateId; 85 86 if (!empty($templateLinkage[$createdTemplate['host']])) { 87 API::Template()->massAdd([ 88 'templates' => ['templateid' => $templateId], 89 'templates_link' => $templateLinkage[$createdTemplate['host']] 90 ]); 91 } 92 } 93 } 94 95 if ($this->options['templates']['updateExisting'] && $templatesToUpdate) { 96 API::Template()->update($templatesToUpdate); 97 98 foreach ($templatesToUpdate as $updatedTemplate) { 99 $this->processedTemplateIds[$updatedTemplate['templateid']] = $updatedTemplate['templateid']; 100 101 if (!empty($templateLinkage[$updatedTemplate['host']])) { 102 API::Template()->massAdd([ 103 'templates' => $updatedTemplate, 104 'templates_link' => $templateLinkage[$updatedTemplate['host']] 105 ]); 106 } 107 } 108 } 109 } while (!empty($independentTemplates)); 110 111 // if there are templates left in $templates, then they have unresolved references 112 foreach ($templates as $template) { 113 $unresolvedReferences = []; 114 foreach ($template['templates'] as $linkedTemplate) { 115 if (!$this->referencer->resolveTemplate($linkedTemplate['name'])) { 116 $unresolvedReferences[] = $linkedTemplate['name']; 117 } 118 } 119 throw new Exception(_n('Cannot import template "%1$s", linked template "%2$s" does not exist.', 120 'Cannot import template "%1$s", linked templates "%2$s" do not exist.', 121 $template['host'], implode(', ', $unresolvedReferences), count($unresolvedReferences))); 122 } 123 } 124 125 /** 126 * Get a list of created or updated template IDs. 127 * 128 * @return array 129 */ 130 public function getProcessedTemplateIds() { 131 return $this->processedTemplateIds; 132 } 133 134 /** 135 * Check if templates have circular references. 136 * 137 * @throws Exception 138 * @see checkCircularRecursive 139 * 140 * @param array $templates 141 * 142 * @return void 143 */ 144 protected function checkCircularTemplateReferences(array $templates) { 145 foreach ($templates as $name => $template) { 146 if (empty($template['templates'])) { 147 continue; 148 } 149 150 foreach ($template['templates'] as $linkedTemplate) { 151 $checked = [$name]; 152 if ($circTemplates = $this->checkCircularRecursive($linkedTemplate, $templates, $checked)) { 153 throw new Exception(_s('Circular reference in templates: %1$s.', implode(' - ', $circTemplates))); 154 } 155 } 156 } 157 } 158 159 /** 160 * Recursive function for searching for circular template references. 161 * If circular reference exist it return array with template names with circular reference. 162 * 163 * @param array $linkedTemplate template element to inspect on current recursive loop 164 * @param array $templates all templates where circular references should be searched 165 * @param array $checked template names that already were processed, 166 * should contain unique values if no circular references exist 167 * 168 * @return array|bool 169 */ 170 protected function checkCircularRecursive(array $linkedTemplate, array $templates, array $checked) { 171 $linkedTemplateName = $linkedTemplate['name']; 172 173 // if current element map name is already in list of checked map names, 174 // circular reference exists 175 if (in_array($linkedTemplateName, $checked)) { 176 // to have nice result containing only maps that have circular reference, 177 // remove everything that was added before repeated map name 178 $checked = array_slice($checked, array_search($linkedTemplateName, $checked)); 179 // add repeated name to have nice loop like m1->m2->m3->m1 180 $checked[] = $linkedTemplateName; 181 return $checked; 182 } 183 else { 184 $checked[] = $linkedTemplateName; 185 } 186 187 // we need to find map that current element reference to 188 // and if it has selements check all them recursively 189 if (!empty($templates[$linkedTemplateName]['templates'])) { 190 foreach ($templates[$linkedTemplateName]['templates'] as $tpl) { 191 return $this->checkCircularRecursive($tpl, $templates, $checked); 192 } 193 } 194 195 return false; 196 } 197 198 /** 199 * Get templates that don't have not existing linked templates i.e. all templates that must be linked to these templates exist. 200 * Returns array with template names (host). 201 * 202 * @param array $templates 203 * 204 * @return array 205 */ 206 protected function getIndependentTemplates(array $templates) { 207 foreach ($templates as $num => $template) { 208 if (empty($template['templates'])) { 209 continue; 210 } 211 212 foreach ($template['templates'] as $linkedTpl) { 213 if (!$this->referencer->resolveTemplate($linkedTpl['name'])) { 214 unset($templates[$num]); 215 continue 2; 216 } 217 } 218 } 219 220 return zbx_objectValues($templates, 'host'); 221 } 222 223 /** 224 * Change all references in template to database ids. 225 * 226 * @throws Exception 227 * 228 * @param array $template 229 * 230 * @return array 231 */ 232 protected function resolveTemplateReferences(array $template) { 233 if ($templateId = $this->referencer->resolveTemplate($template['host'])) { 234 $template['templateid'] = $templateId; 235 236 // if we update template, existing macros should have hostmacroid 237 if (array_key_exists('macros', $template)) { 238 foreach ($template['macros'] as &$macro) { 239 if ($hostMacroId = $this->referencer->resolveMacro($templateId, $macro['macro'])) { 240 $macro['hostmacroid'] = $hostMacroId; 241 } 242 } 243 unset($macro); 244 } 245 } 246 247 foreach ($template['groups'] as $gnum => $group) { 248 if (!$this->referencer->resolveGroup($group['name'])) { 249 throw new Exception(_s('Group "%1$s" does not exist.', $group['name'])); 250 } 251 $template['groups'][$gnum] = ['groupid' => $this->referencer->resolveGroup($group['name'])]; 252 } 253 254 if (isset($template['templates'])) { 255 foreach ($template['templates'] as $tnum => $parentTemplate) { 256 $template['templates'][$tnum] = [ 257 'templateid' => $this->referencer->resolveTemplate($parentTemplate['name']) 258 ]; 259 } 260 } 261 262 return $template; 263 } 264} 265