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