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, _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active since'))); 352 } 353 354 // validate maintenance active till 355 if (!validateUnixTime($maintenance['active_till'])) { 356 self::exception(ZBX_API_ERROR_PARAMETERS, _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active till'))); 357 } 358 359 // validate maintenance active interval 360 if ($maintenance['active_since'] > $maintenance['active_till']) { 361 self::exception(ZBX_API_ERROR_PARAMETERS, _('Maintenance "Active since" value cannot be bigger than "Active till".')); 362 } 363 364 // validate timeperiods 365 if (!array_key_exists('timeperiods', $maintenance) || !is_array($maintenance['timeperiods']) 366 || !$maintenance['timeperiods']) { 367 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 368 } 369 370 $maintenance['active_since'] -= $maintenance['active_since'] % SEC_PER_MIN; 371 $maintenance['active_till'] -= $maintenance['active_till'] % SEC_PER_MIN; 372 373 foreach ($maintenance['timeperiods'] as $timeperiod) { 374 if (!is_array($timeperiod)) { 375 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 376 } 377 378 $dbFields = [ 379 'timeperiod_type' => TIMEPERIOD_TYPE_ONETIME, 380 'period' => SEC_PER_HOUR, 381 'start_date' => $now 382 ]; 383 check_db_fields($dbFields, $timeperiod); 384 385 if (array_key_exists('every', $timeperiod) && $timeperiod['every'] <= 0) { 386 self::exception(ZBX_API_ERROR_PARAMETERS, 387 _s('Incorrect value "%1$s" for unsigned int field "%2$s".', 388 $timeperiod['every'], 'every') 389 ); 390 } 391 392 if ($timeperiod['timeperiod_type'] != TIMEPERIOD_TYPE_ONETIME) { 393 $timeperiod['start_date'] = DB::getDefault('timeperiods', 'start_date'); 394 } 395 else if (!validateUnixTime($timeperiod['start_date'])) { 396 self::exception(ZBX_API_ERROR_PARAMETERS, _s('"%s" must be between 1970.01.01 and 2038.01.18.', 397 _('Date')) 398 ); 399 } 400 else { 401 $timeperiod['start_date'] -= $timeperiod['start_date'] % SEC_PER_MIN; 402 } 403 404 $tid++; 405 $insertTimeperiods[$tid] = $timeperiod; 406 $timeperiods[$tid] = $mnum; 407 } 408 409 $insert[$mnum] = $maintenance; 410 411 $this->validateTags($maintenance); 412 } 413 414 $maintenanceids = DB::insert('maintenances', $insert); 415 $timeperiodids = DB::insert('timeperiods', $insertTimeperiods); 416 417 $insertWindows = []; 418 foreach ($timeperiods as $tid => $mnum) { 419 $insertWindows[] = [ 420 'timeperiodid' => $timeperiodids[$tid], 421 'maintenanceid' => $maintenanceids[$mnum] 422 ]; 423 } 424 DB::insertBatch('maintenances_windows', $insertWindows); 425 426 $insertHosts = []; 427 $insertGroups = []; 428 $ins_tags = []; 429 foreach ($maintenances as $mnum => &$maintenance) { 430 $maintenance['maintenanceid'] = $maintenanceids[$mnum]; 431 432 if (array_key_exists('hostids', $maintenance)) { 433 foreach ($maintenance['hostids'] as $hostid) { 434 $insertHosts[] = [ 435 'hostid' => $hostid, 436 'maintenanceid' => $maintenance['maintenanceid'] 437 ]; 438 } 439 } 440 441 if (array_key_exists('groupids', $maintenance)) { 442 foreach ($maintenance['groupids'] as $groupid) { 443 $insertGroups[] = [ 444 'groupid' => $groupid, 445 'maintenanceid' => $maintenance['maintenanceid'] 446 ]; 447 } 448 } 449 450 if (array_key_exists('tags', $maintenance)) { 451 foreach ($maintenance['tags'] as $tag) { 452 $ins_tags[] = [ 453 'maintenanceid' => $maintenance['maintenanceid'] 454 ] + $tag; 455 } 456 } 457 } 458 unset($maintenance); 459 460 DB::insertBatch('maintenances_hosts', $insertHosts); 461 DB::insertBatch('maintenances_groups', $insertGroups); 462 463 if ($ins_tags) { 464 DB::insert('maintenance_tag', $ins_tags); 465 } 466 467 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_MAINTENANCE, $maintenances); 468 469 return ['maintenanceids' => $maintenanceids]; 470 } 471 472 /** 473 * Validates maintenance problem tags. 474 * 475 * @param array $maintenance 476 * @param int $maintenance['maintenance_type'] 477 * @param int $maintenance['tags_evaltype'] 478 * @param array $maintenance['tags'] 479 * @param string $maintenance['tags'][]['tag'] 480 * @param int $maintenance['tags'][]['operator'] 481 * @param string $maintenance['tags'][]['value'] 482 * 483 * @throws APIException if the input is invalid. 484 */ 485 private function validateTags(array $maintenance) { 486 if (array_key_exists('maintenance_type', $maintenance) 487 && $maintenance['maintenance_type'] == MAINTENANCE_TYPE_NODATA 488 && array_key_exists('tags', $maintenance) && $maintenance['tags']) { 489 self::exception(ZBX_API_ERROR_PARAMETERS, 490 _s('Incorrect value for field "%1$s": %2$s.', 'tags', _('should be empty')) 491 ); 492 } 493 494 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 495 'maintenance_type' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TYPE_NORMAL, MAINTENANCE_TYPE_NODATA])], 496 'tags_evaltype' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TAG_EVAL_TYPE_AND_OR, MAINTENANCE_TAG_EVAL_TYPE_OR])], 497 'tags' => ['type' => API_OBJECTS, 'uniq' => [['tag', 'operator', 'value']], 'fields' => [ 498 'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('maintenance_tag', 'tag')], 499 'operator' => ['type' => API_INT32, 'in' => implode(',', [MAINTENANCE_TAG_OPERATOR_EQUAL, MAINTENANCE_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('maintenance_tag', 'operator')], 500 'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('maintenance_tag', 'value'), 'default' => DB::getDefault('maintenance_tag', 'value')] 501 ]] 502 ]]; 503 504 // Keep values only for fields with defined validation rules. 505 $maintenance = array_intersect_key($maintenance, $api_input_rules['fields']); 506 507 if (!CApiInputValidator::validate($api_input_rules, $maintenance, '/', $error)) { 508 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 509 } 510 } 511 512 /** 513 * Update maintenances. 514 * 515 * @param array $maintenances 516 * 517 * @throws APIException if no permissions to object, it does no exists or validation errors 518 * 519 * @return array 520 */ 521 public function update(array $maintenances) { 522 if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) { 523 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 524 } 525 526 $maintenances = zbx_toArray($maintenances); 527 $maintenanceids = zbx_objectValues($maintenances, 'maintenanceid'); 528 529 if (!$maintenances) { 530 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 531 } 532 533 $db_fields = [ 534 'maintenanceid' => null 535 ]; 536 537 foreach ($maintenances as $maintenance) { 538 // Validate fields. 539 if (!check_db_fields($db_fields, $maintenance)) { 540 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parameters for maintenance.')); 541 } 542 543 $this->validateTags($maintenance); 544 } 545 546 $db_maintenances = $this->get([ 547 'output' => API_OUTPUT_EXTEND, 548 'maintenanceids' => $maintenanceids, 549 'selectGroups' => ['groupid'], 550 'selectHosts' => ['hostid'], 551 'selectTimeperiods' => API_OUTPUT_EXTEND, 552 'editable' => true, 553 'preservekeys' => true 554 ]); 555 556 $changed_names = []; 557 $hostids = []; 558 $groupids = []; 559 560 foreach ($maintenances as &$maintenance) { 561 if (!array_key_exists($maintenance['maintenanceid'], $db_maintenances)) { 562 self::exception(ZBX_API_ERROR_PERMISSIONS, 563 _('No permissions to referred object or it does not exist!') 564 ); 565 } 566 567 $db_maintenance = $db_maintenances[$maintenance['maintenanceid']]; 568 569 // Check maintenances names and collect for unique checking. 570 if (array_key_exists('name', $maintenance) && $maintenance['name'] !== '' 571 && $db_maintenance['name'] !== $maintenance['name']) { 572 if (array_key_exists($maintenance['name'], $changed_names)) { 573 self::exception(ZBX_API_ERROR_PARAMETERS, 574 _s('Maintenance "%1$s" already exists.', $maintenance['name']) 575 ); 576 } 577 578 $changed_names[$maintenance['name']] = $maintenance['name']; 579 } 580 581 // Validate maintenance active since. 582 if (array_key_exists('active_since', $maintenance)) { 583 $active_since = $maintenance['active_since']; 584 585 if (!validateUnixTime($active_since)) { 586 self::exception(ZBX_API_ERROR_PARAMETERS, 587 _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active since')) 588 ); 589 } 590 591 $maintenance['active_since'] -= $maintenance['active_since'] % SEC_PER_MIN; 592 } 593 else { 594 $active_since = $db_maintenance['active_since']; 595 } 596 597 // Validate maintenance active till. 598 if (array_key_exists('active_till', $maintenance)) { 599 $active_till = $maintenance['active_till']; 600 601 if (!validateUnixTime($active_till)) { 602 self::exception(ZBX_API_ERROR_PARAMETERS, 603 _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Active till')) 604 ); 605 } 606 607 $maintenance['active_till'] -= $maintenance['active_till'] % SEC_PER_MIN; 608 } 609 else { 610 $active_till = $db_maintenance['active_till']; 611 } 612 613 // Validate maintenance active interval. 614 if ($active_since > $active_till) { 615 self::exception(ZBX_API_ERROR_PARAMETERS, 616 _('Maintenance "Active since" value cannot be bigger than "Active till".') 617 ); 618 } 619 620 // Validate timeperiods. 621 if (array_key_exists('timeperiods', $maintenance)) { 622 if (!is_array($maintenance['timeperiods']) || !$maintenance['timeperiods']) { 623 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one maintenance period must be created.')); 624 } 625 626 $db_timeperiods = zbx_toHash($db_maintenance['timeperiods'], 'timeperiodid'); 627 628 foreach ($maintenance['timeperiods'] as &$timeperiod) { 629 if (!is_array($timeperiod)) { 630 self::exception(ZBX_API_ERROR_PARAMETERS, 631 _('At least one maintenance period must be created.') 632 ); 633 } 634 635 $timeperiod_type = array_key_exists('timeperiod_type', $timeperiod) 636 ? $timeperiod['timeperiod_type'] 637 : null; 638 639 if (array_key_exists('timeperiodid', $timeperiod)) { 640 $timeperiodid = $timeperiod['timeperiodid']; 641 642 // Validate incorrect "timeperiodid". 643 if (!array_key_exists($timeperiodid, $db_timeperiods)) { 644 self::exception(ZBX_API_ERROR_PERMISSIONS, 645 _('No permissions to referred object or it does not exist!') 646 ); 647 } 648 649 if ($timeperiod_type === null) { 650 $timeperiod_type = $db_timeperiods[$timeperiodid]['timeperiod_type']; 651 } 652 } 653 654 if (array_key_exists('every', $timeperiod) && $timeperiod['every'] <= 0) { 655 self::exception(ZBX_API_ERROR_PARAMETERS, 656 _s('Incorrect value "%1$s" for unsigned int field "%2$s".', 657 $timeperiod['every'], 'every') 658 ); 659 } 660 661 // Without "timeperiod_type" it resolves to default TIMEPERIOD_TYPE_ONETIME. But will it be forever? 662 if ($timeperiod_type === null) { 663 $timeperiod_type = DB::getDefault('timeperiods', 'timeperiod_type'); 664 } 665 666 // Reset "start_date" to default value in case "timeperiod_type" is not one time only. 667 if ($timeperiod_type != TIMEPERIOD_TYPE_ONETIME) { 668 $timeperiod['start_date'] = DB::getDefault('timeperiods', 'start_date'); 669 } 670 else if (array_key_exists('start_date', $timeperiod) 671 && !validateUnixTime($timeperiod['start_date'])) { 672 self::exception(ZBX_API_ERROR_PARAMETERS, 673 _s('"%s" must be between 1970.01.01 and 2038.01.18.', _('Date')) 674 ); 675 } 676 else { 677 $timeperiod['start_date'] -= $timeperiod['start_date'] % SEC_PER_MIN; 678 } 679 } 680 unset($timeperiod); 681 } 682 683 // Collect hostids for permission checking. 684 if (array_key_exists('hostids', $maintenance) && is_array($maintenance['hostids'])) { 685 $hostids = array_merge($hostids, $maintenance['hostids']); 686 $has_hosts = (bool) $maintenance['hostids']; 687 } 688 else { 689 $has_hosts = (bool) $db_maintenances[$maintenance['maintenanceid']]['hosts']; 690 } 691 692 // Collect groupids for permission checking. 693 if (array_key_exists('groupids', $maintenance) && is_array($maintenance['groupids'])) { 694 $groupids = array_merge($groupids, $maintenance['groupids']); 695 $has_groups = (bool) $maintenance['groupids']; 696 } 697 else { 698 $has_groups = (bool) $db_maintenances[$maintenance['maintenanceid']]['groups']; 699 } 700 701 if (!$has_hosts && !$has_groups) { 702 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one host group or host must be selected.')); 703 } 704 705 // Check if maintenance without data collection has no tags. 706 $db_maintenance_type = $db_maintenances[$maintenance['maintenanceid']]['maintenance_type']; 707 $maintenance_type = array_key_exists('maintenance_type', $maintenance) 708 ? $maintenance['maintenance_type'] 709 : $db_maintenance_type; 710 if ($db_maintenance_type == MAINTENANCE_TYPE_NODATA && $maintenance_type == $db_maintenance_type 711 && array_key_exists('tags', $maintenance) && $maintenance['tags']) { 712 self::exception(ZBX_API_ERROR_PARAMETERS, 713 _s('Incorrect value for field "%1$s": %2$s.', 'tags', _('should be empty')) 714 ); 715 } 716 } 717 unset($maintenance); 718 719 // Check if maintenance already exists. 720 if ($changed_names) { 721 $db_maintenances_names = $this->get([ 722 'output' => ['name'], 723 'filter' => ['name' => $changed_names], 724 'nopermissions' => true, 725 'limit' => 1 726 ]); 727 728 if ($db_maintenances_names) { 729 $maintenance = reset($db_maintenances_names); 730 self::exception(ZBX_API_ERROR_PARAMETERS, 731 _s('Maintenance "%1$s" already exists.', $maintenance['name']) 732 ); 733 } 734 } 735 736 // Check hosts permission and availability. 737 if ($hostids) { 738 $db_hosts = API::Host()->get([ 739 'output' => [], 740 'hostids' => $hostids, 741 'editable' => true, 742 'preservekeys' => true 743 ]); 744 745 foreach ($hostids as $hostid) { 746 if (!array_key_exists($hostid, $db_hosts)) { 747 self::exception(ZBX_API_ERROR_PERMISSIONS, 748 _('No permissions to referred object or it does not exist!') 749 ); 750 } 751 } 752 } 753 754 // Check host groups permission and availability. 755 if ($groupids) { 756 $db_groups = API::HostGroup()->get([ 757 'output' => [], 758 'groupids' => $groupids, 759 'editable' => true, 760 'preservekeys' => true 761 ]); 762 763 foreach ($groupids as $groupid) { 764 if (!array_key_exists($groupid, $db_groups)) { 765 self::exception(ZBX_API_ERROR_PERMISSIONS, 766 _('No permissions to referred object or it does not exist!') 767 ); 768 } 769 } 770 } 771 772 $update_maintenances = []; 773 foreach ($maintenances as $mnum => $maintenance) { 774 $update_maintenances[$mnum] = [ 775 'values' => $maintenance, 776 'where' => ['maintenanceid' => $maintenance['maintenanceid']] 777 ]; 778 779 // Update time periods. 780 if (array_key_exists('timeperiods', $maintenance)) { 781 $this->replaceTimePeriods($db_maintenances[$maintenance['maintenanceid']], $maintenance); 782 } 783 } 784 DB::update('maintenances', $update_maintenances); 785 786 // Some of the hosts and groups bound to maintenance must be deleted, other inserted and others left alone. 787 $insert_hosts = []; 788 $insert_groups = []; 789 790 foreach ($maintenances as $maintenance) { 791 if (array_key_exists('hostids', $maintenance)) { 792 // Putting apart those host<->maintenance connections that should be inserted, deleted and not changed: 793 // $hosts_diff['first'] - new hosts, that should be inserted; 794 // $hosts_diff['second'] - hosts, that should be deleted; 795 // $hosts_diff['both'] - hosts, that should not be touched; 796 $hosts_diff = zbx_array_diff( 797 zbx_toObject($maintenance['hostids'], 'hostid'), 798 $db_maintenances[$maintenance['maintenanceid']]['hosts'], 799 'hostid' 800 ); 801 802 foreach ($hosts_diff['first'] as $host) { 803 $insert_hosts[] = [ 804 'hostid' => $host['hostid'], 805 'maintenanceid' => $maintenance['maintenanceid'] 806 ]; 807 } 808 foreach ($hosts_diff['second'] as $host) { 809 DB::delete('maintenances_hosts', [ 810 'hostid' => $host['hostid'], 811 'maintenanceid' => $maintenance['maintenanceid'] 812 ]); 813 } 814 } 815 816 if (array_key_exists('groupids', $maintenance)) { 817 // Now the same with the groups. 818 $groups_diff = zbx_array_diff( 819 zbx_toObject($maintenance['groupids'], 'groupid'), 820 $db_maintenances[$maintenance['maintenanceid']]['groups'], 821 'groupid' 822 ); 823 824 foreach ($groups_diff['first'] as $group) { 825 $insert_groups[] = [ 826 'groupid' => $group['groupid'], 827 'maintenanceid' => $maintenance['maintenanceid'] 828 ]; 829 } 830 foreach ($groups_diff['second'] as $group) { 831 DB::delete('maintenances_groups', [ 832 'groupid' => $group['groupid'], 833 'maintenanceid' => $maintenance['maintenanceid'] 834 ]); 835 } 836 } 837 } 838 839 if ($insert_hosts) { 840 DB::insert('maintenances_hosts', $insert_hosts); 841 } 842 843 if ($insert_groups) { 844 DB::insert('maintenances_groups', $insert_groups); 845 } 846 847 $this->updateTags($maintenances, $db_maintenances); 848 849 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_MAINTENANCE, $maintenances, $db_maintenances); 850 851 return ['maintenanceids' => $maintenanceids]; 852 } 853 854 /** 855 * Compares input tags with tags stored in the database and performs tag deleting and inserting. 856 * 857 * @param array $maintenances 858 * @param int $maintenances[]['maintenanceid'] 859 * @param int $maintenances[]['maintenance_type'] 860 * @param array $maintenances[]['tags'] 861 * @param string $maintenances[]['tags'][]['tag'] 862 * @param int $maintenances[]['tags'][]['operator'] 863 * @param string $maintenances[]['tags'][]['value'] 864 * @param array $db_maintenances 865 * @param int $db_maintenances[<maintenanceid>] 866 * @param int $db_maintenances[<maintenanceid>]['maintenance_type'] 867 */ 868 private function updateTags(array $maintenances, array $db_maintenances) { 869 $db_tags = API::getApiService()->select('maintenance_tag', [ 870 'output' => ['maintenancetagid', 'maintenanceid', 'tag', 'operator', 'value'], 871 'filter' => ['maintenanceid' => array_keys($db_maintenances)], 872 'preservekeys' => true 873 ]); 874 $relation_map = $this->createRelationMap($db_tags, 'maintenanceid', 'maintenancetagid'); 875 $db_maintenances = $relation_map->mapMany($db_maintenances, $db_tags, 'tags'); 876 877 $ins_tags = []; 878 $del_maintenancetagids = []; 879 880 foreach ($maintenances as $mnum => $maintenance) { 881 $maintenanceid = $maintenance['maintenanceid']; 882 883 if (array_key_exists('maintenance_type', $maintenance) 884 && $maintenance['maintenance_type'] == MAINTENANCE_TYPE_NODATA 885 && $db_maintenances[$maintenanceid]['tags']) { 886 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag) { 887 $del_maintenancetagids[] = $db_tag['maintenancetagid']; 888 } 889 unset($maintenances[$mnum], $db_maintenances[$maintenanceid]); 890 continue; 891 } 892 893 if (!array_key_exists('tags', $maintenance)) { 894 unset($maintenances[$mnum], $db_maintenances[$maintenanceid]); 895 continue; 896 } 897 898 foreach ($maintenance['tags'] as $tag_num => $tag) { 899 $tag += [ 900 'operator' => MAINTENANCE_TAG_OPERATOR_LIKE, 901 'value' => '' 902 ]; 903 904 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag_num => $db_tag) { 905 if ($tag['tag'] === $db_tag['tag'] && $tag['operator'] == $db_tag['operator'] 906 && $tag['value'] === $db_tag['value']) { 907 unset($maintenances[$mnum]['tags'][$tag_num], 908 $db_maintenances[$maintenanceid]['tags'][$db_tag_num] 909 ); 910 } 911 } 912 } 913 } 914 915 foreach ($maintenances as $maintenance) { 916 $maintenanceid = $maintenance['maintenanceid']; 917 918 foreach ($maintenance['tags'] as $tag) { 919 $ins_tags[] = ['maintenanceid' => $maintenanceid] + $tag; 920 } 921 922 foreach ($db_maintenances[$maintenanceid]['tags'] as $db_tag) { 923 $del_maintenancetagids[] = $db_tag['maintenancetagid']; 924 } 925 } 926 927 if ($del_maintenancetagids) { 928 DB::delete('maintenance_tag', ['maintenancetagid' => $del_maintenancetagids]); 929 } 930 931 if ($ins_tags) { 932 DB::insert('maintenance_tag', $ins_tags); 933 } 934 } 935 936 /** 937 * Delete Maintenances. 938 * 939 * @param array $maintenanceids 940 * 941 * @return array 942 */ 943 public function delete(array $maintenanceids) { 944 if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) { 945 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 946 } 947 948 $db_maintenances = $this->get([ 949 'output' => ['maintenanceid', 'name'], 950 'maintenanceids' => $maintenanceids, 951 'editable' => true, 952 'preservekeys' => true 953 ]); 954 955 if (array_diff_key(array_flip($maintenanceids), $db_maintenances)) { 956 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 957 } 958 959 $timeperiodids = []; 960 $dbTimeperiods = DBselect( 961 'SELECT DISTINCT tp.timeperiodid'. 962 ' FROM timeperiods tp,maintenances_windows mw'. 963 ' WHERE '.dbConditionInt('mw.maintenanceid', $maintenanceids). 964 ' AND tp.timeperiodid=mw.timeperiodid' 965 ); 966 while ($timeperiod = DBfetch($dbTimeperiods)) { 967 $timeperiodids[] = $timeperiod['timeperiodid']; 968 } 969 970 $midCond = ['maintenanceid' => $maintenanceids]; 971 972 // Lock maintenances table before maintenance delete to prevent server from adding host to maintenance. 973 DBselect( 974 'SELECT NULL'. 975 ' FROM maintenances'. 976 ' WHERE '.dbConditionId('maintenanceid', $maintenanceids). 977 ' FOR UPDATE' 978 ); 979 980 // Remove maintenanceid from hosts table. 981 DB::update('hosts', [ 982 'values' => ['maintenanceid' => 0], 983 'where' => ['maintenanceid' => $maintenanceids] 984 ]); 985 986 DB::delete('timeperiods', ['timeperiodid' => $timeperiodids]); 987 DB::delete('maintenances_windows', $midCond); 988 DB::delete('maintenances_hosts', $midCond); 989 DB::delete('maintenances_groups', $midCond); 990 DB::delete('maintenances', $midCond); 991 992 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_MAINTENANCE, $db_maintenances); 993 994 return ['maintenanceids' => $maintenanceids]; 995 } 996 997 /** 998 * Updates maintenance time periods. 999 * 1000 * @param array $maintenance 1001 * @param array $oldMaintenance 1002 */ 1003 protected function replaceTimePeriods(array $oldMaintenance, array $maintenance) { 1004 // replace time periods 1005 $timePeriods = DB::replace('timeperiods', $oldMaintenance['timeperiods'], $maintenance['timeperiods']); 1006 1007 // link new time periods to maintenance 1008 $oldTimePeriods = zbx_toHash($oldMaintenance['timeperiods'], 'timeperiodid'); 1009 $newMaintenanceWindows = []; 1010 foreach ($timePeriods as $tp) { 1011 if (!isset($oldTimePeriods[$tp['timeperiodid']])) { 1012 $newMaintenanceWindows[] = [ 1013 'maintenanceid' => $maintenance['maintenanceid'], 1014 'timeperiodid' => $tp['timeperiodid'] 1015 ]; 1016 } 1017 } 1018 DB::insert('maintenances_windows', $newMaintenanceWindows); 1019 } 1020 1021 protected function addRelatedObjects(array $options, array $result) { 1022 $result = parent::addRelatedObjects($options, $result); 1023 1024 // selectGroups 1025 if ($options['selectGroups'] !== null && $options['selectGroups'] != API_OUTPUT_COUNT) { 1026 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'groupid', 'maintenances_groups'); 1027 $groups = API::HostGroup()->get([ 1028 'output' => $options['selectGroups'], 1029 'hostgroupids' => $relationMap->getRelatedIds(), 1030 'preservekeys' => true 1031 ]); 1032 $result = $relationMap->mapMany($result, $groups, 'groups'); 1033 } 1034 1035 // selectHosts 1036 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 1037 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'hostid', 'maintenances_hosts'); 1038 $groups = API::Host()->get([ 1039 'output' => $options['selectHosts'], 1040 'hostids' => $relationMap->getRelatedIds(), 1041 'preservekeys' => true 1042 ]); 1043 $result = $relationMap->mapMany($result, $groups, 'hosts'); 1044 } 1045 1046 // Adding problem tags. 1047 if ($options['selectTags'] !== null && $options['selectTags'] != API_OUTPUT_COUNT) { 1048 $tags = API::getApiService()->select('maintenance_tag', [ 1049 'output' => $this->outputExtend($options['selectTags'], ['maintenanceid']), 1050 'filter' => ['maintenanceids' => array_keys($result)], 1051 'preservekeys' => true 1052 ]); 1053 $relation_map = $this->createRelationMap($tags, 'maintenanceid', 'maintenancetagid'); 1054 $tags = $this->unsetExtraFields($tags, ['maintenancetagid', 'maintenanceid'], []); 1055 $result = $relation_map->mapMany($result, $tags, 'tags'); 1056 } 1057 1058 // selectTimeperiods 1059 if ($options['selectTimeperiods'] !== null && $options['selectTimeperiods'] != API_OUTPUT_COUNT) { 1060 $relationMap = $this->createRelationMap($result, 'maintenanceid', 'timeperiodid', 'maintenances_windows'); 1061 $timeperiods = API::getApiService()->select('timeperiods', [ 1062 'output' => $options['selectTimeperiods'], 1063 'filter' => ['timeperiodid' => $relationMap->getRelatedIds()], 1064 'preservekeys' => true 1065 ]); 1066 $result = $relationMap->mapMany($result, $timeperiods, 'timeperiods'); 1067 } 1068 1069 return $result; 1070 } 1071} 1072