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 22/** 23 * Class containing methods for operations with scheduled reports. 24 */ 25class CReport extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => [ 29 'min_user_type' => USER_TYPE_ZABBIX_ADMIN 30 ], 31 'create' => [ 32 'min_user_type' => USER_TYPE_ZABBIX_ADMIN, 33 'action' => CRoleHelper::ACTIONS_MANAGE_SCHEDULED_REPORTS 34 ], 35 'update' => [ 36 'min_user_type' => USER_TYPE_ZABBIX_ADMIN, 37 'action' => CRoleHelper::ACTIONS_MANAGE_SCHEDULED_REPORTS 38 ], 39 'delete' => [ 40 'min_user_type' => USER_TYPE_ZABBIX_ADMIN, 41 'action' => CRoleHelper::ACTIONS_MANAGE_SCHEDULED_REPORTS 42 ] 43 ]; 44 45 protected $tableName = 'report'; 46 protected $tableAlias = 'r'; 47 protected $sortColumns = ['reportid', 'name', 'status']; 48 49 protected $output_fields = ['reportid', 'userid', 'name', 'description', 'status', 'dashboardid', 'period', 'cycle', 50 'weekdays', 'start_time', 'active_since', 'active_till', 'state', 'lastsent', 'info', 'subject', 'message' 51 ]; 52 protected $user_output_fields = ['userid', 'access_userid', 'exclude']; 53 protected $usrgrp_output_fields = ['usrgrpid', 'access_userid']; 54 55 /** 56 * @param array $options 57 * 58 * @throws APIException if the input is invalid. 59 * 60 * @return array|string 61 */ 62 public function get(array $options = []) { 63 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 64 // filter 65 'reportids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 66 'expired' => ['type' => API_BOOLEAN, 'flags' => API_ALLOW_NULL, 'default' => null], 67 'filter' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 68 'reportid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 69 'userid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 70 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 71 'dashboardid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 72 'status' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => ZBX_REPORT_STATUS_DISABLED.','.ZBX_REPORT_STATUS_ENABLED], 73 'state' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [ZBX_REPORT_STATE_UNKNOWN, ZBX_REPORT_STATE_SENT, ZBX_REPORT_STATE_ERROR, ZBX_REPORT_STATE_SUCCESS_INFO])] 74 ]], 75 'search' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 76 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 77 ]], 78 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 79 'startSearch' => ['type' => API_FLAG, 'default' => false], 80 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 81 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], 82 // output 83 'output' => ['type' => API_OUTPUT, 'in' => implode(',', $this->output_fields), 'default' => API_OUTPUT_EXTEND], 84 'countOutput' => ['type' => API_FLAG, 'default' => false], 85 'selectUsers' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $this->user_output_fields), 'default' => null], 86 'selectUserGroups' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $this->usrgrp_output_fields), 'default' => null], 87 // sort and limit 88 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 89 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 90 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], 91 // flags 92 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false] 93 ]]; 94 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 95 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 96 } 97 98 $sql_parts = $this->createSelectQueryParts($this->tableName(), $this->tableAlias(), $options); 99 100 // expired 101 if ($options['expired'] !== null) { 102 $sql_parts['where'][] = $options['expired'] 103 ? '(r.active_till>0 AND r.active_till<'.time().')' 104 : '(r.active_till=0 OR r.active_till>='.time().')'; 105 } 106 107 $result = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']); 108 109 $db_reports = []; 110 while ($row = DBfetch($result)) { 111 if ($options['countOutput']) { 112 return $row['rowscount']; 113 } 114 115 $db_reports[$row['reportid']] = $row; 116 } 117 118 if ($db_reports) { 119 $db_reports = $this->addRelatedObjects($options, $db_reports); 120 $db_reports = $this->unsetExtraFields($db_reports, ['reportid'], $options['output']); 121 122 if (!$options['preservekeys']) { 123 $db_reports = array_values($db_reports); 124 } 125 } 126 127 return $db_reports; 128 } 129 130 /** 131 * @param array $reports 132 * 133 * @return array 134 */ 135 public function create(array $reports): array { 136 $this->validateCreate($reports); 137 138 $ins_reports = []; 139 140 foreach ($reports as $report) { 141 unset($report['subject'], $report['message'], $report['users'], $report['user_groups']); 142 $ins_reports[] = $report; 143 } 144 145 $reportids = DB::insert('report', $ins_reports); 146 147 foreach ($reports as $index => &$report) { 148 $report['reportid'] = $reportids[$index]; 149 } 150 unset($report); 151 152 $this->updateParams($reports, __FUNCTION__); 153 $this->updateUsers($reports, __FUNCTION__); 154 $this->updateUserGroups($reports, __FUNCTION__); 155 156 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_SCHEDULED_REPORT, $reports); 157 158 return ['reportids' => $reportids]; 159 } 160 161 /** 162 * @param array $reports 163 * 164 * @throws APIException if no permissions or the input is invalid. 165 */ 166 protected function validateCreate(array &$reports): void { 167 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ 168 'userid' => ['type' => API_ID, 'default' => self::$userData['userid']], 169 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('report', 'name')], 170 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('report', 'description')], 171 'status' => ['type' => API_INT32, 'in' => ZBX_REPORT_STATUS_DISABLED.','.ZBX_REPORT_STATUS_ENABLED], 172 'dashboardid' => ['type' => API_ID, 'flags' => API_REQUIRED], 173 'period' => ['type' => API_INT32, 'in' => implode(',', [ZBX_REPORT_PERIOD_DAY, ZBX_REPORT_PERIOD_WEEK, ZBX_REPORT_PERIOD_MONTH, ZBX_REPORT_PERIOD_YEAR])], 174 'cycle' => ['type' => API_INT32, 'in' => implode(',', [ZBX_REPORT_CYCLE_DAILY, ZBX_REPORT_CYCLE_WEEKLY, ZBX_REPORT_CYCLE_MONTHLY, ZBX_REPORT_CYCLE_YEARLY]), 'default' => DB::getDefault('report', 'cycle')], 175 'weekdays' => ['type' => API_INT32], 176 'start_time' => ['type' => API_INT32, 'in' => '0:86340'], 177 'active_since' => ['type' => API_DATE, 'default' => ''], 178 'active_till' => ['type' => API_DATE, 'default' => ''], 179 // The length of the "report.subject" and "media_type_message.subject" fields should match. 180 'subject' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('media_type_message', 'subject')], 181 'message' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('report_param', 'value')], 182 'users' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['userid']], 'default' => [], 'fields' => [ 183 'userid' => ['type' => API_ID, 'flags' => API_REQUIRED], 184 'access_userid' => ['type' => API_ID], 185 'exclude' => ['type' => API_INT32, 'in' => ZBX_REPORT_EXCLUDE_USER_FALSE.','.ZBX_REPORT_EXCLUDE_USER_TRUE, 'default' => DB::getDefault('report_user', 'exclude')] 186 ]], 187 'user_groups' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['usrgrpid']], 'default' => [], 'fields' => [ 188 'usrgrpid' => ['type' => API_ID, 'flags' => API_REQUIRED], 189 'access_userid' => ['type' => API_ID] 190 ]] 191 ]]; 192 if (!CApiInputValidator::validate($api_input_rules, $reports, '/', $error)) { 193 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 194 } 195 196 $rnum = 0; 197 foreach ($reports as &$report) { 198 $rnum++; 199 200 if ($report['cycle'] == ZBX_REPORT_CYCLE_WEEKLY) { 201 if (!array_key_exists('weekdays', $report)) { 202 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.$rnum, 203 _s('the parameter "%1$s" is missing', 'weekdays') 204 )); 205 } 206 207 if ($report['weekdays'] < 1 || $report['weekdays'] > 127) { 208 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 209 '/'.$rnum.'/weekdays', _s('value must be one of %1$s', '1-127') 210 )); 211 } 212 } 213 elseif (array_key_exists('weekdays', $report) && $report['weekdays'] != 0) { 214 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 215 '/'.$rnum.'/weekdays', _s('value must be %1$s', '0') 216 )); 217 } 218 219 $report['active_since'] = ($report['active_since'] !== '') 220 ? $report['active_since'] = (DateTime::createFromFormat(ZBX_DATE, $report['active_since'], 221 new DateTimeZone('UTC') 222 )) 223 ->setTime(0, 0) 224 ->getTimestamp() 225 : 0; 226 227 $report['active_till'] = ($report['active_till'] !== '') 228 ? $report['active_till'] = (DateTime::createFromFormat(ZBX_DATE, $report['active_till'], 229 new DateTimeZone('UTC') 230 )) 231 ->setTime(23, 59, 59) 232 ->getTimestamp() 233 : 0; 234 235 if ($report['active_till'] > 0 && $report['active_since'] > $report['active_till']) { 236 self::exception(ZBX_API_ERROR_PARAMETERS, 237 _s('"%1$s" must be an empty string or greater than "%2$s".', 'active_till', 'active_since') 238 ); 239 } 240 } 241 unset($report); 242 243 $this->checkDuplicates(array_column($reports, 'name')); 244 $this->checkDashboards(array_unique(array_column($reports, 'dashboardid'))); 245 $this->checkUsers($reports); 246 $this->checkUserGroups($reports); 247 } 248 249 /** 250 * Check for duplicated reports. 251 * 252 * @param array $names 253 * 254 * @throws APIException if report already exists. 255 */ 256 protected function checkDuplicates(array $names): void { 257 $db_reports = DB::select('report', [ 258 'output' => ['name'], 259 'filter' => ['name' => $names], 260 'limit' => 1 261 ]); 262 263 if ($db_reports) { 264 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Report "%1$s" already exists.', $db_reports[0]['name'])); 265 } 266 } 267 268 /** 269 * Check for valid dashboards. 270 * 271 * @param array $dashboardids 272 * 273 * @throws APIException if dashboard is not valid. 274 */ 275 protected function checkDashboards(array $dashboardids): void { 276 $db_dashboards = API::Dashboard()->get([ 277 'output' => [], 278 'dashboardids' => $dashboardids, 279 'preservekeys' => true 280 ]); 281 282 foreach ($dashboardids as $dashboardid) { 283 if (!array_key_exists($dashboardid, $db_dashboards)) { 284 self::exception(ZBX_API_ERROR_PARAMETERS, 285 _s('Dashboard with ID "%1$s" is not available.', $dashboardid) 286 ); 287 } 288 } 289 } 290 291 /** 292 * Check for valid users. 293 * 294 * @param array $reports 295 * @param string $reports[]['userid'] (optional) 296 * @param string $reports[]['dashboardid'] (optional) 297 * @param array $reports[]['users'] (optional) 298 * @param string $reports[]['users'][]['userid'] 299 * @param string $reports[]['users'][]['access_userid'] (optional) 300 * @param string $reports[]['users'][]['exclude'] 301 * @param array $reports[]['user_groups'] (optional) 302 * @param string $reports[]['user_groups'][]['access_userid'] (optional) 303 * @param array $db_reports (optional) 304 * @param string $db_reports[]['reportid'] 305 * @param string $db_reports[]['userid'] 306 * @param string $db_reports[]['dashboardid'] 307 * @param array $db_reports[]['users'] 308 * @param string $db_reports[]['users'][]['userid'] 309 * @param string $db_reports[]['users'][]['access_userid'] 310 * @param string $db_reports[]['users'][]['exclude'] 311 * @param array $db_reports[]['user_groups'] 312 * @param string $db_reports[]['user_groups'][]['access_userid'] 313 * 314 * @throws APIException if user is not valid. 315 */ 316 protected function checkUsers(array $reports, array $db_reports = []): void { 317 $userids = []; 318 319 foreach ($reports as $report) { 320 $db_report = []; 321 $dashboardid_has_changed = false; 322 $users = array_key_exists('users', $report) ? $report['users'] : []; 323 $user_groups = array_key_exists('user_groups', $report) ? $report['user_groups'] : []; 324 325 if ($db_reports) { 326 $db_report = $db_reports[$report['reportid']]; 327 $dashboardid_has_changed = (array_key_exists('dashboardid', $report) 328 && $report['dashboardid'] != $db_report['dashboardid'] 329 ); 330 if (!array_key_exists('users', $report)) { 331 $users = $db_report['users']; 332 } 333 if (!array_key_exists('user_groups', $report)) { 334 $user_groups = $db_report['user_groups']; 335 } 336 } 337 338 if (array_key_exists('userid', $report) && (!$db_report || $report['userid'] != $db_report['userid'])) { 339 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 340 if ((!$db_report && $report['userid'] != self::$userData['userid']) 341 || ($db_report && $report['userid'] != $db_report['userid'])) { 342 self::exception(ZBX_API_ERROR_PARAMETERS, _('Only super admins can set report owner.')); 343 } 344 } 345 346 $userids[$report['userid']] = true; 347 } 348 349 if (!$user_groups) { 350 if (!$users) { 351 self::exception(ZBX_API_ERROR_PARAMETERS, _('At least one user or user group must be specified.')); 352 } 353 354 if (!array_key_exists(ZBX_REPORT_EXCLUDE_USER_FALSE, array_column($users, 'exclude', 'exclude'))) { 355 self::exception(ZBX_API_ERROR_PARAMETERS, 356 _('If no user groups are specified, at least one user must be included in the mailing list.') 357 ); 358 } 359 } 360 361 if (array_key_exists('users', $report) && $report['users']) { 362 $db_userids = []; 363 $db_access_userids = []; 364 if ($db_report) { 365 $db_userids = array_flip(array_column($db_report['users'], 'userid')); 366 $db_access_userids = array_flip(array_column($db_report['users'], 'access_userid')); 367 } 368 369 foreach ($report['users'] as $user) { 370 if ($dashboardid_has_changed || !array_key_exists($user['userid'], $db_userids)) { 371 $userids[$user['userid']] = true; 372 } 373 374 if (array_key_exists('access_userid', $user) && $user['access_userid'] != 0 375 && ($dashboardid_has_changed 376 || !array_key_exists($user['access_userid'], $db_access_userids))) { 377 $userids[$user['access_userid']] = true; 378 } 379 } 380 } 381 382 if (array_key_exists('user_groups', $report) && $report['user_groups']) { 383 $db_access_userids = $db_report 384 ? array_flip(array_column($db_report['user_groups'], 'access_userid')) 385 : []; 386 387 foreach ($report['user_groups'] as $usrgrp) { 388 if (array_key_exists('access_userid', $usrgrp) && $usrgrp['access_userid'] != 0 389 && ($dashboardid_has_changed 390 || !array_key_exists($usrgrp['access_userid'], $db_access_userids))) { 391 $userids[$usrgrp['access_userid']] = true; 392 } 393 } 394 } 395 } 396 397 unset($userids[self::$userData['userid']]); 398 399 if (!$userids) { 400 return; 401 } 402 403 $userids = array_keys($userids); 404 405 $db_users = API::User()->get([ 406 'output' => [], 407 'userids' => $userids, 408 'preservekeys' => true 409 ]); 410 411 foreach ($userids as $userid) { 412 if (!array_key_exists($userid, $db_users)) { 413 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User with ID "%1$s" is not available.', $userid)); 414 } 415 } 416 } 417 418 /** 419 * Check for valid user groups. 420 * 421 * @param array $reports 422 * @param string $reports[]['dashboarid'] (optional) 423 * @param array $reports[]['user_groups'] (optional) 424 * @param string $reports[]['user_groups'][]['usrgrpid'] 425 * @param array $db_reports (optional) 426 * @param string $db_reports[]['reportid'] 427 * @param string $db_reports[]['dashboarid'] 428 * @param array $db_reports[]['user_groups'] 429 * @param string $db_reports[]['user_groups'][]['usrgrpid'] 430 * 431 * @throws APIException if user group is not valid. 432 */ 433 protected function checkUserGroups(array $reports, array $db_reports = []): void { 434 $usrgrpids = []; 435 436 foreach ($reports as $report) { 437 if (array_key_exists('user_groups', $report) && $report['user_groups']) { 438 $db_usrgrpids = []; 439 $dashboardid_has_changed = false; 440 441 if ($db_reports) { 442 $db_report = $db_reports[$report['reportid']]; 443 $db_usrgrpids = array_flip(array_column($db_report['user_groups'], 'usrgrpid')); 444 $dashboardid_has_changed = (array_key_exists('dashboarid', $report) 445 && $report['dashboarid'] != $db_report['dashboarid'] 446 ); 447 } 448 449 foreach ($report['user_groups'] as $usrgrp) { 450 if ($dashboardid_has_changed || !array_key_exists($usrgrp['usrgrpid'], $db_usrgrpids)) { 451 $usrgrpids[$usrgrp['usrgrpid']] = true; 452 } 453 } 454 } 455 } 456 457 if (!$usrgrpids) { 458 return; 459 } 460 461 $usrgrpids = array_keys($usrgrpids); 462 463 $db_usrgrps = API::UserGroup()->get([ 464 'output' => [], 465 'usrgrpids' => $usrgrpids, 466 'preservekeys' => true 467 ]); 468 469 foreach ($usrgrpids as $usrgrpid) { 470 if (!array_key_exists($usrgrpid, $db_usrgrps)) { 471 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group with ID "%1$s" is not available.', $usrgrpid)); 472 } 473 } 474 } 475 476 /** 477 * @param array $reports 478 * 479 * @return array 480 */ 481 public function update(array $reports): array { 482 $this->validateUpdate($reports, $db_reports); 483 484 $upd_reports = []; 485 486 foreach ($reports as $report) { 487 $db_report = $db_reports[$report['reportid']]; 488 489 $upd_report = []; 490 491 foreach (['userid', 'status', 'dashboardid', 'period', 'cycle', 'weekdays', 'start_time', 'active_since', 492 'active_till'] as $field_name) { 493 if (array_key_exists($field_name, $report) && $report[$field_name] != $db_report[$field_name]) { 494 $upd_report[$field_name] = $report[$field_name]; 495 } 496 } 497 498 foreach (['name', 'description'] as $field_name) { 499 if (array_key_exists($field_name, $report) && $report[$field_name] !== $db_report[$field_name]) { 500 $upd_report[$field_name] = $report[$field_name]; 501 } 502 } 503 504 if ($upd_report) { 505 $upd_reports[] = [ 506 'values' => $upd_report, 507 'where' => ['reportid' => $report['reportid']] 508 ]; 509 } 510 } 511 512 if ($upd_reports) { 513 DB::update('report', $upd_reports); 514 } 515 516 $this->updateParams($reports, __FUNCTION__); 517 $this->updateUsers($reports, __FUNCTION__); 518 $this->updateUserGroups($reports, __FUNCTION__); 519 520 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_SCHEDULED_REPORT, $reports, $db_reports); 521 522 return ['reportids' => array_column($reports, 'reportid')]; 523 } 524 525 /** 526 * @param array $reports 527 * @param array|null $db_reports 528 * 529 * @throws APIException if no permissions or the input is invalid. 530 */ 531 protected function validateUpdate(array &$reports, ?array &$db_reports = null): void { 532 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ 533 'reportid' => ['type' => API_ID, 'flags' => API_REQUIRED], 534 'userid' => ['type' => API_ID], 535 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('report', 'name')], 536 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('report', 'description')], 537 'status' => ['type' => API_INT32, 'in' => ZBX_REPORT_STATUS_DISABLED.','.ZBX_REPORT_STATUS_ENABLED], 538 'dashboardid' => ['type' => API_ID], 539 'period' => ['type' => API_INT32, 'in' => implode(',', [ZBX_REPORT_PERIOD_DAY, ZBX_REPORT_PERIOD_WEEK, ZBX_REPORT_PERIOD_MONTH, ZBX_REPORT_PERIOD_YEAR])], 540 'cycle' => ['type' => API_INT32, 'in' => implode(',', [ZBX_REPORT_CYCLE_DAILY, ZBX_REPORT_CYCLE_WEEKLY, ZBX_REPORT_CYCLE_MONTHLY, ZBX_REPORT_CYCLE_YEARLY])], 541 'weekdays' => ['type' => API_INT32], 542 'start_time' => ['type' => API_INT32, 'in' => '0:86340'], 543 'active_since' => ['type' => API_DATE], 544 'active_till' => ['type' => API_DATE], 545 // The length of the "report.subject" and "media_type_message.subject" fields should match. 546 'subject' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('media_type_message', 'subject')], 547 'message' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('report_param', 'value')], 548 'users' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['userid']], 'fields' => [ 549 'userid' => ['type' => API_ID, 'flags' => API_REQUIRED], 550 'access_userid' => ['type' => API_ID], 551 'exclude' => ['type' => API_INT32, 'in' => ZBX_REPORT_EXCLUDE_USER_FALSE.','.ZBX_REPORT_EXCLUDE_USER_TRUE, 'default' => DB::getDefault('report_user', 'exclude')] 552 ]], 553 'user_groups' => ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['usrgrpid']], 'fields' => [ 554 'usrgrpid' => ['type' => API_ID, 'flags' => API_REQUIRED], 555 'access_userid' => ['type' => API_ID] 556 ]] 557 ]]; 558 if (!CApiInputValidator::validate($api_input_rules, $reports, '/', $error)) { 559 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 560 } 561 562 $db_reports = $this->get([ 563 'output' => $this->output_fields, 564 'selectUsers' => $this->user_output_fields, 565 'selectUserGroups' => $this->usrgrp_output_fields, 566 'reportids' => array_column($reports, 'reportid'), 567 'preservekeys' => true 568 ]); 569 570 $names = []; 571 $dashboardids = []; 572 573 $rnum = 0; 574 foreach ($reports as &$report) { 575 $rnum++; 576 577 if (!array_key_exists($report['reportid'], $db_reports)) { 578 self::exception(ZBX_API_ERROR_PARAMETERS, 579 _s('Report with ID "%1$s" is not available.', $report['reportid']) 580 ); 581 } 582 583 $db_report = $db_reports[$report['reportid']]; 584 585 if (array_key_exists('name', $report) && $report['name'] !== $db_report['name']) { 586 $names[] = $report['name']; 587 } 588 589 if (array_key_exists('dashboardid', $report) && $report['dashboardid'] != $db_report['dashboardid']) { 590 if (!array_key_exists('users', $report)) { 591 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.$rnum, 592 _s('the parameter "%1$s" is missing', 'users') 593 )); 594 } 595 596 if (!array_key_exists('user_groups', $report)) { 597 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', '/'.$rnum, 598 _s('the parameter "%1$s" is missing', 'user_groups') 599 )); 600 } 601 602 $dashboardids[$report['dashboardid']] = true; 603 } 604 605 if (array_key_exists('cycle', $report) || array_key_exists('weekdays', $report)) { 606 $cycle = array_key_exists('cycle', $report) ? $report['cycle'] : $db_report['cycle']; 607 $weekdays = array_key_exists('weekdays', $report) ? $report['weekdays'] : $db_report['weekdays']; 608 609 if ($cycle == ZBX_REPORT_CYCLE_WEEKLY) { 610 if ($weekdays < 1 || $weekdays > 127) { 611 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 612 '/'.$rnum.'/weekdays', _s('value must be one of %1$s', '1-127') 613 )); 614 } 615 } 616 elseif ($weekdays != 0) { 617 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.', 618 '/'.$rnum.'/weekdays', _s('value must be %1$s', '0') 619 )); 620 } 621 } 622 623 if (array_key_exists('active_since', $report) || array_key_exists('active_till', $report)) { 624 $active_since = $db_report['active_since']; 625 $active_till = $db_report['active_till']; 626 627 if (array_key_exists('active_since', $report)) { 628 $active_since = ($report['active_since'] !== '') 629 ? (DateTime::createFromFormat(ZBX_DATE, $report['active_since'], 630 new DateTimeZone('UTC') 631 )) 632 ->setTime(0, 0) 633 ->getTimestamp() 634 : 0; 635 $report['active_since'] = $active_since; 636 } 637 638 if (array_key_exists('active_till', $report)) { 639 $active_till = ($report['active_till'] !== '') 640 ? (DateTime::createFromFormat(ZBX_DATE, $report['active_till'], 641 new DateTimeZone('UTC') 642 )) 643 ->setTime(23, 59, 59) 644 ->getTimestamp() 645 : 0; 646 $report['active_till'] = $active_till; 647 } 648 649 if ($active_till > 0 && $active_since > $active_till) { 650 self::exception(ZBX_API_ERROR_PARAMETERS, 651 _s('"%1$s" must be an empty string or greater than "%2$s".', 'active_till', 'active_since') 652 ); 653 } 654 } 655 } 656 unset($report); 657 658 if ($names) { 659 $this->checkDuplicates($names); 660 } 661 if ($dashboardids) { 662 $this->checkDashboards(array_keys($dashboardids)); 663 } 664 $this->checkUsers($reports, $db_reports); 665 $this->checkUserGroups($reports, $db_reports); 666 } 667 668 /** 669 * Update table "report_param". 670 * 671 * @param array $reports 672 * @param string $method 673 */ 674 protected function updateParams(array $reports, string $method): void { 675 $params_by_name = [ 676 'subject' => 'subject', 677 'body' => 'message' 678 ]; 679 $report_params = []; 680 681 foreach ($reports as $report) { 682 foreach ($params_by_name as $name => $param) { 683 if (!array_key_exists($param, $report)) { 684 continue; 685 } 686 687 $report_params[$report['reportid']][$name] = [ 688 'name' => $name, 689 'value' => $report[$param] 690 ]; 691 } 692 } 693 694 if (!$report_params) { 695 return; 696 } 697 698 $db_report_params = ($method === 'update') 699 ? DB::select('report_param', [ 700 'output' => ['reportparamid', 'reportid', 'name', 'value'], 701 'filter' => ['reportid' => array_keys($report_params)] 702 ]) 703 : []; 704 705 $ins_report_params = []; 706 $upd_report_params = []; 707 $del_reportparamids = []; 708 709 foreach ($db_report_params as $db_report_param) { 710 $reportid = $db_report_param['reportid']; 711 $name = $db_report_param['name']; 712 713 if (array_key_exists($name, $report_params[$reportid])) { 714 $report_param = $report_params[$reportid][$name]; 715 unset($report_params[$reportid][$name]); 716 717 if ($report_param['value'] === '') { 718 $del_reportparamids[] = $db_report_param['reportparamid']; 719 } 720 else { 721 $upd_report_param = DB::getUpdatedValues('report_param', $report_param, $db_report_param); 722 723 if ($upd_report_param) { 724 $upd_report_params[] = [ 725 'values' => $upd_report_param, 726 'where' => ['reportparamid' => $db_report_param['reportparamid']] 727 ]; 728 } 729 } 730 } 731 } 732 733 foreach ($report_params as $reportid => $report_param) { 734 foreach ($report_param as $param) { 735 if ($param['value'] !== '') { 736 $ins_report_params[] = ['reportid' => $reportid] + $param; 737 } 738 } 739 } 740 741 if ($ins_report_params) { 742 DB::insertBatch('report_param', $ins_report_params); 743 } 744 745 if ($upd_report_params) { 746 DB::update('report_param', $upd_report_params); 747 } 748 749 if ($del_reportparamids) { 750 DB::delete('report_param', ['reportparamid' => $del_reportparamids]); 751 } 752 } 753 754 /** 755 * Update table "report_user". 756 * 757 * @param array $reports 758 * @param string $method 759 */ 760 protected function updateUsers(array $reports, string $method): void { 761 $report_users = []; 762 763 foreach ($reports as $report) { 764 if (array_key_exists('users', $report)) { 765 $report_users[$report['reportid']] = array_column($report['users'], null, 'userid'); 766 } 767 } 768 769 if (!$report_users) { 770 return; 771 } 772 773 $db_report_users = ($method === 'update') 774 ? DB::select('report_user', [ 775 'output' => ['reportuserid', 'reportid', 'userid', 'access_userid', 'exclude'], 776 'filter' => ['reportid' => array_keys($report_users)] 777 ]) 778 : []; 779 780 $ins_report_users = []; 781 $upd_report_users = []; 782 $del_reportuserids = []; 783 784 foreach ($db_report_users as $db_report_user) { 785 if (array_key_exists($db_report_user['userid'], $report_users[$db_report_user['reportid']])) { 786 $report_user = $report_users[$db_report_user['reportid']][$db_report_user['userid']]; 787 unset($report_users[$db_report_user['reportid']][$db_report_user['userid']]); 788 789 $upd_report_user = DB::getUpdatedValues('report_user', $report_user, $db_report_user); 790 791 if ($upd_report_user) { 792 $upd_report_users[] = [ 793 'values' => $upd_report_user, 794 'where' => ['reportuserid' => $db_report_user['reportuserid']] 795 ]; 796 } 797 } 798 else { 799 $del_reportuserids[] = $db_report_user['reportuserid']; 800 } 801 } 802 803 foreach ($report_users as $reportid => $users) { 804 foreach ($users as $user) { 805 $ins_report_users[] = ['reportid' => $reportid] + $user; 806 } 807 } 808 809 if ($ins_report_users) { 810 DB::insert('report_user', $ins_report_users); 811 } 812 813 if ($upd_report_users) { 814 DB::update('report_user', $upd_report_users); 815 } 816 817 if ($del_reportuserids) { 818 DB::delete('report_user', ['reportuserid' => $del_reportuserids]); 819 } 820 } 821 822 /** 823 * Update table "report_usrgrp". 824 * 825 * @param array $reports 826 * @param string $method 827 */ 828 protected function updateUserGroups(array $reports, string $method): void { 829 $report_usrgrps = []; 830 831 foreach ($reports as $report) { 832 if (array_key_exists('user_groups', $report)) { 833 $report_usrgrps[$report['reportid']] = array_column($report['user_groups'], null, 'usrgrpid'); 834 } 835 } 836 837 if (!$report_usrgrps) { 838 return; 839 } 840 841 $db_report_usrgrps = ($method === 'update') 842 ? DB::select('report_usrgrp', [ 843 'output' => ['reportusrgrpid', 'reportid', 'usrgrpid', 'access_userid'], 844 'filter' => ['reportid' => array_keys($report_usrgrps)] 845 ]) 846 : []; 847 848 $ins_report_usrgrps = []; 849 $upd_report_usrgrps = []; 850 $del_reportusrgrpids = []; 851 852 foreach ($db_report_usrgrps as $db_report_usrgrp) { 853 if (array_key_exists($db_report_usrgrp['usrgrpid'], $report_usrgrps[$db_report_usrgrp['reportid']])) { 854 $report_usrgrp = $report_usrgrps[$db_report_usrgrp['reportid']][$db_report_usrgrp['usrgrpid']]; 855 unset($report_usrgrps[$db_report_usrgrp['reportid']][$db_report_usrgrp['usrgrpid']]); 856 857 $upd_report_usrgrp = DB::getUpdatedValues('report_usrgrp', $report_usrgrp, $db_report_usrgrp); 858 859 if ($upd_report_usrgrp) { 860 $upd_report_usrgrps[] = [ 861 'values' => $upd_report_usrgrp, 862 'where' => ['reportusrgrpid' => $db_report_usrgrp['reportusrgrpid']] 863 ]; 864 } 865 } 866 else { 867 $del_reportusrgrpids[] = $db_report_usrgrp['reportusrgrpid']; 868 } 869 } 870 871 foreach ($report_usrgrps as $reportid => $usrgrps) { 872 foreach ($usrgrps as $usrgrp) { 873 $ins_report_usrgrps[] = ['reportid' => $reportid] + $usrgrp; 874 } 875 } 876 877 if ($ins_report_usrgrps) { 878 DB::insert('report_usrgrp', $ins_report_usrgrps); 879 } 880 881 if ($upd_report_usrgrps) { 882 DB::update('report_usrgrp', $upd_report_usrgrps); 883 } 884 885 if ($del_reportusrgrpids) { 886 DB::delete('report_usrgrp', ['reportusrgrpid' => $del_reportusrgrpids]); 887 } 888 } 889 890 /** 891 * @param array $reportids 892 * 893 * @return array 894 */ 895 public function delete(array $reportids): array { 896 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 897 if (!CApiInputValidator::validate($api_input_rules, $reportids, '/', $error)) { 898 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 899 } 900 901 $db_reports = $this->get([ 902 'output' => ['reportid', 'name'], 903 'reportids' => $reportids, 904 'preservekeys' => true 905 ]); 906 907 foreach ($reportids as $reportid) { 908 if (!array_key_exists($reportid, $db_reports)) { 909 self::exception(ZBX_API_ERROR_PERMISSIONS, 910 _('No permissions to referred object or it does not exist!') 911 ); 912 } 913 } 914 915 DB::delete('report', ['reportid' => $reportids]); 916 917 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_SCHEDULED_REPORT, $db_reports); 918 919 return ['reportids' => $reportids]; 920 } 921 922 protected function addRelatedObjects(array $options, array $result): array { 923 $reportids = array_keys($result); 924 925 // If requested, convert 'active_since' and 'active_till' from timestamp to date. 926 if ($this->outputIsRequested('active_since', $options['output']) 927 || $this->outputIsRequested('active_till', $options['output'])) { 928 foreach ($result as &$report) { 929 if (array_key_exists('active_since', $report)) { 930 $report['active_since'] = ($report['active_since'] != 0) 931 ? (new DateTime('@'.$report['active_since']))->format(ZBX_DATE) 932 : ''; 933 } 934 if (array_key_exists('active_till', $report)) { 935 $report['active_till'] = ($report['active_till'] != 0) 936 ? (new DateTime('@'.$report['active_till']))->format(ZBX_DATE) 937 : ''; 938 } 939 } 940 unset($report); 941 } 942 943 // Adding email subject and message. 944 $fields_by_name = []; 945 if ($this->outputIsRequested('subject', $options['output'])) { 946 $fields_by_name['subject'] = 'subject'; 947 } 948 if ($this->outputIsRequested('message', $options['output'])) { 949 $fields_by_name['body'] = 'message'; 950 } 951 952 if ($fields_by_name) { 953 foreach ($result as &$report) { 954 foreach ($fields_by_name as $field) { 955 $report[$field] = ''; 956 } 957 } 958 unset($report); 959 960 $params = DBselect( 961 'SELECT rp.reportid,rp.name,rp.value'. 962 ' FROM report_param rp'. 963 ' WHERE '.dbConditionInt('rp.reportid', $reportids) 964 ); 965 while ($param = DBfetch($params)) { 966 if (array_key_exists($param['name'], $fields_by_name)) { 967 $result[$param['reportid']][$fields_by_name[$param['name']]] = $param['value']; 968 } 969 } 970 } 971 972 // Adding users. 973 if ($options['selectUsers'] !== null && $options['selectUsers'] !== API_OUTPUT_COUNT) { 974 if ($options['selectUsers'] === API_OUTPUT_EXTEND) { 975 $options['selectUsers'] = $this->user_output_fields; 976 } 977 978 foreach ($result as &$report) { 979 $report['users'] = []; 980 } 981 unset($report); 982 983 if ($options['selectUsers']) { 984 $output_fields = [ 985 $this->fieldId('reportid', 'ru') 986 ]; 987 foreach ($options['selectUsers'] as $field) { 988 $output_fields[$field] = $this->fieldId($field, 'ru'); 989 } 990 991 $users = DBselect( 992 'SELECT '.implode(',', $output_fields). 993 ' FROM report_user ru'. 994 ' WHERE '.dbConditionInt('reportid', $reportids) 995 ); 996 997 while ($user = DBfetch($users)) { 998 $reportid = $user['reportid']; 999 unset($user['reportid']); 1000 1001 $result[$reportid]['users'][] = $user; 1002 } 1003 } 1004 } 1005 1006 // Adding user groups. 1007 if ($options['selectUserGroups'] !== null && $options['selectUserGroups'] !== API_OUTPUT_COUNT) { 1008 if ($options['selectUserGroups'] === API_OUTPUT_EXTEND) { 1009 $options['selectUserGroups'] = $this->usrgrp_output_fields; 1010 } 1011 1012 foreach ($result as &$report) { 1013 $report['user_groups'] = []; 1014 } 1015 unset($report); 1016 1017 if ($options['selectUserGroups']) { 1018 $output_fields = [ 1019 $this->fieldId('reportid', 'rug') 1020 ]; 1021 foreach ($options['selectUserGroups'] as $field) { 1022 $output_fields[$field] = $this->fieldId($field, 'rug'); 1023 } 1024 1025 $user_groups = DBselect( 1026 'SELECT '.implode(',', $output_fields). 1027 ' FROM report_usrgrp rug'. 1028 ' WHERE '.dbConditionInt('reportid', $reportids) 1029 ); 1030 1031 while ($user_group = DBfetch($user_groups)) { 1032 $reportid = $user_group['reportid']; 1033 unset($user_group['reportid']); 1034 1035 $result[$reportid]['user_groups'][] = $user_group; 1036 } 1037 } 1038 } 1039 1040 return $result; 1041 } 1042} 1043