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 * Common class for dashboards API and template dashboards API. 24 */ 25abstract class CDashboardGeneral extends CApiService { 26 27 protected const WIDGET_FIELD_TYPE_COLUMNS_FK = [ 28 ZBX_WIDGET_FIELD_TYPE_GROUP => 'value_groupid', 29 ZBX_WIDGET_FIELD_TYPE_HOST => 'value_hostid', 30 ZBX_WIDGET_FIELD_TYPE_ITEM => 'value_itemid', 31 ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE => 'value_itemid', 32 ZBX_WIDGET_FIELD_TYPE_GRAPH => 'value_graphid', 33 ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE => 'value_graphid', 34 ZBX_WIDGET_FIELD_TYPE_MAP => 'value_sysmapid' 35 ]; 36 37 protected const WIDGET_FIELD_TYPE_COLUMNS = [ 38 ZBX_WIDGET_FIELD_TYPE_INT32 => 'value_int', 39 ZBX_WIDGET_FIELD_TYPE_STR => 'value_str' 40 ] + self::WIDGET_FIELD_TYPE_COLUMNS_FK; 41 42 protected $tableName = 'dashboard'; 43 protected $tableAlias = 'd'; 44 protected $sortColumns = ['dashboardid', 'name']; 45 46 /** 47 * @param array $options 48 * 49 * @throws APIException if the input is invalid. 50 * 51 * @return array|int 52 */ 53 abstract public function get(array $options = []); 54 55 /** 56 * @param array $dashboardids 57 * 58 * @throws APIException if the input is invalid. 59 * 60 * @return array 61 */ 62 public function delete(array $dashboardids): array { 63 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 64 65 if (!CApiInputValidator::validate($api_input_rules, $dashboardids, '/', $error)) { 66 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 67 } 68 69 $db_dashboards = $this->get([ 70 'output' => ['dashboardid', 'name'], 71 'dashboardids' => $dashboardids, 72 'editable' => true, 73 'preservekeys' => true 74 ]); 75 76 if (count($db_dashboards) != count($dashboardids)) { 77 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 78 } 79 80 // Check if dashboards are used in scheduled reports. 81 if ($this instanceof CDashboard) { 82 $db_reports = DB::select('report', [ 83 'output' => ['name', 'dashboardid'], 84 'filter' => ['dashboardid' => $dashboardids], 85 'limit' => 1 86 ]); 87 88 if ($db_reports) { 89 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Dashboard "%1$s" is used in report "%2$s".', 90 $db_dashboards[$db_reports[0]['dashboardid']]['name'], $db_reports[0]['name'] 91 )); 92 } 93 } 94 95 $db_dashboard_pages = DB::select('dashboard_page', [ 96 'output' => [], 97 'filter' => ['dashboardid' => $dashboardids], 98 'preservekeys' => true 99 ]); 100 101 if ($db_dashboard_pages) { 102 $db_widgets = DB::select('widget', [ 103 'output' => [], 104 'filter' => ['dashboard_pageid' => array_keys($db_dashboard_pages)], 105 'preservekeys' => true 106 ]); 107 108 if ($db_widgets) { 109 self::deleteWidgets(array_keys($db_widgets)); 110 } 111 112 DB::delete('dashboard_page', ['dashboard_pageid' => array_keys($db_dashboard_pages)]); 113 } 114 115 DB::delete('dashboard', ['dashboardid' => $dashboardids]); 116 117 $this->addAuditBulk(AUDIT_ACTION_DELETE, static::AUDIT_RESOURCE, $db_dashboards); 118 119 return ['dashboardids' => $dashboardids]; 120 } 121 122 /** 123 * Add the existing pages, widgets and widget fields to $db_dashboards whether these are affected by the update. 124 * 125 * @param array $dashboards 126 * @param array $db_dashboards 127 */ 128 protected function addAffectedObjects(array $dashboards, array &$db_dashboards): void { 129 // Select pages of these dashboards. 130 $dashboardids = []; 131 132 // Select widgets of these pages. 133 $dashboard_pageids = []; 134 135 // Select fields of these widgets. 136 $widgetids = []; 137 138 foreach ($dashboards as $dashboard) { 139 if (array_key_exists('pages', $dashboard)) { 140 $dashboardids[$dashboard['dashboardid']] = true; 141 142 foreach ($dashboard['pages'] as $dashboard_page) { 143 if (array_key_exists('dashboard_pageid', $dashboard_page)) { 144 if (array_key_exists('widgets', $dashboard_page)) { 145 $dashboard_pageids[$dashboard_page['dashboard_pageid']] = true; 146 147 foreach ($dashboard_page['widgets'] as $widget) { 148 if (array_key_exists('widgetid', $widget)) { 149 if (array_key_exists('fields', $widget)) { 150 $widgetids[$widget['widgetid']] = true; 151 } 152 } 153 } 154 } 155 } 156 } 157 } 158 } 159 160 foreach ($db_dashboards as &$db_dashboard) { 161 $db_dashboard['pages'] = []; 162 } 163 unset($db_dashboard); 164 165 if ($dashboardids) { 166 $db_dashboard_pages = DB::select('dashboard_page', [ 167 'output' => array_keys(DB::getSchema('dashboard_page')['fields']), 168 'filter' => ['dashboardid' => array_keys($dashboardids)], 169 'preservekeys' => true 170 ]); 171 172 foreach ($db_dashboard_pages as &$db_dashboard_page) { 173 $db_dashboard_page['widgets'] = []; 174 } 175 unset($db_dashboard_page); 176 177 if ($dashboard_pageids) { 178 $db_widgets = DB::select('widget', [ 179 'output' => array_keys(DB::getSchema('widget')['fields']), 180 'filter' => ['dashboard_pageid' => array_keys($dashboard_pageids)], 181 'preservekeys' => true 182 ]); 183 184 foreach ($db_widgets as &$db_widget) { 185 $db_widget['fields'] = []; 186 } 187 unset($db_widget); 188 189 if ($widgetids) { 190 $db_widget_fields = DB::select('widget_field', [ 191 'output' => array_keys(DB::getSchema('widget_field')['fields']), 192 'filter' => ['widgetid' => array_keys($widgetids)], 193 'preservekeys' => true 194 ]); 195 196 foreach ($db_widget_fields as $widget_fieldid => $db_widget_field) { 197 $db_widgets[$db_widget_field['widgetid']]['fields'][$widget_fieldid] = $db_widget_field; 198 } 199 } 200 201 foreach ($db_widgets as $widgetid => $db_widget) { 202 $db_dashboard_pages[$db_widget['dashboard_pageid']]['widgets'][$widgetid] = $db_widget; 203 } 204 } 205 206 foreach ($db_dashboard_pages as $dashboard_pageid => $db_dashboard_page) { 207 $db_dashboards[$db_dashboard_page['dashboardid']]['pages'][$dashboard_pageid] = $db_dashboard_page; 208 } 209 } 210 } 211 212 /** 213 * Check ownership of the referenced pages and widgets. 214 * 215 * @param array $dashboards 216 * @param array $db_dashboards 217 * 218 * @throws APIException. 219 */ 220 protected function checkReferences(array $dashboards, array $db_dashboards): void { 221 foreach ($dashboards as $dashboard) { 222 if (!array_key_exists('pages', $dashboard)) { 223 continue; 224 } 225 226 $db_dashboard_pages = $db_dashboards[$dashboard['dashboardid']]['pages']; 227 228 foreach ($dashboard['pages'] as $dashboard_page) { 229 if (array_key_exists('dashboard_pageid', $dashboard_page) 230 && !array_key_exists($dashboard_page['dashboard_pageid'], $db_dashboard_pages)) { 231 self::exception(ZBX_API_ERROR_PERMISSIONS, 232 _('No permissions to referred object or it does not exist!') 233 ); 234 } 235 236 if (!array_key_exists('widgets', $dashboard_page)) { 237 continue; 238 } 239 240 $db_widgets = array_key_exists('dashboard_pageid', $dashboard_page) 241 ? $db_dashboard_pages[$dashboard_page['dashboard_pageid']]['widgets'] 242 : []; 243 244 foreach ($dashboard_page['widgets'] as $widget) { 245 if (array_key_exists('widgetid', $widget) && !array_key_exists($widget['widgetid'], $db_widgets)) { 246 self::exception(ZBX_API_ERROR_PERMISSIONS, 247 _('No permissions to referred object or it does not exist!') 248 ); 249 } 250 } 251 } 252 } 253 } 254 255 /** 256 * Check widgets. 257 * 258 * Note: For any object with ID in $dashboards a corresponding object in $db_dashboards must exist. 259 * 260 * @param array $dashboards 261 * @param array|null $db_dashboards 262 * 263 * @throws APIException if the input is invalid. 264 */ 265 protected function checkWidgets(array $dashboards, array $db_dashboards = null): void { 266 $widget_defaults = DB::getDefaults('widget'); 267 268 foreach ($dashboards as $dashboard) { 269 if (!array_key_exists('pages', $dashboard)) { 270 continue; 271 } 272 273 $db_dashboard_pages = ($db_dashboards !== null) ? $db_dashboards[$dashboard['dashboardid']]['pages'] : null; 274 275 foreach ($dashboard['pages'] as $index => $dashboard_page) { 276 if (!array_key_exists('widgets', $dashboard_page)) { 277 continue; 278 } 279 280 $filled = []; 281 282 foreach ($dashboard_page['widgets'] as $widget) { 283 $widget += array_key_exists('widgetid', $widget) 284 ? $db_dashboard_pages[$dashboard_page['dashboard_pageid']]['widgets'][$widget['widgetid']] 285 : $widget_defaults; 286 287 for ($x = $widget['x']; $x < $widget['x'] + $widget['width']; $x++) { 288 for ($y = $widget['y']; $y < $widget['y'] + $widget['height']; $y++) { 289 if (array_key_exists($x, $filled) && array_key_exists($y, $filled[$x])) { 290 self::exception(ZBX_API_ERROR_PARAMETERS, 291 _s('Overlapping widgets at X:%3$d, Y:%4$d on page #%2$d of dashboard "%1$s".', 292 $dashboard['name'], $index + 1, $widget['x'], $widget['y'] 293 ) 294 ); 295 } 296 297 $filled[$x][$y] = true; 298 } 299 } 300 301 if ($widget['x'] + $widget['width'] > DASHBOARD_MAX_COLUMNS 302 || $widget['y'] + $widget['height'] > DASHBOARD_MAX_ROWS) { 303 self::exception(ZBX_API_ERROR_PARAMETERS, 304 _s('Widget at X:%3$d, Y:%4$d on page #%2$d of dashboard "%1$s" is out of bounds.', 305 $dashboard['name'], $index + 1, $widget['x'], $widget['y'] 306 ) 307 ); 308 } 309 } 310 } 311 } 312 } 313 314 /** 315 * Check widget fields. 316 * 317 * Note: For any object with ID in $dashboards a corresponding object in $db_dashboards must exist. 318 * 319 * @param array $dashboards 320 * @param array|null $db_dashboards 321 * 322 * @throws APIException if the input is invalid. 323 */ 324 protected function checkWidgetFields(array $dashboards, array $db_dashboards = null): void { 325 $ids = [ 326 ZBX_WIDGET_FIELD_TYPE_ITEM => [], 327 ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE => [], 328 ZBX_WIDGET_FIELD_TYPE_GRAPH => [], 329 ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE => [], 330 ZBX_WIDGET_FIELD_TYPE_GROUP => [], 331 ZBX_WIDGET_FIELD_TYPE_HOST => [], 332 ZBX_WIDGET_FIELD_TYPE_MAP => [] 333 ]; 334 335 foreach ($dashboards as $dashboard) { 336 if (!array_key_exists('pages', $dashboard)) { 337 continue; 338 } 339 340 $db_dashboard_pages = ($db_dashboards !== null) ? $db_dashboards[$dashboard['dashboardid']]['pages'] : null; 341 342 foreach ($dashboard['pages'] as $dashboard_page) { 343 if (!array_key_exists('widgets', $dashboard_page)) { 344 continue; 345 } 346 347 foreach ($dashboard_page['widgets'] as $widget) { 348 if (!array_key_exists('fields', $widget)) { 349 continue; 350 } 351 352 $widgetid = array_key_exists('widgetid', $widget) ? $widget['widgetid'] : null; 353 354 // Skip testing linked object availability of already stored widget fields. 355 $stored_widget_fields = []; 356 357 if ($widgetid !== null) { 358 $db_widget = $db_dashboard_pages[$dashboard_page['dashboard_pageid']]['widgets'][$widgetid]; 359 360 foreach ($db_widget['fields'] as $db_widget_field) { 361 if (array_key_exists($db_widget_field['type'], $ids)) { 362 $value = $db_widget_field[self::WIDGET_FIELD_TYPE_COLUMNS[$db_widget_field['type']]]; 363 $stored_widget_fields[$db_widget_field['type']][$value] = true; 364 } 365 } 366 } 367 368 foreach ($widget['fields'] as $widget_field) { 369 if (array_key_exists($widget_field['type'], $ids)) { 370 if ($widgetid === null 371 || !array_key_exists($widget_field['type'], $stored_widget_fields) 372 || !array_key_exists($widget_field['value'], 373 $stored_widget_fields[$widget_field['type']] 374 )) { 375 if ($this instanceof CTemplateDashboard) { 376 $ids[$widget_field['type']][$widget_field['value']][$dashboard['templateid']] = 377 true; 378 } 379 else { 380 $ids[$widget_field['type']][$widget_field['value']] = true; 381 } 382 } 383 } 384 } 385 } 386 } 387 } 388 389 if ($ids[ZBX_WIDGET_FIELD_TYPE_ITEM]) { 390 $itemids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_ITEM]); 391 392 $db_items = API::Item()->get([ 393 'output' => ($this instanceof CTemplateDashboard) ? ['hostid'] : [], 394 'itemids' => $itemids, 395 'webitems' => true, 396 'preservekeys' => true 397 ]); 398 399 foreach ($itemids as $itemid) { 400 if (!array_key_exists($itemid, $db_items)) { 401 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Item with ID "%1$s" is not available.', $itemid)); 402 } 403 404 if ($this instanceof CTemplateDashboard) { 405 foreach (array_keys($ids[ZBX_WIDGET_FIELD_TYPE_ITEM][$itemid]) as $templateid) { 406 if ($db_items[$itemid]['hostid'] != $templateid) { 407 self::exception(ZBX_API_ERROR_PARAMETERS, 408 _s('Item with ID "%1$s" is not available.', $itemid) 409 ); 410 } 411 } 412 } 413 } 414 } 415 416 if ($ids[ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE]) { 417 $item_prototypeids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE]); 418 419 $db_item_prototypes = API::ItemPrototype()->get([ 420 'output' => ($this instanceof CTemplateDashboard) ? ['hostid'] : [], 421 'itemids' => $item_prototypeids, 422 'preservekeys' => true 423 ]); 424 425 foreach ($item_prototypeids as $item_prototypeid) { 426 if (!array_key_exists($item_prototypeid, $db_item_prototypes)) { 427 self::exception(ZBX_API_ERROR_PARAMETERS, 428 _s('Item prototype with ID "%1$s" is not available.', $item_prototypeid) 429 ); 430 } 431 432 if ($this instanceof CTemplateDashboard) { 433 foreach (array_keys($ids[ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE][$item_prototypeid]) as $templateid) { 434 if ($db_item_prototypes[$item_prototypeid]['hostid'] != $templateid) { 435 self::exception(ZBX_API_ERROR_PARAMETERS, 436 _s('Item prototype with ID "%1$s" is not available.', $item_prototypeid) 437 ); 438 } 439 } 440 } 441 } 442 } 443 444 if ($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH]) { 445 $graphids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH]); 446 447 $db_graphs = API::Graph()->get([ 448 'output' => [], 449 'selectHosts' => ($this instanceof CTemplateDashboard) ? ['hostid'] : null, 450 'graphids' => $graphids, 451 'preservekeys' => true 452 ]); 453 454 foreach ($graphids as $graphid) { 455 if (!array_key_exists($graphid, $db_graphs)) { 456 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Graph with ID "%1$s" is not available.', $graphid)); 457 } 458 459 if ($this instanceof CTemplateDashboard) { 460 foreach (array_keys($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH][$graphid]) as $templateid) { 461 if (!in_array($templateid, array_column($db_graphs[$graphid]['hosts'], 'hostid'))) { 462 self::exception(ZBX_API_ERROR_PARAMETERS, 463 _s('Graph with ID "%1$s" is not available.', $graphid) 464 ); 465 } 466 } 467 } 468 } 469 } 470 471 if ($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE]) { 472 $graph_prototypeids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE]); 473 474 $db_graph_prototypes = API::GraphPrototype()->get([ 475 'output' => [], 476 'selectHosts' => ($this instanceof CTemplateDashboard) ? ['hostid'] : null, 477 'graphids' => $graph_prototypeids, 478 'preservekeys' => true 479 ]); 480 481 foreach ($graph_prototypeids as $graph_prototypeid) { 482 if (!array_key_exists($graph_prototypeid, $db_graph_prototypes)) { 483 self::exception(ZBX_API_ERROR_PARAMETERS, 484 _s('Graph prototype with ID "%1$s" is not available.', $graph_prototypeid) 485 ); 486 } 487 488 if ($this instanceof CTemplateDashboard) { 489 $templateids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE][$graph_prototypeid]); 490 foreach ($templateids as $templateid) { 491 $hostids = array_column($db_graph_prototypes[$graph_prototypeid]['hosts'], 'hostid'); 492 if (!in_array($templateid, $hostids)) { 493 self::exception(ZBX_API_ERROR_PARAMETERS, 494 _s('Graph prototype with ID "%1$s" is not available.', $graph_prototypeid) 495 ); 496 } 497 } 498 } 499 } 500 } 501 502 if ($ids[ZBX_WIDGET_FIELD_TYPE_GROUP]) { 503 $groupids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_GROUP]); 504 505 $db_groups = API::HostGroup()->get([ 506 'output' => [], 507 'groupids' => $groupids, 508 'preservekeys' => true 509 ]); 510 511 foreach ($groupids as $groupid) { 512 if (!array_key_exists($groupid, $db_groups)) { 513 self::exception(ZBX_API_ERROR_PARAMETERS, 514 _s('Host group with ID "%1$s" is not available.', $groupid) 515 ); 516 } 517 } 518 } 519 520 if ($ids[ZBX_WIDGET_FIELD_TYPE_HOST]) { 521 $hostids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_HOST]); 522 523 $db_hosts = API::Host()->get([ 524 'output' => [], 525 'hostids' => $hostids, 526 'preservekeys' => true 527 ]); 528 529 foreach ($hostids as $hostid) { 530 if (!array_key_exists($hostid, $db_hosts)) { 531 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host with ID "%1$s" is not available.', $hostid)); 532 } 533 } 534 } 535 536 if ($ids[ZBX_WIDGET_FIELD_TYPE_MAP]) { 537 $sysmapids = array_keys($ids[ZBX_WIDGET_FIELD_TYPE_MAP]); 538 539 $db_sysmaps = API::Map()->get([ 540 'output' => [], 541 'sysmapids' => $sysmapids, 542 'preservekeys' => true 543 ]); 544 545 foreach ($sysmapids as $sysmapid) { 546 if (!array_key_exists($sysmapid, $db_sysmaps)) { 547 self::exception(ZBX_API_ERROR_PARAMETERS, 548 _s('Map with ID "%1$s" is not available.', $sysmapid) 549 ); 550 } 551 } 552 } 553 } 554 555 /** 556 * Update table "dashboard_page". 557 * 558 * Note: For any object with ID in $dashboards a corresponding object in $db_dashboards must exist. 559 * 560 * @param array $dashboards 561 * @param array|null $db_dashboards 562 */ 563 protected function updatePages(array $dashboards, array $db_dashboards = null): void { 564 $db_dashboard_pages = []; 565 566 if ($db_dashboards !== null) { 567 foreach ($dashboards as $dashboard) { 568 if (array_key_exists('pages', $dashboard)) { 569 $db_dashboard_pages += $db_dashboards[$dashboard['dashboardid']]['pages']; 570 } 571 } 572 } 573 574 $ins_dashboard_pages = []; 575 $upd_dashboard_pages = []; 576 577 foreach ($dashboards as $dashboard) { 578 if (!array_key_exists('pages', $dashboard)) { 579 continue; 580 } 581 582 foreach ($dashboard['pages'] as $index => $dashboard_page) { 583 $dashboard_page['sortorder'] = $index; 584 585 if (array_key_exists('dashboard_pageid', $dashboard_page)) { 586 $upd_dashboard_page = DB::getUpdatedValues('dashboard_page', $dashboard_page, 587 $db_dashboard_pages[$dashboard_page['dashboard_pageid']] 588 ); 589 590 if ($upd_dashboard_page) { 591 $upd_dashboard_pages[] = [ 592 'values' => $upd_dashboard_page, 593 'where' => ['dashboard_pageid' => $dashboard_page['dashboard_pageid']] 594 ]; 595 } 596 597 unset($db_dashboard_pages[$dashboard_page['dashboard_pageid']]); 598 } 599 else { 600 unset($dashboard_page['widgets']); 601 $ins_dashboard_pages[] = ['dashboardid' => $dashboard['dashboardid']] + $dashboard_page; 602 } 603 } 604 } 605 606 if ($ins_dashboard_pages) { 607 $dashboard_pageids = DB::insert('dashboard_page', $ins_dashboard_pages); 608 609 foreach ($dashboards as &$dashboard) { 610 if (array_key_exists('pages', $dashboard)) { 611 foreach ($dashboard['pages'] as &$dashboard_page) { 612 if (!array_key_exists('dashboard_pageid', $dashboard_page)) { 613 $dashboard_page['dashboard_pageid'] = array_shift($dashboard_pageids); 614 } 615 } 616 unset($dashboard_page); 617 } 618 } 619 unset($dashboard); 620 } 621 622 if ($upd_dashboard_pages) { 623 DB::update('dashboard_page', $upd_dashboard_pages); 624 } 625 626 $this->updateWidgets($dashboards, $db_dashboards); 627 628 if ($db_dashboard_pages) { 629 DB::delete('dashboard_page', ['dashboard_pageid' => array_keys($db_dashboard_pages)]); 630 } 631 } 632 633 /** 634 * Update table "widget". 635 * 636 * Note: For any object with ID in $dashboards a corresponding object in $db_dashboards must exist. 637 * 638 * @param array $dashboards 639 * @param array|null $db_dashboards 640 */ 641 protected function updateWidgets(array $dashboards, array $db_dashboards = null): void { 642 $db_widgets = []; 643 644 if ($db_dashboards !== null) { 645 foreach ($dashboards as $dashboard) { 646 if (!array_key_exists('pages', $dashboard)) { 647 continue; 648 } 649 650 $db_dashboard_pages = $db_dashboards[$dashboard['dashboardid']]['pages']; 651 652 foreach ($dashboard['pages'] as $dashboard_page) { 653 if (!array_key_exists('widgets', $dashboard_page)) { 654 continue; 655 } 656 657 if (array_key_exists($dashboard_page['dashboard_pageid'], $db_dashboard_pages)) { 658 $db_widgets += $db_dashboard_pages[$dashboard_page['dashboard_pageid']]['widgets']; 659 } 660 } 661 } 662 } 663 664 $ins_widgets = []; 665 $upd_widgets = []; 666 667 foreach ($dashboards as $dashboard) { 668 if (!array_key_exists('pages', $dashboard)) { 669 continue; 670 } 671 672 foreach ($dashboard['pages'] as $dashboard_page) { 673 if (!array_key_exists('widgets', $dashboard_page)) { 674 continue; 675 } 676 677 foreach ($dashboard_page['widgets'] as $widget) { 678 if (array_key_exists('widgetid', $widget)) { 679 $upd_widget = DB::getUpdatedValues('widget', $widget, $db_widgets[$widget['widgetid']]); 680 681 if ($upd_widget) { 682 $upd_widgets[] = [ 683 'values' => $upd_widget, 684 'where' => ['widgetid' => $widget['widgetid']] 685 ]; 686 } 687 688 unset($db_widgets[$widget['widgetid']]); 689 } 690 else { 691 $ins_widgets[] = ['dashboard_pageid' => $dashboard_page['dashboard_pageid']] + $widget; 692 } 693 } 694 } 695 } 696 697 if ($ins_widgets) { 698 $widgetids = DB::insert('widget', $ins_widgets); 699 700 foreach ($dashboards as &$dashboard) { 701 if (array_key_exists('pages', $dashboard)) { 702 foreach ($dashboard['pages'] as &$dashboard_page) { 703 if (array_key_exists('widgets', $dashboard_page)) { 704 foreach ($dashboard_page['widgets'] as &$widget) { 705 if (!array_key_exists('widgetid', $widget)) { 706 $widget['widgetid'] = array_shift($widgetids); 707 } 708 } 709 unset($widget); 710 } 711 } 712 unset($dashboard_page); 713 } 714 } 715 unset($dashboard); 716 } 717 718 if ($upd_widgets) { 719 DB::update('widget', $upd_widgets); 720 } 721 722 if ($db_widgets) { 723 self::deleteWidgets(array_keys($db_widgets)); 724 } 725 726 $this->updateWidgetFields($dashboards, $db_dashboards); 727 } 728 729 /** 730 * Update table "widget_field". 731 * 732 * Note: For any object with ID in $dashboards a corresponding object in $db_dashboards must exist. 733 * 734 * @param array $dashboards 735 * @param array|null $db_dashboards 736 */ 737 protected function updateWidgetFields(array $dashboards, array $db_dashboards = null): void { 738 $ins_widget_fields = []; 739 $upd_widget_fields = []; 740 $del_widget_fieldids = []; 741 742 foreach ($dashboards as $dashboard) { 743 if (!array_key_exists('pages', $dashboard)) { 744 continue; 745 } 746 747 $db_dashboard_pages = ($db_dashboards !== null) ? $db_dashboards[$dashboard['dashboardid']]['pages'] : []; 748 749 foreach ($dashboard['pages'] as $dashboard_page) { 750 if (!array_key_exists('widgets', $dashboard_page)) { 751 continue; 752 } 753 754 $db_widgets = array_key_exists($dashboard_page['dashboard_pageid'], $db_dashboard_pages) 755 ? $db_dashboard_pages[$dashboard_page['dashboard_pageid']]['widgets'] 756 : []; 757 758 foreach ($dashboard_page['widgets'] as $widget) { 759 if (!array_key_exists('fields', $widget)) { 760 continue; 761 } 762 763 $db_widget_fields = array_key_exists($widget['widgetid'], $db_widgets) 764 ? $db_widgets[$widget['widgetid']]['fields'] 765 : []; 766 767 $widget_fields = []; 768 769 foreach ($widget['fields'] as $widget_field) { 770 $widget_field[self::WIDGET_FIELD_TYPE_COLUMNS[$widget_field['type']]] = $widget_field['value']; 771 $widget_fields[$widget_field['type']][$widget_field['name']][] = $widget_field; 772 } 773 774 foreach ($db_widget_fields as $db_widget_field) { 775 if (array_key_exists($db_widget_field['type'], $widget_fields) 776 && array_key_exists($db_widget_field['name'], $widget_fields[$db_widget_field['type']]) 777 && $widget_fields[$db_widget_field['type']][$db_widget_field['name']]) { 778 $widget_field = array_shift( 779 $widget_fields[$db_widget_field['type']][$db_widget_field['name']] 780 ); 781 782 $upd_widget_field = DB::getUpdatedValues('widget_field', $widget_field, $db_widget_field); 783 784 if ($upd_widget_field) { 785 $upd_widget_fields[] = [ 786 'values' => $upd_widget_field, 787 'where' => ['widget_fieldid' => $db_widget_field['widget_fieldid']] 788 ]; 789 } 790 } 791 else { 792 $del_widget_fieldids[] = $db_widget_field['widget_fieldid']; 793 } 794 } 795 796 foreach ($widget_fields as $widget_fields) { 797 foreach ($widget_fields as $widget_fields) { 798 foreach ($widget_fields as $widget_field) { 799 $ins_widget_fields[] = ['widgetid' => $widget['widgetid']] + $widget_field; 800 } 801 } 802 } 803 } 804 } 805 } 806 807 if ($ins_widget_fields) { 808 DB::insert('widget_field', $ins_widget_fields); 809 } 810 811 if ($upd_widget_fields) { 812 DB::update('widget_field', $upd_widget_fields); 813 } 814 815 if ($del_widget_fieldids) { 816 DB::delete('widget_field', ['widget_fieldid' => $del_widget_fieldids]); 817 } 818 } 819 820 /** 821 * Delete widgets. 822 * 823 * This will also delete profile keys related to the specified widgets, including the standard ones: 824 * - web.dashboard.widget.rf_rate 825 * - web.dashboard.widget.navtree.item.selected 826 * - web.dashboard.widget.navtree.item-*.toggle 827 * 828 * @static 829 * 830 * @param array $widgetids 831 */ 832 protected static function deleteWidgets(array $widgetids): void { 833 DBexecute( 834 'DELETE FROM profiles'. 835 ' WHERE idx LIKE '.zbx_dbstr('web.dashboard.widget.%'). 836 ' AND '.dbConditionId('idx2', $widgetids) 837 ); 838 839 DB::delete('widget', ['widgetid' => $widgetids]); 840 } 841 842 protected function addRelatedObjects(array $options, array $result) { 843 $result = parent::addRelatedObjects($options, $result); 844 845 if ($options['selectPages'] !== null) { 846 foreach ($result as &$row) { 847 $row['pages'] = []; 848 } 849 unset($row); 850 851 $widgets_requested = $this->outputIsRequested('widgets', $options['selectPages']); 852 853 if ($widgets_requested && is_array($options['selectPages'])) { 854 $options['selectPages'] = array_diff($options['selectPages'], ['widgets']); 855 } 856 857 $db_dashboard_pages = API::getApiService()->select('dashboard_page', [ 858 'output' => $this->outputExtend($options['selectPages'], ['dashboardid', 'sortorder']), 859 'filter' => ['dashboardid' => array_keys($result)], 860 'preservekeys' => true 861 ]); 862 863 if ($db_dashboard_pages) { 864 uasort($db_dashboard_pages, function (array $db_dashboard_page_1, array $db_dashboard_page_2): int { 865 return $db_dashboard_page_1['sortorder'] <=> $db_dashboard_page_2['sortorder']; 866 }); 867 868 if ($widgets_requested) { 869 foreach ($db_dashboard_pages as &$db_dashboard_page) { 870 $db_dashboard_page['widgets'] = []; 871 } 872 unset($db_dashboard_page); 873 874 $db_widgets = DB::select('widget', [ 875 'output' => ['widgetid', 'type', 'name', 'x', 'y', 'width', 'height', 'view_mode', 876 'dashboard_pageid' 877 ], 878 'filter' => ['dashboard_pageid' => array_keys($db_dashboard_pages)], 879 'preservekeys' => true 880 ]); 881 882 if ($db_widgets) { 883 foreach ($db_widgets as &$db_widget) { 884 $db_widget['fields'] = []; 885 } 886 unset($db_widget); 887 888 $db_widget_fields = DB::select('widget_field', [ 889 'output' => ['widget_fieldid', 'widgetid', 'type', 'name', 'value_int', 'value_str', 890 'value_groupid', 'value_hostid', 'value_itemid', 'value_graphid', 'value_sysmapid' 891 ], 892 'filter' => [ 893 'widgetid' => array_keys($db_widgets), 894 'type' => array_keys(self::WIDGET_FIELD_TYPE_COLUMNS) 895 ] 896 ]); 897 898 foreach ($db_widget_fields as $db_widget_field) { 899 $db_widgets[$db_widget_field['widgetid']]['fields'][] = [ 900 'type' => $db_widget_field['type'], 901 'name' => $db_widget_field['name'], 902 'value' => $db_widget_field[self::WIDGET_FIELD_TYPE_COLUMNS[$db_widget_field['type']]] 903 ]; 904 } 905 } 906 907 foreach ($db_widgets as $db_widget) { 908 $dashboard_pageid = $db_widget['dashboard_pageid']; 909 unset($db_widget['dashboard_pageid']); 910 $db_dashboard_pages[$dashboard_pageid]['widgets'][] = $db_widget; 911 } 912 } 913 914 $db_dashboard_pages = $this->unsetExtraFields($db_dashboard_pages, ['dashboard_pageid'], 915 $options['selectPages'] 916 ); 917 918 foreach ($db_dashboard_pages as $db_dashboard_page) { 919 $dashboardid = $db_dashboard_page['dashboardid']; 920 unset($db_dashboard_page['dashboardid'], $db_dashboard_page['sortorder']); 921 $result[$dashboardid]['pages'][] = $db_dashboard_page; 922 } 923 } 924 } 925 926 return $result; 927 } 928} 929