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 22/** 23 * Class containing methods for operations with maintenances. 24 */ 25class CMaintenance extends CApiService { 26 27 protected $tableName = 'maintenances'; 28 protected $tableAlias = 'm'; 29 protected $sortColumns = ['maintenanceid', 'name', 'maintenance_type', 'active_till', 'active_since']; 30 31 /** 32 * Get maintenances data. 33 * 34 * @param array $options 35 * @param array $options['itemids'] 36 * @param array $options['hostids'] 37 * @param array $options['groupids'] 38 * @param array $options['triggerids'] 39 * @param array $options['maintenanceids'] 40 * @param bool $options['status'] 41 * @param bool $options['editable'] 42 * @param bool $options['count'] 43 * @param string $options['pattern'] 44 * @param int $options['limit'] 45 * @param string $options['order'] 46 * 47 * @return array 48 */ 49 public function get(array $options = []) { 50 $result = []; 51 52 $sqlParts = [ 53 'select' => ['maintenance' => 'm.maintenanceid'], 54 'from' => ['maintenances' => 'maintenances m'], 55 'where' => [], 56 'group' => [], 57 'order' => [], 58 'limit' => null 59 ]; 60 61 $defOptions = [ 62 'groupids' => null, 63 'hostids' => null, 64 'maintenanceids' => null, 65 'editable' => false, 66 'nopermissions' => null, 67 // filter 68 'filter' => null, 69 'search' => null, 70 'searchByAny' => null, 71 'startSearch' => false, 72 'excludeSearch' => false, 73 'searchWildcardsEnabled' => null, 74 // output 75 'output' => API_OUTPUT_EXTEND, 76 'selectGroups' => null, 77 'selectHosts' => null, 78 'selectTags' => null, 79 'selectTimeperiods' => null, 80 'countOutput' => false, 81 'groupCount' => false, 82 'preservekeys' => false, 83 'sortfield' => '', 84 'sortorder' => '', 85 'limit' => null 86 ]; 87 $options = zbx_array_merge($defOptions, $options); 88 89 // editable + PERMISSION CHECK 90 $maintenanceids = []; 91 if (self::$userData['type'] == USER_TYPE_SUPER_ADMIN || $options['nopermissions']) { 92 if (!is_null($options['groupids']) || !is_null($options['hostids'])) { 93 if (!is_null($options['groupids'])) { 94 zbx_value2array($options['groupids']); 95 $res = DBselect( 96 'SELECT mmg.maintenanceid'. 97 ' FROM maintenances_groups mmg'. 98 ' WHERE '.dbConditionInt('mmg.groupid', $options['groupids']) 99 ); 100 while ($maintenance = DBfetch($res)) { 101 $maintenanceids[] = $maintenance['maintenanceid']; 102 } 103 } 104 105 $sql = 'SELECT mmh.maintenanceid'. 106 ' FROM maintenances_hosts mmh,hosts_groups hg'. 107 ' WHERE hg.hostid=mmh.hostid'; 108 109 if (!is_null($options['groupids'])) { 110 zbx_value2array($options['groupids']); 111 $sql .= ' AND '.dbConditionInt('hg.groupid', $options['groupids']); 112 } 113 114 if (!is_null($options['hostids'])) { 115 zbx_value2array($options['hostids']); 116 $sql .= ' AND '.dbConditionInt('hg.hostid', $options['hostids']); 117 } 118 $res = DBselect($sql); 119 while ($maintenance = DBfetch($res)) { 120 $maintenanceids[] = $maintenance['maintenanceid']; 121 } 122 $sqlParts['where'][] = dbConditionInt('m.maintenanceid', $maintenanceids); 123 } 124 } 125 else { 126 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 127 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 128 129 $sql = 'SELECT m.maintenanceid'. 130 ' FROM maintenances m'. 131 ' WHERE NOT EXISTS ('. 132 'SELECT NULL'. 133 ' FROM maintenances_hosts mh,hosts_groups hg'. 134 ' LEFT JOIN rights r'. 135 ' ON r.id=hg.groupid'. 136 ' AND '.dbConditionInt('r.groupid', $userGroups). 137 ' WHERE m.maintenanceid=mh.maintenanceid'. 138 ' AND mh.hostid=hg.hostid'. 139 ' GROUP by mh.hostid'. 140 ' HAVING MIN(r.permission) IS NULL'. 141 ' OR MIN(r.permission)='.PERM_DENY. 142 ' OR MAX(r.permission)<'.zbx_dbstr($permission). 143 ')'. 144 ' AND NOT EXISTS ('. 145 'SELECT NULL'. 146 ' FROM maintenances_groups mg'. 147 ' LEFT JOIN rights r'. 148 ' ON r.id=mg.groupid'. 149 ' AND '.dbConditionInt('r.groupid', $userGroups). 150 ' WHERE m.maintenanceid=mg.maintenanceid'. 151 ' GROUP by mg.groupid'. 152 ' HAVING MIN(r.permission) IS NULL'. 153 ' OR MIN(r.permission)='.PERM_DENY. 154 ' OR MAX(r.permission)<'.zbx_dbstr($permission). 155 ')'; 156 157 if (!is_null($options['groupids'])) { 158 zbx_value2array($options['groupids']); 159 $sql .= ' AND ('. 160 'EXISTS ('. 161 'SELECT NULL'. 162 ' FROM maintenances_groups mg'. 163 ' WHERE m.maintenanceid=mg.maintenanceid'. 164 ' AND '.dbConditionInt('mg.groupid', $options['groupids']). 165 ')'. 166 ' OR EXISTS ('. 167 'SELECT NULL'. 168 ' FROM maintenances_hosts mh,hosts_groups hg'. 169 ' WHERE m.maintenanceid=mh.maintenanceid'. 170 ' AND mh.hostid=hg.hostid'. 171 ' AND '.dbConditionInt('hg.groupid', $options['groupids']). 172 ')'. 173 ')'; 174 } 175 176 if (!is_null($options['hostids'])) { 177 zbx_value2array($options['hostids']); 178 $sql .= ' AND EXISTS ('. 179 'SELECT NULL'. 180 ' FROM maintenances_hosts mh'. 181 ' WHERE m.maintenanceid=mh.maintenanceid'. 182 ' AND '.dbConditionInt('mh.hostid', $options['hostids']). 183 ')'; 184 } 185 186 if (!is_null($options['maintenanceids'])) { 187 zbx_value2array($options['maintenanceids']); 188 $sql .= ' AND '.dbConditionInt('m.maintenanceid', $options['maintenanceids']); 189 } 190 191 $res = DBselect($sql); 192 while ($maintenance = DBfetch($res)) { 193 $maintenanceids[] = $maintenance['maintenanceid']; 194 } 195 $sqlParts['where'][] = dbConditionInt('m.maintenanceid', $maintenanceids); 196 } 197 198 // maintenanceids 199 if (!is_null($options['maintenanceids'])) { 200 zbx_value2array($options['maintenanceids']); 201 202 $sqlParts['where'][] = dbConditionInt('m.maintenanceid', $options['maintenanceids']); 203 } 204 205 // filter 206 if (is_array($options['filter'])) { 207 $this->dbFilter('maintenances m', $options, $sqlParts); 208 } 209 210 // search 211 if (is_array($options['search'])) { 212 zbx_db_search('maintenances m', $options, $sqlParts); 213 } 214 215 // limit 216 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 217 $sqlParts['limit'] = $options['limit']; 218 } 219 220 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 221 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 222 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 223 while ($maintenance = DBfetch($res)) { 224 if ($options['countOutput']) { 225 if ($options['groupCount']) { 226 $result[] = $maintenance; 227 } 228 else { 229 $result = $maintenance['rowscount']; 230 } 231 } 232 else { 233 $result[$maintenance['maintenanceid']] = $maintenance; 234 } 235 } 236 237 if ($options['countOutput']) { 238 return $result; 239 } 240 241 if ($result) { 242 $result = $this->addRelatedObjects($options, $result); 243 } 244 245 if (!$options['preservekeys']) { 246 $result = zbx_cleanHashes($result); 247 } 248 return $result; 249 } 250 251 /** 252 * Add maintenances. 253 * 254 * @param array $maintenances 255 * 256 * @throws APIException if no permissions to object, it does no exists or validation errors. 257 * 258 * @return array 259 */ 260 public function create(array $maintenances) { 261 $maintenances = zbx_toArray($maintenances); 262 if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) { 263 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 264 } 265 266 $hostids = []; 267 $groupids = []; 268 foreach ($maintenances as $maintenance) { 269 if (array_key_exists('hostids', $maintenance)) { 270 $hostids = array_merge($hostids, $maintenance['hostids']); 271 } 272 if (array_key_exists('groupids', $maintenance)) { 273 $groupids = array_merge($groupids, $maintenance['groupids']); 274 } 275 } 276 277 // validate hosts & groups 278 if (empty($hostids) && empty($groupids)) { 279 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one host group or host must be selected.')); 280 } 281 282 // hosts permissions 283 $options = [ 284 'hostids' => $hostids, 285 'editable' => true, 286 'output' => ['hostid'], 287 'preservekeys' => true 288 ]; 289 $updHosts = API::Host()->get($options); 290 foreach ($hostids as $hostid) { 291 if (!isset($updHosts[$hostid])) { 292 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 293 } 294 } 295 // groups permissions 296 $options = [ 297 'groupids' => $groupids, 298 'editable' => true, 299 'output' => ['groupid'], 300 'preservekeys' => true 301 ]; 302 $updGroups = API::HostGroup()->get($options); 303 foreach ($groupids as $groupid) { 304 if (!isset($updGroups[$groupid])) { 305 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 306 } 307 } 308 309 $tid = 0; 310 $insert = []; 311 $timeperiods = []; 312 $insertTimeperiods = []; 313 $now = time(); 314 $now -= $now % SEC_PER_MIN; 315 316 // check fields 317 foreach ($maintenances as $maintenance) { 318 $dbFields = [ 319 'name' => null, 320 'active_since' => null, 321 'active_till' => null 322 ]; 323 324 if (!check_db_fields($dbFields, $maintenance)) { 325 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for maintenance.')); 326 } 327 } 328 329 $collectionValidator = new CCollectionValidator([ 330 'uniqueField' => 'name', 331 'messageDuplicate' => _('Maintenance "%1$s" already exists.') 332 ]); 333 $this->checkValidator($maintenances, $collectionValidator); 334 335 // validate if maintenance name already exists 336 $dbMaintenances = $this->get([ 337 'output' => ['name'], 338 'filter' => ['name' => zbx_objectValues($maintenances, 'name')], 339 'nopermissions' => true, 340 'limit' => 1 341 ]); 342 343 if ($dbMaintenances) { 344 $dbMaintenance = reset($dbMaintenances); 345 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Maintenance "%1$s" already exists.', $dbMaintenance['name'])); 346 } 347 348 foreach ($maintenances as $mnum => $maintenance) { 349 // validate maintenance active since 350 if (!validateUnixTime($maintenance['active_since'])) { 351 self::exception(ZBX_API_ERROR_PARAMETERS, 352 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'active_since') 353 ); 354 } 355 356 // validate maintenance active till 357 if (!validateUnixTime($maintenance['active_till'])) { 358 self::exception(ZBX_API_ERROR_PARAMETERS, 359 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'active_till') 360 ); 361 } 362 363 // validate maintenance active interval 364 if ($maintenance['active_since'] > $maintenance['active_till']) { 365 self::exception(ZBX_API_ERROR_PARAMETERS, 366 _s('Maintenance "%1$s" value cannot be bigger than "%2$s".', 'active_since', 'active_till') 367 ); 368 } 369 370 // validate timeperiods 371 if (!array_key_exists('timeperiods', $maintenance) || !is_array($maintenance['timeperiods']) 372 || !$maintenance['timeperiods']) { 373 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 374 } 375 376 $maintenance['active_since'] -= $maintenance['active_since'] % SEC_PER_MIN; 377 $maintenance['active_till'] -= $maintenance['active_till'] % SEC_PER_MIN; 378 379 foreach ($maintenance['timeperiods'] as $timeperiod) { 380 if (!is_array($timeperiod)) { 381 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 382 } 383 384 $dbFields = [ 385 'timeperiod_type' => TIMEPERIOD_TYPE_ONETIME, 386 'period' => SEC_PER_HOUR, 387 'start_date' => $now 388 ]; 389 check_db_fields($dbFields, $timeperiod); 390 391 if (array_key_exists('every', $timeperiod) && $timeperiod['every'] <= 0) { 392 self::exception(ZBX_API_ERROR_PARAMETERS, 393 _s('Incorrect value "%1$s" for unsigned int field "%2$s".', $timeperiod['every'], 'every') 394 ); 395 } 396 397 if ($timeperiod['timeperiod_type'] != TIMEPERIOD_TYPE_ONETIME) { 398 $timeperiod['start_date'] = DB::getDefault('timeperiods', 'start_date'); 399 } 400 else if (!validateUnixTime($timeperiod['start_date'])) { 401 self::exception(ZBX_API_ERROR_PARAMETERS, 402 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'start_date') 403 ); 404 } 405 else { 406 $timeperiod['start_date'] -= $timeperiod['start_date'] % SEC_PER_MIN; 407 } 408 409 $tid++; 410 $insertTimeperiods[$tid] = $timeperiod; 411 $timeperiods[$tid] = $mnum; 412 } 413 414 $insert[$mnum] = $maintenance; 415 416 $this->validateTags($maintenance); 417 } 418 419 $maintenanceids = DB::insert('maintenances', $insert); 420 $timeperiodids = DB::insert('timeperiods', $insertTimeperiods); 421 422 $insertWindows = []; 423 foreach ($timeperiods as $tid => $mnum) { 424 $insertWindows[] = [ 425 'timeperiodid' => $timeperiodids[$tid], 426 'maintenanceid' => $maintenanceids[$mnum] 427 ]; 428 } 429 DB::insertBatch('maintenances_windows', $insertWindows); 430 431 $insertHosts = []; 432 $insertGroups = []; 433 $ins_tags = []; 434 foreach ($maintenances as $mnum => &$maintenance) { 435 $maintenance['maintenanceid'] = $maintenanceids[$mnum]; 436 437 if (array_key_exists('hostids', $maintenance)) { 438 foreach ($maintenance['hostids'] as $hostid) { 439 $insertHosts[] = [ 440 'hostid' => $hostid, 441 'maintenanceid' => $maintenance['maintenanceid'] 442 ]; 443 } 444 } 445 446 if (array_key_exists('groupids', $maintenance)) { 447 foreach ($maintenance['groupids'] as $groupid) { 448 $insertGroups[] = [ 449 'groupid' => $groupid, 450 'maintenanceid' => $maintenance['maintenanceid'] 451 ]; 452 } 453 } 454 455 if (array_key_exists('tags', $maintenance)) { 456 foreach ($maintenance['tags'] as $tag) { 457 $ins_tags[] = [ 458 'maintenanceid' => $maintenance['maintenanceid'] 459 ] + $tag; 460 } 461 } 462 } 463 unset($maintenance); 464 465 DB::insertBatch('maintenances_hosts', $insertHosts); 466 DB::insertBatch('maintenances_groups', $insertGroups); 467 468 if ($ins_tags) { 469 DB::insert('maintenance_tag', $ins_tags); 470 } 471 472 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MAINTENANCE, $maintenances); 473 474 return ['maintenanceids' => $maintenanceids]; 475 } 476 477 /** 478 * Validates maintenance problem tags. 479 * 480 * @param array $maintenance 481 * @param int $maintenance['maintenance_type'] 482 * @param int $maintenance['tags_evaltype'] 483 * @param array $maintenance['tags'] 484 * @param string $maintenance['tags'][]['tag'] 485 * @param int $maintenance['tags'][]['operator'] 486 * @param string $maintenance['tags'][]['value'] 487 * 488 * @throws APIException if the input is invalid. 489 */ 490 private function validateTags(array $maintenance) { 491 if (array_key_exists('maintenance_type', $maintenance) 492 && $maintenance['maintenance_type'] == MAINTENANCE_TYPE_NODATA 493 && array_key_exists('tags', $maintenance) && $maintenance['tags']) { 494 self::exception(ZBX_API_ERROR_PARAMETERS, 495 _s('Incorrect value for field "%1$s": %2$s.', 'tags', _('should be empty')) 496 ); 497 } 498 499 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 500 'maintenance_type' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TYPE_NORMAL, MAINTENANCE_TYPE_NODATA])], 501 'tags_evaltype' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TAG_EVAL_TYPE_AND_OR, MAINTENANCE_TAG_EVAL_TYPE_OR])], 502 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'operator', 'value']], 'fields' => [ 503 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('maintenance_tag', 'tag')], 504 'operator' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TAG_OPERATOR_EQUAL, MAINTENANCE_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('maintenance_tag', 'operator')], 505 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('maintenance_tag', 'value'), 'default' => DB::getDefault('maintenance_tag', 'value')] 506 ]] 507 ]]; 508 509 // Keep values only for fields with defined validation rules. 510 $maintenance = array_intersect_key($maintenance, $api_input_rules['fields']); 511 512 if (!CApiInputValidator::validate($api_input_rules, $maintenance, '/', $error)) { 513 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 514 } 515 } 516 517 /** 518 * Update maintenances. 519 * 520 * @param array $maintenances 521 * 522 * @throws APIException if no permissions to object, it does no exists or validation errors 523 * 524 * @return array 525 */ 526 public function update(array $maintenances) { 527 if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) { 528 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 529 } 530 531 $maintenances = zbx_toArray($maintenances); 532 $maintenanceids = zbx_objectValues($maintenances, 'maintenanceid'); 533 534 if (!$maintenances) { 535 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 536 } 537 538 $db_fields = [ 539 'maintenanceid' => null 540 ]; 541 542 foreach ($maintenances as $maintenance) { 543 // Validate fields. 544 if (!check_db_fields($db_fields, $maintenance)) { 545 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for maintenance.')); 546 } 547 548 $this->validateTags($maintenance); 549 } 550 551 $db_maintenances = $this->get([ 552 'output' => API_OUTPUT_EXTEND, 553 'maintenanceids' => $maintenanceids, 554 'selectGroups' => ['groupid'], 555 'selectHosts' => ['hostid'], 556 'selectTimeperiods' => API_OUTPUT_EXTEND, 557 'editable' => true, 558 'preservekeys' => true 559 ]); 560 561 $changed_names = []; 562 $hostids = []; 563 $groupids = []; 564 565 foreach ($maintenances as &$maintenance) { 566 if (!array_key_exists($maintenance['maintenanceid'], $db_maintenances)) { 567 self::exception(ZBX_API_ERROR_PERMISSIONS, 568 _('No permissions to referred object or it does not exist!') 569 ); 570 } 571 572 $db_maintenance = $db_maintenances[$maintenance['maintenanceid']]; 573 574 // Check maintenances names and collect for unique checking. 575 if (array_key_exists('name', $maintenance) && $maintenance['name'] !== '' 576 && $db_maintenance['name'] !== $maintenance['name']) { 577 if (array_key_exists($maintenance['name'], $changed_names)) { 578 self::exception(ZBX_API_ERROR_PARAMETERS, 579 _s('Maintenance "%1$s" already exists.', $maintenance['name']) 580 ); 581 } 582 583 $changed_names[$maintenance['name']] = $maintenance['name']; 584 } 585 586 // Validate maintenance active since. 587 if (array_key_exists('active_since', $maintenance)) { 588 $active_since = $maintenance['active_since']; 589 590 if (!validateUnixTime($active_since)) { 591 self::exception(ZBX_API_ERROR_PARAMETERS, 592 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'active_since') 593 ); 594 } 595 596 $maintenance['active_since'] -= $maintenance['active_since'] % SEC_PER_MIN; 597 } 598 else { 599 $active_since = $db_maintenance['active_since']; 600 } 601 602 // Validate maintenance active till. 603 if (array_key_exists('active_till', $maintenance)) { 604 $active_till = $maintenance['active_till']; 605 606 if (!validateUnixTime($active_till)) { 607 self::exception(ZBX_API_ERROR_PARAMETERS, 608 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'active_till') 609 ); 610 } 611 612 $maintenance['active_till'] -= $maintenance['active_till'] % SEC_PER_MIN; 613 } 614 else { 615 $active_till = $db_maintenance['active_till']; 616 } 617 618 // Validate maintenance active interval. 619 if ($active_since > $active_till) { 620 self::exception(ZBX_API_ERROR_PARAMETERS, 621 _('Maintenance "Active since" value cannot be bigger than "Active till".') 622 ); 623 } 624 625 // Validate timeperiods. 626 if (array_key_exists('timeperiods', $maintenance)) { 627 if (!is_array($maintenance['timeperiods']) || !$maintenance['timeperiods']) { 628 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 629 } 630 631 $db_timeperiods = zbx_toHash($db_maintenance['timeperiods'], 'timeperiodid'); 632 633 foreach ($maintenance['timeperiods'] as &$timeperiod) { 634 if (!is_array($timeperiod)) { 635 self::exception(ZBX_API_ERROR_PARAMETERS, 636 _('At least one maintenance period must be created.') 637 ); 638 } 639 640 $timeperiod_type = array_key_exists('timeperiod_type', $timeperiod) 641 ? $timeperiod['timeperiod_type'] 642 : null; 643 644 if (array_key_exists('timeperiodid', $timeperiod)) { 645 $timeperiodid = $timeperiod['timeperiodid']; 646 647 // Validate incorrect "timeperiodid". 648 if (!array_key_exists($timeperiodid, $db_timeperiods)) { 649 self::exception(ZBX_API_ERROR_PERMISSIONS, 650 _('No permissions to referred object or it does not exist!') 651 ); 652 } 653 654 if ($timeperiod_type === null) { 655 $timeperiod_type = $db_timeperiods[$timeperiodid]['timeperiod_type']; 656 } 657 } 658 659 if (array_key_exists('every', $timeperiod) && $timeperiod['every'] <= 0) { 660 self::exception(ZBX_API_ERROR_PARAMETERS, 661 _s('Incorrect value "%1$s" for unsigned int field "%2$s".', $timeperiod['every'], 'every') 662 ); 663 } 664 665 // Without "timeperiod_type" it resolves to default TIMEPERIOD_TYPE_ONETIME. But will it be forever? 666 if ($timeperiod_type === null) { 667 $timeperiod_type = DB::getDefault('timeperiods', 'timeperiod_type'); 668 } 669 670 // Reset "start_date" to default value in case "timeperiod_type" is not one time only. 671 if ($timeperiod_type != TIMEPERIOD_TYPE_ONETIME) { 672 $timeperiod['start_date'] = DB::getDefault('timeperiods', 'start_date'); 673 } 674 else if (array_key_exists('start_date', $timeperiod) 675 && !validateUnixTime($timeperiod['start_date'])) { 676 self::exception(ZBX_API_ERROR_PARAMETERS, 677 _s('"%1$s" must be between 1970.01.01 and 2038.01.18.', 'start_date') 678 ); 679 } 680 else { 681 $timeperiod['start_date'] -= $timeperiod['start_date'] % SEC_PER_MIN; 682 } 683 } 684 unset($timeperiod); 685 } 686 687 // Collect hostids for permission checking. 688 if (array_key_exists('hostids', $maintenance) && is_array($maintenance['hostids'])) { 689 $hostids = array_merge($hostids, $maintenance['hostids']); 690 $has_hosts = (bool) $maintenance['hostids']; 691 } 692 else { 693 $has_hosts = (bool) $db_maintenances[$maintenance['maintenanceid']]['hosts']; 694 } 695 696 // Collect groupids for permission checking. 697 if (array_key_exists('groupids', $maintenance) && is_array($maintenance['groupids'])) { 698 $groupids = array_merge($groupids, $maintenance['groupids']); 699 $has_groups = (bool) $maintenance['groupids']; 700 } 701 else { 702 $has_groups = (bool) $db_maintenances[$maintenance['maintenanceid']]['groups']; 703 } 704 705 if (!$has_hosts && !$has_groups) { 706 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one host group or host must be selected.')); 707 } 708 709 // Check if maintenance without data collection has no tags. 710 $db_maintenance_type = $db_maintenances[$maintenance['maintenanceid']]['maintenance_type']; 711 $maintenance_type = array_key_exists('maintenance_type', $maintenance) 712 ? $maintenance['maintenance_type'] 713 : $db_maintenance_type; 714 if ($db_maintenance_type == MAINTENANCE_TYPE_NODATA && $maintenance_type == $db_maintenance_type 715 && array_key_exists('tags', $maintenance) && $maintenance['tags']) { 716 self::exception(ZBX_API_ERROR_PARAMETERS, 717 _s('Incorrect value for field "%1$s": %2$s.', 'tags', _('should be empty')) 718 ); 719 } 720 } 721 unset($maintenance); 722 723 // Check if maintenance already exists. 724 if ($changed_names) { 725 $db_maintenances_names = $this->get([ 726 'output' => ['name'], 727 'filter' => ['name' => $changed_names], 728 'nopermissions' => true, 729 'limit' => 1 730 ]); 731 732 if ($db_maintenances_names) { 733 $maintenance = reset($db_maintenances_names); 734 self::exception(ZBX_API_ERROR_PARAMETERS, 735 _s('Maintenance "%1$s" already exists.', $maintenance['name']) 736 ); 737 } 738 } 739 740 // Check hosts permission and availability. 741 if ($hostids) { 742 $db_hosts = API::Host()->get([ 743 'output' => [], 744 'hostids' => $hostids, 745 'editable' => true, 746 'preservekeys' => true 747 ]); 748 749 foreach ($hostids as $hostid) { 750 if (!array_key_exists($hostid, $db_hosts)) { 751 self::exception(ZBX_API_ERROR_PERMISSIONS, 752 _('No permissions to referred object or it does not exist!') 753 ); 754 } 755 } 756 } 757 758 // Check host groups permission and availability. 759 if ($groupids) { 760 $db_groups = API::HostGroup()->get([ 761 'output' => [], 762 'groupids' => $groupids, 763 'editable' => true, 764 'preservekeys' => true 765 ]); 766 767 foreach ($groupids as $groupid) { 768 if (!array_key_exists($groupid, $db_groups)) { 769 self::exception(ZBX_API_ERROR_PERMISSIONS, 770 _('No permissions to referred object or it does not exist!') 771 ); 772 } 773 } 774 } 775 776 $update_maintenances = []; 777 foreach ($maintenances as $mnum => $maintenance) { 778 $update_maintenances[$mnum] = [ 779 'values' => $maintenance, 780 'where' => ['maintenanceid' => $maintenance['maintenanceid']] 781 ]; 782 783 // Update time periods. 784 if (array_key_exists('timeperiods', $maintenance)) { 785 $this->replaceTimePeriods($db_maintenances[$maintenance['maintenanceid']], $maintenance); 786 } 787 } 788 DB::update('maintenances', $update_maintenances); 789 790 // Some of the hosts and groups bound to maintenance must be deleted, other inserted and others left alone. 791 $insert_hosts = []; 792 $insert_groups = []; 793 794 foreach ($maintenances as $maintenance) { 795 if (array_key_exists('hostids', $maintenance)) { 796 // Putting apart those host<->maintenance connections that should be inserted, deleted and not changed: 797 // $hosts_diff['first'] - new hosts, that should be inserted; 798 // $hosts_diff['second'] - hosts, that should be deleted; 799 // $hosts_diff['both'] - hosts, that should not be touched; 800 $hosts_diff = zbx_array_diff( 801 zbx_toObject($maintenance['hostids'], 'hostid'), 802 $db_maintenances[$maintenance['maintenanceid']]['hosts'], 803 'hostid' 804 ); 805 806 foreach ($hosts_diff['first'] as $host) { 807 $insert_hosts[] = [ 808 'hostid' => $host['hostid'], 809 'maintenanceid' => $maintenance['maintenanceid'] 810 ]; 811 } 812 foreach ($hosts_diff['second'] as $host) { 813 DB::delete('maintenances_hosts', [ 814 'hostid' => $host['hostid'], 815 'maintenanceid' => $maintenance['maintenanceid'] 816 ]); 817 } 818 } 819 820 if (array_key_exists('groupids', $maintenance)) { 821 // Now the same with the groups. 822 $groups_diff = zbx_array_diff( 823 zbx_toObject($maintenance['groupids'], 'groupid'), 824 $db_maintenances[$maintenance['maintenanceid']]['groups'], 825 'groupid' 826 ); 827 828 foreach ($groups_diff['first'] as $group) { 829 $insert_groups[] = [ 830 'groupid' => $group['groupid'], 831 'maintenanceid' => $maintenance['maintenanceid'] 832 ]; 833 } 834 foreach ($groups_diff['second'] as $group) { 835 DB::delete('maintenances_groups', [ 836 'groupid' => $group['groupid'], 837 'maintenanceid' => $maintenance['maintenanceid'] 838 ]); 839 } 840 } 841 } 842 843 if ($insert_hosts) { 844 DB::insert('maintenances_hosts', $insert_hosts); 845 } 846 847 if ($insert_groups) { 848 DB::insert('maintenances_groups', $insert_groups); 849 } 850 851 $this->updateTags($maintenances, $db_maintenances); 852 853 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MAINTENANCE, $maintenances, $db_maintenances); 854 855 return ['maintenanceids' => $maintenanceids]; 856 } 857 858 /** 859 * Compares input tags with tags stored in the database and performs tag deleting and inserting. 860 * 861 * @param array $maintenances 862 * @param int $maintenances[]['maintenanceid'] 863 * @param int $maintenances[]['maintenance_type'] 864 * @param array $maintenances[]['tags'] 865 * @param string $maintenances[]['tags'][]['tag'] 866 * @param int $maintenances[]['tags'][]['operator'] 867 * @param string $maintenances[]['tags'][]['value'] 868 * @param array $db_maintenances 869 * @param int $db_maintenances[<maintenanceid>] 870 * @param int $db_maintenances[<maintenanceid>]['maintenance_type'] 871 */ 872 private function updateTags(array $maintenances, array $db_maintenances) { 873 $db_tags = API::getApiService()->select('maintenance_tag', [ 874 'output' => ['maintenancetagid', 'maintenanceid', 'tag', 'operator', 'value'], 875 'filter' => ['maintenanceid' => array_keys($db_maintenances)], 876 'preservekeys' => true 877 ]); 878 $relation_map = $this->createRelationMap($db_tags, 'maintenanceid', 'maintenancetagid'); 879 $db_maintenances = $relation_map->mapMany($db_maintenances, $db_tags, 'tags'); 880 881 $ins_tags = []; 882 $del_maintenancetagids = []; 883 884 foreach ($maintenances as $mnum => $maintenance) { 885 $maintenanceid = $maintenance['maintenanceid']; 886 887 if (array_key_exists('maintenance_type', $maintenance) 888 && $maintenance['maintenance_type'] == MAINTENANCE_TYPE_NODATA 889 && $db_maintenances[$maintenanceid]['tags']) { 890 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag) { 891 $del_maintenancetagids[] = $db_tag['maintenancetagid']; 892 } 893 unset($maintenances[$mnum], $db_maintenances[$maintenanceid]); 894 continue; 895 } 896 897 if (!array_key_exists('tags', $maintenance)) { 898 unset($maintenances[$mnum], $db_maintenances[$maintenanceid]); 899 continue; 900 } 901 902 foreach ($maintenance['tags'] as $tag_num => $tag) { 903 $tag += [ 904 'operator' => MAINTENANCE_TAG_OPERATOR_LIKE, 905 'value' => '' 906 ]; 907 908 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag_num => $db_tag) { 909 if ($tag['tag'] === $db_tag['tag'] && $tag['operator'] == $db_tag['operator'] 910 && $tag['value'] === $db_tag['value']) { 911 unset($maintenances[$mnum]['tags'][$tag_num], 912 $db_maintenances[$maintenanceid]['tags'][$db_tag_num] 913 ); 914 } 915 } 916 } 917 } 918 919 foreach ($maintenances as $maintenance) { 920 $maintenanceid = $maintenance['maintenanceid']; 921 922 foreach ($maintenance['tags'] as $tag) { 923 $ins_tags[] = ['maintenanceid' => $maintenanceid] + $tag; 924 } 925 926 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag) { 927 $del_maintenancetagids[] = $db_tag['maintenancetagid']; 928 } 929 } 930 931 if ($del_maintenancetagids) { 932 DB::delete('maintenance_tag', ['maintenancetagid' => $del_maintenancetagids]); 933 } 934 935 if ($ins_tags) { 936 DB::insert('maintenance_tag', $ins_tags); 937 } 938 } 939 940 /** 941 * Delete Maintenances. 942 * 943 * @param array $maintenanceids 944 * 945 * @return array 946 */ 947 public function delete(array $maintenanceids) { 948 if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) { 949 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 950 } 951 952 $db_maintenances = $this->get([ 953 'output' => ['maintenanceid', 'name'], 954 'maintenanceids' => $maintenanceids, 955 'editable' => true, 956 'preservekeys' => true 957 ]); 958 959 if (array_diff_key(array_flip($maintenanceids), $db_maintenances)) { 960 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 961 } 962 963 $timeperiodids = []; 964 $dbTimeperiods = DBselect( 965 'SELECT DISTINCT tp.timeperiodid'. 966 ' FROM timeperiods tp,maintenances_windows mw'. 967 ' WHERE '.dbConditionInt('mw.maintenanceid', $maintenanceids). 968 ' AND tp.timeperiodid=mw.timeperiodid' 969 ); 970 while ($timeperiod = DBfetch($dbTimeperiods)) { 971 $timeperiodids[] = $timeperiod['timeperiodid']; 972 } 973 974 $midCond = ['maintenanceid' => $maintenanceids]; 975 976 // Lock maintenances table before maintenance delete to prevent server from adding host to maintenance. 977 DBselect( 978 'SELECT NULL'. 979 ' FROM maintenances'. 980 ' WHERE '.dbConditionId('maintenanceid', $maintenanceids). 981 ' FOR UPDATE' 982 ); 983 984 // Remove maintenanceid from hosts table. 985 DB::update('hosts', [ 986 'values' => ['maintenanceid' => 0], 987 'where' => ['maintenanceid' => $maintenanceids] 988 ]); 989 990 DB::delete('timeperiods', ['timeperiodid' => $timeperiodids]); 991 DB::delete('maintenances_windows', $midCond); 992 DB::delete('maintenances_hosts', $midCond); 993 DB::delete('maintenances_groups', $midCond); 994 DB::delete('maintenances', $midCond); 995 996 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MAINTENANCE, $db_maintenances); 997 998 return ['maintenanceids' => $maintenanceids]; 999 } 1000 1001 /** 1002 * Updates maintenance time periods. 1003 * 1004 * @param array $maintenance 1005 * @param array $oldMaintenance 1006 */ 1007 protected function replaceTimePeriods(array $oldMaintenance, array $maintenance) { 1008 // replace time periods 1009 $timePeriods = DB::replace('timeperiods', $oldMaintenance['timeperiods'], $maintenance['timeperiods']); 1010 1011 // link new time periods to maintenance 1012 $oldTimePeriods = zbx_toHash($oldMaintenance['timeperiods'], 'timeperiodid'); 1013 $newMaintenanceWindows = []; 1014 foreach ($timePeriods as $tp) { 1015 if (!isset($oldTimePeriods[$tp['timeperiodid']])) { 1016 $newMaintenanceWindows[] = [ 1017 'maintenanceid' => $maintenance['maintenanceid'], 1018 'timeperiodid' => $tp['timeperiodid'] 1019 ]; 1020 } 1021 } 1022 DB::insert('maintenances_windows', $newMaintenanceWindows); 1023 } 1024 1025 protected function addRelatedObjects(array $options, array $result) { 1026 $result = parent::addRelatedObjects($options, $result); 1027 1028 // selectGroups 1029 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 1030 $groups = []; 1031 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'groupid', 'maintenances_groups'); 1032 $related_ids = $relationMap->getRelatedIds(); 1033 1034 if ($related_ids) { 1035 $groups = API::HostGroup()->get([ 1036 'output' => $options['selectGroups'], 1037 'hostgroupids' => $related_ids, 1038 'preservekeys' => true 1039 ]); 1040 } 1041 1042 $result = $relationMap->mapMany($result, $groups, 'groups'); 1043 } 1044 1045 // selectHosts 1046 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 1047 $hosts = []; 1048 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'hostid', 'maintenances_hosts'); 1049 $related_ids = $relationMap->getRelatedIds(); 1050 1051 if ($related_ids) { 1052 $hosts = API::Host()->get([ 1053 'output' => $options['selectHosts'], 1054 'hostids' => $related_ids, 1055 'preservekeys' => true 1056 ]); 1057 } 1058 1059 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 1060 } 1061 1062 // Adding problem tags. 1063 if ($options['selectTags'] !== null && $options['selectTags'] != API_OUTPUT_COUNT) { 1064 $tags = API::getApiService()->select('maintenance_tag', [ 1065 'output' => $this->outputExtend($options['selectTags'], ['maintenanceid']), 1066 'filter' => ['maintenanceids' => array_keys($result)], 1067 'preservekeys' => true 1068 ]); 1069 $relation_map = $this->createRelationMap($tags, 'maintenanceid', 'maintenancetagid'); 1070 $tags = $this->unsetExtraFields($tags, ['maintenancetagid', 'maintenanceid'], []); 1071 $result = $relation_map->mapMany($result, $tags, 'tags'); 1072 } 1073 1074 // selectTimeperiods 1075 if ($options['selectTimeperiods'] !== null && $options['selectTimeperiods'] != API_OUTPUT_COUNT) { 1076 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'timeperiodid', 'maintenances_windows'); 1077 $timeperiods = API::getApiService()->select('timeperiods', [ 1078 'output' => $options['selectTimeperiods'], 1079 'filter' => ['timeperiodid' => $relationMap->getRelatedIds()], 1080 'preservekeys' => true 1081 ]); 1082 $result = $relationMap->mapMany($result, $timeperiods, 'timeperiods'); 1083 } 1084 1085 return $result; 1086 } 1087} 1088