1<?php declare(strict_types = 1); 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/forms.inc.php'; 23 24class CControllerPopupMassupdateHost extends CControllerPopupMassupdateAbstract { 25 26 protected function checkInput() { 27 $fields = [ 28 'ids' => 'required|array', 29 'update' => 'in 1', 30 'visible' => 'array', 31 'tags' => 'array', 32 'macros' => 'array', 33 'groups' => 'array', 34 'host_inventory' => 'array', 35 'templates' => 'array', 36 'inventories' => 'array', 37 'description' => 'string', 38 'proxy_hostid' => 'string', 39 'ipmi_username' => 'string', 40 'ipmi_password' => 'string', 41 'tls_issuer' => 'string', 42 'tls_subject' => 'string', 43 'tls_psk_identity' => 'string', 44 'tls_psk' => 'string', 45 'valuemaps' => 'array', 46 'valuemap_remove' => 'array', 47 'valuemap_remove_except' => 'in 1', 48 'valuemap_remove_all' => 'in 1', 49 'valuemap_rename' => 'array', 50 'valuemap_update_existing' => 'in 1', 51 'valuemap_add_missing' => 'in 1', 52 'macros_add' => 'in 0,1', 53 'macros_update' => 'in 0,1', 54 'macros_remove' => 'in 0,1', 55 'macros_remove_all' => 'in 0,1', 56 'mass_clear_tpls' => 'in 0,1', 57 'mass_action_tpls' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]), 58 'mass_update_groups' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]), 59 'mass_update_tags' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]), 60 'mass_update_macros' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE, ZBX_ACTION_REMOVE_ALL]), 61 'valuemap_massupdate' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE, ZBX_ACTION_RENAME, ZBX_ACTION_REMOVE_ALL]), 62 'inventory_mode' => 'in '.implode(',', [HOST_INVENTORY_DISABLED, HOST_INVENTORY_MANUAL, HOST_INVENTORY_AUTOMATIC]), 63 'status' => 'in '.implode(',', [HOST_STATUS_MONITORED, HOST_STATUS_NOT_MONITORED]), 64 'tls_connect' => 'in '.implode(',', [HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, HOST_ENCRYPTION_CERTIFICATE]), 65 'tls_accept' => 'ge 0|le '.(HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE), 66 'ipmi_authtype' => 'in '.implode(',', [IPMI_AUTHTYPE_DEFAULT, IPMI_AUTHTYPE_NONE, IPMI_AUTHTYPE_MD2, IPMI_AUTHTYPE_MD5, IPMI_AUTHTYPE_STRAIGHT, IPMI_AUTHTYPE_OEM, IPMI_AUTHTYPE_RMCP_PLUS]), 67 'ipmi_privilege' => 'in '.implode(',', [IPMI_PRIVILEGE_CALLBACK, IPMI_PRIVILEGE_USER, IPMI_PRIVILEGE_OPERATOR, IPMI_PRIVILEGE_ADMIN, IPMI_PRIVILEGE_OEM]) 68 ]; 69 70 $ret = $this->validateInput($fields); 71 72 if (!$ret) { 73 $output = []; 74 if (($messages = getMessages()) !== null) { 75 $output['errors'] = $messages->toString(); 76 } 77 78 $this->setResponse( 79 (new CControllerResponseData(['main_block' => json_encode($output)]))->disableView() 80 ); 81 } 82 83 return $ret; 84 } 85 86 protected function checkPermissions() { 87 $hosts = API::Host()->get([ 88 'output' => [], 89 'hostids' => $this->getInput('ids'), 90 'editable' => true 91 ]); 92 93 return count($hosts) > 0; 94 } 95 96 protected function doAction() { 97 if ($this->hasInput('update')) { 98 $output = []; 99 $hostids = $this->getInput('ids'); 100 $visible = $this->getInput('visible', []); 101 $macros = array_filter(cleanInheritedMacros($this->getInput('macros', [])), 102 function (array $macro): bool { 103 return (bool) array_filter( 104 array_intersect_key($macro, array_flip(['hostmacroid', 'macro', 'value', 'description'])) 105 ); 106 } 107 ); 108 $tags = array_filter($this->getInput('tags', []), 109 function (array $tag): bool { 110 return ($tag['tag'] !== '' || $tag['value'] !== ''); 111 } 112 ); 113 114 $result = true; 115 116 try { 117 DBstart(); 118 119 // filter only normal and discovery created hosts 120 $options = [ 121 'output' => ['hostid', 'inventory_mode', 'flags'], 122 'hostids' => $hostids, 123 'filter' => ['flags' => [ZBX_FLAG_DISCOVERY_NORMAL, ZBX_FLAG_DISCOVERY_CREATED]] 124 ]; 125 126 if (array_key_exists('groups', $visible)) { 127 $options['selectGroups'] = ['groupid']; 128 } 129 130 if (array_key_exists('templates', $visible) 131 && !($this->getInput('mass_action_tpls') == ZBX_ACTION_REPLACE 132 && !$this->hasInput('mass_clear_tpls'))) { 133 $options['selectParentTemplates'] = ['templateid']; 134 } 135 136 if (array_key_exists('tags', $visible)) { 137 $mass_update_tags = $this->getInput('mass_update_tags', ZBX_ACTION_ADD); 138 139 if ($mass_update_tags == ZBX_ACTION_ADD || $mass_update_tags == ZBX_ACTION_REMOVE) { 140 $options['selectTags'] = ['tag', 'value']; 141 } 142 143 $unique_tags = []; 144 145 foreach ($tags as $tag) { 146 $unique_tags[$tag['tag'].':'.$tag['value']] = $tag; 147 } 148 149 $tags = array_values($unique_tags); 150 } 151 152 if (array_key_exists('macros', $visible)) { 153 $mass_update_macros = $this->getInput('mass_update_macros', ZBX_ACTION_ADD); 154 155 if ($mass_update_macros == ZBX_ACTION_ADD || $mass_update_macros == ZBX_ACTION_REPLACE 156 || $mass_update_macros == ZBX_ACTION_REMOVE) { 157 $options['selectMacros'] = ['hostmacroid', 'macro']; 158 } 159 } 160 161 $hosts = API::Host()->get($options); 162 163 if (array_key_exists('groups', $visible)) { 164 $new_groupids = []; 165 $remove_groupids = []; 166 $mass_update_groups = $this->getInput('mass_update_groups', ZBX_ACTION_ADD); 167 168 if ($mass_update_groups == ZBX_ACTION_ADD || $mass_update_groups == ZBX_ACTION_REPLACE) { 169 if (CWebUser::getType() == USER_TYPE_SUPER_ADMIN) { 170 $ins_groups = []; 171 172 foreach ($this->getInput('groups', []) as $new_group) { 173 if (is_array($new_group) && array_key_exists('new', $new_group)) { 174 $ins_groups[] = ['name' => $new_group['new']]; 175 } 176 else { 177 $new_groupids[] = $new_group; 178 } 179 } 180 181 if ($ins_groups) { 182 if (!$result = API::HostGroup()->create($ins_groups)) { 183 throw new Exception(); 184 } 185 186 $new_groupids = array_merge($new_groupids, $result['groupids']); 187 } 188 } 189 else { 190 $new_groupids = $this->getInput('groups', []); 191 } 192 } 193 elseif ($mass_update_groups == ZBX_ACTION_REMOVE) { 194 $remove_groupids = $this->getInput('groups', []); 195 } 196 } 197 198 $properties = ['description', 'proxy_hostid', 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username', 199 'ipmi_password' 200 ]; 201 202 $new_values = []; 203 foreach ($properties as $property) { 204 if (array_key_exists($property, $visible)) { 205 $new_values[$property] = $this->getInput($property); 206 } 207 } 208 209 if (array_key_exists('status', $visible)) { 210 $new_values['status'] = $this->getInput('status', HOST_STATUS_NOT_MONITORED); 211 } 212 213 $host_inventory = array_intersect_key($this->getInput('host_inventory', []), $visible); 214 215 if (array_key_exists('inventory_mode', $visible)) { 216 $new_values['inventory_mode'] = $this->getInput('inventory_mode', HOST_INVENTORY_DISABLED); 217 218 if ($new_values['inventory_mode'] == HOST_INVENTORY_DISABLED) { 219 $host_inventory = []; 220 } 221 } 222 223 if (array_key_exists('encryption', $visible)) { 224 $new_values['tls_connect'] = $this->getInput('tls_connect', HOST_ENCRYPTION_NONE); 225 $new_values['tls_accept'] = $this->getInput('tls_accept', HOST_ENCRYPTION_NONE); 226 227 if ($new_values['tls_connect'] == HOST_ENCRYPTION_PSK 228 || ($new_values['tls_accept'] & HOST_ENCRYPTION_PSK)) { 229 $new_values['tls_psk_identity'] = $this->getInput('tls_psk_identity', ''); 230 $new_values['tls_psk'] = $this->getInput('tls_psk', ''); 231 } 232 233 if ($new_values['tls_connect'] == HOST_ENCRYPTION_CERTIFICATE 234 || ($new_values['tls_accept'] & HOST_ENCRYPTION_CERTIFICATE)) { 235 $new_values['tls_issuer'] = $this->getInput('tls_issuer', ''); 236 $new_values['tls_subject'] = $this->getInput('tls_subject', ''); 237 } 238 } 239 240 foreach ($hosts as &$host) { 241 if (array_key_exists('groups', $visible)) { 242 if ($new_groupids && $mass_update_groups == ZBX_ACTION_ADD) { 243 $current_groupids = array_column($host['groups'], 'groupid'); 244 $host['groups'] = zbx_toObject(array_unique(array_merge($current_groupids, $new_groupids)), 245 'groupid' 246 ); 247 } 248 elseif ($new_groupids && $mass_update_groups == ZBX_ACTION_REPLACE) { 249 $host['groups'] = zbx_toObject($new_groupids, 'groupid'); 250 } 251 elseif ($remove_groupids) { 252 $current_groupids = array_column($host['groups'], 'groupid'); 253 $host['groups'] = zbx_toObject(array_diff($current_groupids, $remove_groupids), 'groupid'); 254 } 255 } 256 257 if (array_key_exists('templates', $visible)) { 258 $host_templateids = array_key_exists('parentTemplates', $host) 259 ? array_column($host['parentTemplates'], 'templateid') 260 : []; 261 262 switch ($this->getInput('mass_action_tpls')) { 263 case ZBX_ACTION_ADD: 264 $host['templates'] = array_unique( 265 array_merge($host_templateids, $this->getInput('templates', [])) 266 ); 267 break; 268 269 case ZBX_ACTION_REPLACE: 270 $host['templates'] = $this->getInput('templates', []); 271 if ($this->hasInput('mass_clear_tpls')) { 272 $host['templates_clear'] = array_unique( 273 array_diff($host_templateids, $this->getInput('templates', [])) 274 ); 275 } 276 break; 277 278 case ZBX_ACTION_REMOVE: 279 $host['templates'] = array_unique( 280 array_diff($host_templateids, $this->getInput('templates', [])) 281 ); 282 if ($this->hasInput('mass_clear_tpls')) { 283 $host['templates_clear'] = array_unique($this->getInput('templates', [])); 284 } 285 break; 286 } 287 } 288 289 /* 290 * Inventory mode cannot be changed for discovered hosts. If discovered host has disabled inventory 291 * mode, inventory values also cannot be changed. 292 */ 293 if (array_key_exists('inventory_mode', $new_values) 294 && $host['flags'] != ZBX_FLAG_DISCOVERY_CREATED) { 295 $host['inventory'] = $host_inventory; 296 } 297 elseif ($host['inventory_mode'] != HOST_INVENTORY_DISABLED) { 298 $host['inventory'] = $host_inventory; 299 } 300 else { 301 $host['inventory'] = []; 302 } 303 304 if (array_key_exists('tags', $visible)) { 305 if ($tags && $mass_update_tags == ZBX_ACTION_ADD) { 306 $unique_tags = []; 307 308 foreach (array_merge($host['tags'], $tags) as $tag) { 309 $unique_tags[$tag['tag'].':'.$tag['value']] = $tag; 310 } 311 312 $host['tags'] = array_values($unique_tags); 313 } 314 elseif ($mass_update_tags == ZBX_ACTION_REPLACE) { 315 $host['tags'] = $tags; 316 } 317 elseif ($tags && $mass_update_tags == ZBX_ACTION_REMOVE) { 318 $diff_tags = []; 319 320 foreach ($host['tags'] as $a) { 321 foreach ($tags as $b) { 322 if ($a['tag'] === $b['tag'] && $a['value'] === $b['value']) { 323 continue 2; 324 } 325 } 326 327 $diff_tags[] = $a; 328 } 329 330 $host['tags'] = $diff_tags; 331 } 332 } 333 334 if (array_key_exists('macros', $visible)) { 335 switch ($mass_update_macros) { 336 case ZBX_ACTION_ADD: 337 $update_existing = (bool) getRequest('macros_add', 0); 338 $host['macros'] = array_column($host['macros'], null, 'hostmacroid'); 339 $host_macros_by_macro = array_column($host['macros'], null, 'macro'); 340 341 foreach ($macros as $macro) { 342 if (!array_key_exists($macro['macro'], $host_macros_by_macro)) { 343 $host['macros'][] = $macro; 344 } 345 elseif ($update_existing) { 346 $hostmacroid = $host_macros_by_macro[$macro['macro']]['hostmacroid']; 347 $host['macros'][$hostmacroid] = ['hostmacroid' => $hostmacroid] + $macro; 348 } 349 } 350 break; 351 352 case ZBX_ACTION_REPLACE: 353 $add_missing = (bool) getRequest('macros_update', 0); 354 $host['macros'] = array_column($host['macros'], null, 'hostmacroid'); 355 $host_macros_by_macro = array_column($host['macros'], null, 'macro'); 356 357 foreach ($macros as $macro) { 358 if (array_key_exists($macro['macro'], $host_macros_by_macro)) { 359 $hostmacroid = $host_macros_by_macro[$macro['macro']]['hostmacroid']; 360 $host['macros'][$hostmacroid] = ['hostmacroid' => $hostmacroid] + $macro; 361 } 362 elseif ($add_missing) { 363 $host['macros'][] = $macro; 364 } 365 } 366 break; 367 368 case ZBX_ACTION_REMOVE: 369 if ($macros) { 370 $except_selected = $this->getInput('macros_remove', 0); 371 $host_macros_by_macro = array_column($host['macros'], null, 'macro'); 372 $macros_by_macro = array_column($macros, null, 'macro'); 373 374 $host['macros'] = $except_selected 375 ? array_intersect_key($host_macros_by_macro, $macros_by_macro) 376 : array_diff_key($host_macros_by_macro, $macros_by_macro); 377 } 378 break; 379 380 case ZBX_ACTION_REMOVE_ALL: 381 if (!$this->getInput('macros_remove_all', 0)) { 382 throw new Exception(); 383 } 384 385 $host['macros'] = []; 386 break; 387 } 388 389 $host['macros'] = array_values($host['macros']); 390 } 391 392 unset($host['parentTemplates']); 393 394 $host = $new_values + $host; 395 396 /* 397 * API prevents changing host inventory_mode for discovered hosts. However, inventory values can 398 * still be updated if inventory mode allows it. 399 */ 400 if ($host['flags'] == ZBX_FLAG_DISCOVERY_CREATED) { 401 unset($host['inventory_mode']); 402 } 403 unset($host['flags']); 404 } 405 unset($host); 406 407 $result = $hosts ? (bool) API::Host()->update($hosts) : true; 408 409 if ($result === false) { 410 throw new Exception(); 411 } 412 413 // Value mapping. 414 if (array_key_exists('valuemaps', $visible)) { 415 $this->updateValueMaps($hostids); 416 } 417 418 DBend(true); 419 } 420 catch (Exception $e) { 421 DBend(false); 422 423 CMessageHelper::setErrorTitle(_('Cannot update hosts')); 424 425 $result = false; 426 } 427 428 if ($result) { 429 $messages = CMessageHelper::getMessages(); 430 $output = ['title' => _('Hosts updated')]; 431 if (count($messages)) { 432 $output['messages'] = array_column($messages, 'message'); 433 } 434 } 435 else { 436 $output['errors'] = makeMessageBox(ZBX_STYLE_MSG_BAD, filter_messages(), CMessageHelper::getTitle()) 437 ->toString(); 438 } 439 440 $this->setResponse( 441 (new CControllerResponseData(['main_block' => json_encode($output)]))->disableView() 442 ); 443 } 444 else { 445 $data = [ 446 'title' => _('Mass update'), 447 'user' => [ 448 'debug_mode' => $this->getDebugMode() 449 ], 450 'ids' => $this->getInput('ids'), 451 'inventories' => zbx_toHash(getHostInventories(), 'db_field'), 452 'location_url' => 'hosts.php' 453 ]; 454 455 $data['proxies'] = API::Proxy()->get([ 456 'output' => ['hostid', 'host'], 457 'filter' => [ 458 'status' => [HOST_STATUS_PROXY_ACTIVE, HOST_STATUS_PROXY_PASSIVE] 459 ], 460 'sortfield' => 'host' 461 ]); 462 463 $data['discovered_host'] = !(bool) API::Host()->get([ 464 'output' => [], 465 'hostids' => $data['ids'], 466 'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL], 467 'limit' => 1 468 ]); 469 470 $this->setResponse(new CControllerResponseData($data)); 471 } 472 } 473} 474