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 22abstract class CHostBase extends CApiService { 23 24 protected $tableName = 'hosts'; 25 protected $tableAlias = 'h'; 26 27 /** 28 * Links the templates to the given hosts. 29 * 30 * @param array $templateIds 31 * @param array $targetIds an array of host IDs to link the templates to 32 * 33 * @return array an array of added hosts_templates rows, with 'hostid' and 'templateid' set for each row 34 */ 35 protected function link(array $templateIds, array $targetIds) { 36 if (empty($templateIds)) { 37 return; 38 } 39 40 // permission check 41 $templateIds = array_unique($templateIds); 42 43 $count = API::Template()->get([ 44 'countOutput' => true, 45 'templateids' => $templateIds 46 ]); 47 48 if ($count != count($templateIds)) { 49 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 50 } 51 52 // check if someone passed duplicate templates in the same query 53 $templateIdDuplicates = zbx_arrayFindDuplicates($templateIds); 54 if (!zbx_empty($templateIdDuplicates)) { 55 $duplicatesFound = []; 56 foreach ($templateIdDuplicates as $value => $count) { 57 $duplicatesFound[] = _s('template ID "%1$s" is passed %2$s times', $value, $count); 58 } 59 self::exception( 60 ZBX_API_ERROR_PARAMETERS, 61 _s('Cannot pass duplicate template IDs for the linkage: %s.', implode(', ', $duplicatesFound)) 62 ); 63 } 64 65 // get DB templates which exists in all targets 66 $res = DBselect('SELECT * FROM hosts_templates WHERE '.dbConditionInt('hostid', $targetIds)); 67 $mas = []; 68 while ($row = DBfetch($res)) { 69 if (!isset($mas[$row['templateid']])) { 70 $mas[$row['templateid']] = []; 71 } 72 $mas[$row['templateid']][$row['hostid']] = 1; 73 } 74 $targetIdCount = count($targetIds); 75 $commonDBTemplateIds = []; 76 foreach ($mas as $templateId => $targetList) { 77 if (count($targetList) == $targetIdCount) { 78 $commonDBTemplateIds[] = $templateId; 79 } 80 } 81 82 // check if there are any template with triggers which depends on triggers in templates which will be not linked 83 $commonTemplateIds = array_unique(array_merge($commonDBTemplateIds, $templateIds)); 84 foreach ($templateIds as $templateid) { 85 $triggerids = []; 86 $dbTriggers = get_triggers_by_hostid($templateid); 87 while ($trigger = DBfetch($dbTriggers)) { 88 $triggerids[$trigger['triggerid']] = $trigger['triggerid']; 89 } 90 91 $sql = 'SELECT DISTINCT h.host'. 92 ' FROM trigger_depends td,functions f,items i,hosts h'. 93 ' WHERE ('. 94 dbConditionInt('td.triggerid_down', $triggerids). 95 ' AND f.triggerid=td.triggerid_up'. 96 ' )'. 97 ' AND i.itemid=f.itemid'. 98 ' AND h.hostid=i.hostid'. 99 ' AND '.dbConditionInt('h.hostid', $commonTemplateIds, true). 100 ' AND h.status='.HOST_STATUS_TEMPLATE; 101 if ($dbDepHost = DBfetch(DBselect($sql))) { 102 $tmpTpls = API::Template()->get([ 103 'templateids' => $templateid, 104 'output'=> API_OUTPUT_EXTEND 105 ]); 106 $tmpTpl = reset($tmpTpls); 107 108 self::exception(ZBX_API_ERROR_PARAMETERS, 109 _s('Trigger in template "%1$s" has dependency with trigger in template "%2$s".', $tmpTpl['host'], $dbDepHost['host'])); 110 } 111 } 112 113 $res = DBselect( 114 'SELECT ht.hostid,ht.templateid'. 115 ' FROM hosts_templates ht'. 116 ' WHERE '.dbConditionInt('ht.hostid', $targetIds). 117 ' AND '.dbConditionInt('ht.templateid', $templateIds) 118 ); 119 $linked = []; 120 while ($row = DBfetch($res)) { 121 $linked[$row['templateid']][$row['hostid']] = true; 122 } 123 124 // add template linkages, if problems rollback later 125 $hostsLinkageInserts = []; 126 127 foreach ($templateIds as $templateid) { 128 $linked_targets = array_key_exists($templateid, $linked) ? $linked[$templateid] : []; 129 130 foreach ($targetIds as $targetid) { 131 if (array_key_exists($targetid, $linked_targets)) { 132 continue; 133 } 134 135 $hostsLinkageInserts[] = ['hostid' => $targetid, 'templateid' => $templateid]; 136 } 137 } 138 139 if ($hostsLinkageInserts) { 140 self::checkCircularLinkage($hostsLinkageInserts); 141 self::checkDoubleLinkage($hostsLinkageInserts); 142 143 DB::insertBatch('hosts_templates', $hostsLinkageInserts); 144 } 145 146 // check if all trigger templates are linked to host. 147 // we try to find template that is not linked to hosts ($targetids) 148 // and exists trigger which reference that template and template from ($templateids) 149 $sql = 'SELECT DISTINCT h.host'. 150 ' FROM functions f,items i,triggers t,hosts h'. 151 ' WHERE f.itemid=i.itemid'. 152 ' AND f.triggerid=t.triggerid'. 153 ' AND i.hostid=h.hostid'. 154 ' AND h.status='.HOST_STATUS_TEMPLATE. 155 ' AND NOT EXISTS (SELECT 1 FROM hosts_templates ht WHERE ht.templateid=i.hostid AND '.dbConditionInt('ht.hostid', $targetIds).')'. 156 ' AND EXISTS (SELECT 1 FROM functions ff,items ii WHERE ff.itemid=ii.itemid AND ff.triggerid=t.triggerid AND '.dbConditionInt('ii.hostid', $templateIds). ')'; 157 if ($dbNotLinkedTpl = DBfetch(DBSelect($sql, 1))) { 158 self::exception(ZBX_API_ERROR_PARAMETERS, 159 _s('Trigger has items from template "%1$s" that is not linked to host.', $dbNotLinkedTpl['host']) 160 ); 161 } 162 163 return $hostsLinkageInserts; 164 } 165 166 protected function unlink($templateids, $targetids = null) { 167 $cond = ['templateid' => $templateids]; 168 if (!is_null($targetids)) { 169 $cond['hostid'] = $targetids; 170 } 171 DB::delete('hosts_templates', $cond); 172 173 if (!is_null($targetids)) { 174 $hosts = API::Host()->get([ 175 'hostids' => $targetids, 176 'output' => ['hostid', 'host'], 177 'nopermissions' => true 178 ]); 179 } 180 else{ 181 $hosts = API::Host()->get([ 182 'templateids' => $templateids, 183 'output' => ['hostid', 'host'], 184 'nopermissions' => true 185 ]); 186 } 187 188 if (!empty($hosts)) { 189 $templates = API::Template()->get([ 190 'templateids' => $templateids, 191 'output' => ['hostid', 'host'], 192 'nopermissions' => true 193 ]); 194 195 $hosts = implode(', ', zbx_objectValues($hosts, 'host')); 196 $templates = implode(', ', zbx_objectValues($templates, 'host')); 197 198 info(_s('Templates "%1$s" unlinked from hosts "%2$s".', $templates, $hosts)); 199 } 200 } 201 202 /** 203 * Searches for circular linkages for specific template. 204 * 205 * @param array $links[<templateid>][<hostid>] The list of linkages. 206 * @param string $templateid ID of the template to check circular linkages. 207 * @param array $hostids[<hostid>] 208 * 209 * @throws APIException if circular linkage is found. 210 */ 211 private static function checkTemplateCircularLinkage(array $links, $templateid, array $hostids) { 212 if (array_key_exists($templateid, $hostids)) { 213 self::exception(ZBX_API_ERROR_PARAMETERS, _('Circular template linkage is not allowed.')); 214 } 215 216 foreach ($hostids as $hostid => $foo) { 217 if (array_key_exists($hostid, $links)) { 218 self::checkTemplateCircularLinkage($links, $templateid, $links[$hostid]); 219 } 220 } 221 } 222 223 /** 224 * Searches for circular linkages. 225 * 226 * @param array $host_templates 227 * @param string $host_templates[]['templateid'] 228 * @param string $host_templates[]['hostid'] 229 */ 230 private static function checkCircularLinkage(array $host_templates) { 231 $links = []; 232 233 foreach ($host_templates as $host_template) { 234 $links[$host_template['templateid']][$host_template['hostid']] = true; 235 } 236 237 $templateids = array_keys($links); 238 $_templateids = $templateids; 239 240 do { 241 $result = DBselect( 242 'SELECT ht.templateid,ht.hostid'. 243 ' FROM hosts_templates ht'. 244 ' WHERE '.dbConditionId('ht.hostid', $_templateids) 245 ); 246 247 $_templateids = []; 248 249 while ($row = DBfetch($result)) { 250 if (!array_key_exists($row['templateid'], $links)) { 251 $_templateids[$row['templateid']] = true; 252 } 253 254 $links[$row['templateid']][$row['hostid']] = true; 255 } 256 257 $_templateids = array_keys($_templateids); 258 } 259 while ($_templateids); 260 261 foreach ($templateids as $templateid) { 262 self::checkTemplateCircularLinkage($links, $templateid, $links[$templateid]); 263 } 264 } 265 266 /** 267 * Searches for double linkages. 268 * 269 * @param array $links[<hostid>][<templateid>] The list of linked template IDs by host ID. 270 * @param string $hostid 271 * 272 * @throws APIException if double linkage is found. 273 * 274 * @return array An array of the linked templates for the selected host. 275 */ 276 private static function checkTemplateDoubleLinkage(array $links, $hostid) { 277 $templateids = $links[$hostid]; 278 279 foreach ($links[$hostid] as $templateid => $foo) { 280 if (array_key_exists($templateid, $links)) { 281 $_templateids = self::checkTemplateDoubleLinkage($links, $templateid); 282 283 if (array_intersect_key($templateids, $_templateids)) { 284 self::exception(ZBX_API_ERROR_PARAMETERS, 285 _('Template cannot be linked to another template more than once even through other templates.') 286 ); 287 } 288 289 $templateids += $_templateids; 290 } 291 } 292 293 return $templateids; 294 } 295 296 /** 297 * Searches for double linkages. 298 * 299 * @param array $host_templates 300 * @param string $host_templates[]['templateid'] 301 * @param string $host_templates[]['hostid'] 302 */ 303 private static function checkDoubleLinkage(array $host_templates) { 304 $links = []; 305 $templateids = []; 306 $hostids = []; 307 308 foreach ($host_templates as $host_template) { 309 $links[$host_template['hostid']][$host_template['templateid']] = true; 310 $templateids[$host_template['templateid']] = true; 311 $hostids[$host_template['hostid']] = true; 312 } 313 314 $_hostids = array_keys($hostids); 315 316 do { 317 $result = DBselect( 318 'SELECT ht.hostid'. 319 ' FROM hosts_templates ht'. 320 ' WHERE '.dbConditionId('ht.templateid', $_hostids) 321 ); 322 323 $_hostids = []; 324 325 while ($row = DBfetch($result)) { 326 if (!array_key_exists($row['hostid'], $hostids)) { 327 $_hostids[$row['hostid']] = true; 328 } 329 330 $hostids[$row['hostid']] = true; 331 } 332 333 $_hostids = array_keys($_hostids); 334 } 335 while ($_hostids); 336 337 $_templateids = array_keys($templateids + $hostids); 338 $templateids = []; 339 340 do { 341 $result = DBselect( 342 'SELECT ht.templateid,ht.hostid'. 343 ' FROM hosts_templates ht'. 344 ' WHERE '.dbConditionId('hostid', $_templateids) 345 ); 346 347 $_templateids = []; 348 349 while ($row = DBfetch($result)) { 350 if (!array_key_exists($row['templateid'], $templateids)) { 351 $_templateids[$row['templateid']] = true; 352 } 353 354 $templateids[$row['templateid']] = true; 355 $links[$row['hostid']][$row['templateid']] = true; 356 } 357 358 $_templateids = array_keys($_templateids); 359 } 360 while ($_templateids); 361 362 foreach ($hostids as $hostid => $foo) { 363 self::checkTemplateDoubleLinkage($links, $hostid); 364 } 365 } 366} 367