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 22class CWidgetFormSvgGraph extends CWidgetForm { 23 24 public function __construct($data) { 25 parent::__construct($data, WIDGET_SVG_GRAPH); 26 27 $this->data = self::convertDottedKeys($this->data); 28 29 // API doesn't guarantee fields to be retrieved in same order as stored. 30 foreach (['or', 'ds'] as $field) { 31 if (array_key_exists($field, $this->data)) { 32 ksort($this->data[$field]); 33 } 34 } 35 36 /** 37 * Data set tab. 38 * 39 * Contains single CWidgetFieldGraphDataSet field for data sets definition and configuration. 40 */ 41 $field_ds = (new CWidgetFieldGraphDataSet('ds', _('Data set')))->setFlags(CWidgetField::FLAG_NOT_EMPTY); 42 43 if (array_key_exists('ds', $this->data)) { 44 $field_ds->setValue($this->data['ds']); 45 } 46 47 $this->fields[$field_ds->getName()] = $field_ds; 48 49 /** 50 * Display options tab. 51 * 52 * Used to select either data are loaded from History or Trends or turning automatic mode on. 53 */ 54 $field_data_source = (new CWidgetFieldRadioButtonList('source', _('History data selection'), [ 55 SVG_GRAPH_DATA_SOURCE_AUTO => _x('Auto', 'history source selection method'), 56 SVG_GRAPH_DATA_SOURCE_HISTORY => _('History'), 57 SVG_GRAPH_DATA_SOURCE_TRENDS => _('Trends') 58 ])) 59 ->setDefault(SVG_GRAPH_DATA_SOURCE_AUTO) 60 ->setModern(true); 61 62 if (array_key_exists('source', $this->data)) { 63 $field_data_source->setValue($this->data['source']); 64 } 65 66 $this->fields[$field_data_source->getName()] = $field_data_source; 67 68 /** 69 * Time period tab. 70 * 71 * Contains fields for specifying widget time options. 72 */ 73 // Checkbox to specify either relative dashboard time or widget's own time. 74 $field_graph_time = (new CWidgetFieldCheckBox('graph_time', _('Set custom time period'))) 75 ->setAction('jQuery("#time_from, #time_to, #time_from_calendar, #time_to_calendar")'. 76 '.prop("disabled", !jQuery(this).is(":checked"));' 77 ); 78 79 if (array_key_exists('graph_time', $this->data)) { 80 $field_graph_time->setValue($this->data['graph_time']); 81 } 82 83 $this->fields[$field_graph_time->getName()] = $field_graph_time; 84 85 // Date from. 86 $field_time_from = (new CWidgetFieldDatePicker('time_from', _('From')))->setDefault('now-1h'); 87 88 if ($field_graph_time->getValue() != SVG_GRAPH_CUSTOM_TIME) { 89 $field_time_from->setFlags(CWidgetField::FLAG_DISABLED); 90 } 91 elseif (array_key_exists('time_from', $this->data)) { 92 $field_time_from->setValue($this->data['time_from']); 93 } 94 95 $this->fields[$field_time_from->getName()] = $field_time_from; 96 97 // Time to. 98 $field_time_to = (new CWidgetFieldDatePicker('time_to', _('To')))->setDefault('now'); 99 100 if ($field_graph_time->getValue() != SVG_GRAPH_CUSTOM_TIME) { 101 $field_time_to->setFlags(CWidgetField::FLAG_DISABLED); 102 } 103 elseif (array_key_exists('time_to', $this->data)) { 104 $field_time_to->setValue($this->data['time_to']); 105 } 106 107 $this->fields[$field_time_to->getName()] = $field_time_to; 108 109 /** 110 * Axes tab. 111 * 112 * Contains fields to specify options for graph axes. 113 */ 114 // Show left Y axis. 115 $field_lefty = (new CWidgetFieldCheckBox('lefty', _('Left Y'), _('Show'))) 116 ->setDefault(SVG_GRAPH_AXIS_SHOW) 117 ->setAction('onLeftYChange()'); 118 119 if (array_key_exists('lefty', $this->data)) { 120 $field_lefty->setValue($this->data['lefty']); 121 } 122 123 $this->fields[$field_lefty->getName()] = $field_lefty; 124 125 // Min value on left Y axis. 126 $field_lefty_min = (new CWidgetFieldNumericBox('lefty_min', _('Min'))) 127 ->setPlaceholder(_('calculated')) 128 ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH); 129 130 if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) { 131 $field_lefty_min->setFlags(CWidgetField::FLAG_DISABLED); 132 } 133 elseif (array_key_exists('lefty_min', $this->data)) { 134 $field_lefty_min->setValue($this->data['lefty_min']); 135 } 136 137 $this->fields[$field_lefty_min->getName()] = $field_lefty_min; 138 139 // Max value on left Y axis. 140 $field_lefty_max = (new CWidgetFieldNumericBox('lefty_max', _('Max'))) 141 ->setPlaceholder(_('calculated')) 142 ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH); 143 144 if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) { 145 $field_lefty_max->setFlags(CWidgetField::FLAG_DISABLED); 146 } 147 elseif (array_key_exists('lefty_max', $this->data)) { 148 $field_lefty_max->setValue($this->data['lefty_max']); 149 } 150 151 $this->fields[$field_lefty_max->getName()] = $field_lefty_max; 152 153 // Specify the type of units on left Y axis. 154 $field_lefty_units = (new CWidgetFieldComboBox('lefty_units', _('Units'), [ 155 SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'), 156 SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method') 157 ])) 158 ->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO) 159 ->setAction('jQuery("#lefty_static_units")'. 160 '.prop("disabled", (jQuery(this).val() != "'.SVG_GRAPH_AXIS_UNITS_STATIC.'"))' 161 ); 162 163 if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW) { 164 $field_lefty_units->setFlags(CWidgetField::FLAG_DISABLED); 165 } 166 elseif (array_key_exists('lefty_units', $this->data)) { 167 $field_lefty_units->setValue($this->data['lefty_units']); 168 } 169 170 $this->fields[$field_lefty_units->getName()] = $field_lefty_units; 171 172 // Static units on left Y axis. 173 $field_lefty_static_units = (new CWidgetFieldTextBox('lefty_static_units', null)) 174 ->setPlaceholder(_('value')) 175 ->setWidth(ZBX_TEXTAREA_TINY_WIDTH); 176 177 if ($field_lefty->getValue() != SVG_GRAPH_AXIS_SHOW 178 || $field_lefty_units->getValue() != SVG_GRAPH_AXIS_UNITS_STATIC) { 179 $field_lefty_static_units->setFlags(CWidgetField::FLAG_DISABLED); 180 } 181 elseif (array_key_exists('lefty_static_units', $this->data)) { 182 $field_lefty_static_units->setValue($this->data['lefty_static_units']); 183 } 184 185 $this->fields[$field_lefty_static_units->getName()] = $field_lefty_static_units; 186 187 // Show right Y axis. 188 $field_righty = (new CWidgetFieldCheckBox('righty', _('Right Y'), _('Show'))) 189 ->setDefault(SVG_GRAPH_AXIS_SHOW) 190 ->setAction('onRightYChange()'); 191 192 if (array_key_exists('righty', $this->data)) { 193 $field_righty->setValue($this->data['righty']); 194 } 195 196 $this->fields[$field_righty->getName()] = $field_righty; 197 198 // Min value on right Y axis. 199 $field_righty_min = (new CWidgetFieldNumericBox('righty_min', _('Min'))) 200 ->setPlaceholder(_('calculated')) 201 ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH); 202 203 if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) { 204 $field_righty_min->setFlags(CWidgetField::FLAG_DISABLED); 205 } 206 elseif (array_key_exists('righty_min', $this->data)) { 207 $field_righty_min->setValue($this->data['righty_min']); 208 } 209 210 $this->fields[$field_righty_min->getName()] = $field_righty_min; 211 212 // Max value on right Y axis. 213 $field_righty_max = (new CWidgetFieldNumericBox('righty_max', _('Max'))) 214 ->setPlaceholder(_('calculated')) 215 ->setWidth(ZBX_TEXTAREA_SMALL_WIDTH); 216 217 if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) { 218 $field_righty_max->setFlags(CWidgetField::FLAG_DISABLED); 219 } 220 elseif (array_key_exists('righty_max', $this->data)) { 221 $field_righty_max->setValue($this->data['righty_max']); 222 } 223 224 $this->fields[$field_righty_max->getName()] = $field_righty_max; 225 226 // Specify the type of units on right Y axis. 227 $field_righty_units = (new CWidgetFieldComboBox('righty_units', _('Units'), [ 228 SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'), 229 SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method') 230 ])) 231 ->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO) 232 ->setAction('jQuery("#righty_static_units")'. 233 '.prop("disabled", (jQuery(this).val() != "'.SVG_GRAPH_AXIS_UNITS_STATIC.'"))' 234 ); 235 236 if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW) { 237 $field_righty_units->setFlags(CWidgetField::FLAG_DISABLED); 238 } 239 elseif (array_key_exists('righty_units', $this->data)) { 240 $field_righty_units->setValue($this->data['righty_units']); 241 } 242 243 $this->fields[$field_righty_units->getName()] = $field_righty_units; 244 245 // Static units on right Y axis. 246 $field_righty_static_units = (new CWidgetFieldTextBox('righty_static_units', null)) 247 ->setPlaceholder(_('value')) 248 ->setWidth(ZBX_TEXTAREA_TINY_WIDTH); 249 250 if ($field_righty->getValue() != SVG_GRAPH_AXIS_SHOW 251 || $field_righty_units->getValue() != SVG_GRAPH_AXIS_UNITS_STATIC) { 252 $field_righty_static_units->setFlags(CWidgetField::FLAG_DISABLED); 253 } 254 elseif (array_key_exists('righty_static_units', $this->data)) { 255 $field_righty_static_units->setValue($this->data['righty_static_units']); 256 } 257 258 $this->fields[$field_righty_static_units->getName()] = $field_righty_static_units; 259 260 // Show X axis. 261 $field_axisx = (new CWidgetFieldCheckBox('axisx', _('X-Axis'), _('Show')))->setDefault(SVG_GRAPH_AXIS_SHOW); 262 263 if (array_key_exists('axisx', $this->data)) { 264 $field_axisx->setValue($this->data['axisx']); 265 } 266 267 $this->fields[$field_axisx->getName()] = $field_axisx; 268 269 /** 270 * Legend tab. 271 * 272 * Contains check-box field to show/hide legend and field to specify number of lines in which legend is shown. 273 */ 274 // Show legend. 275 $field_legend = (new CWidgetFieldCheckBox('legend', _('Show legend'))) 276 ->setAction('jQuery("[name=legend_lines]").rangeControl('. 277 'jQuery(this).is(":checked") ? "enable" : "disable"'. 278 ');') 279 ->setDefault(SVG_GRAPH_LEGEND_TYPE_SHORT); 280 281 if (array_key_exists('legend', $this->data)) { 282 $field_legend->setValue($this->data['legend']); 283 } 284 285 $this->fields[$field_legend->getName()] = $field_legend; 286 287 // Number of lines. 288 $field_legend_lines = (new CWidgetFieldRangeControl('legend_lines', _('Number of rows'), 289 SVG_GRAPH_LEGEND_LINES_MIN, SVG_GRAPH_LEGEND_LINES_MAX 290 )) 291 ->setDefault(SVG_GRAPH_LEGEND_LINES_MIN); 292 293 if ($field_legend->getValue() == SVG_GRAPH_LEGEND_TYPE_NONE) { 294 $field_legend_lines->setFlags(CWidgetField::FLAG_DISABLED); 295 } 296 if (array_key_exists('legend_lines', $this->data)) { 297 $field_legend_lines->setValue($this->data['legend_lines']); 298 } 299 300 $this->fields[$field_legend_lines->getName()] = $field_legend_lines; 301 302 /** 303 * Problems tab. 304 * 305 * Contains fields to configure highlighted problem areas in graph. 306 */ 307 // Checkbox: Selected items only. 308 $field_show_problems = (new CWidgetFieldCheckBox('show_problems', _('Show problems'))) 309 ->setAction( 310 'var on = jQuery(this).is(":checked");'. 311 'jQuery("#graph_item_problems, #problemhosts, #problem_name, #problemhosts_select")'. 312 '.prop("disabled", !on);'. 313 'jQuery("[name=\"severities[]\"]").prop("disabled", !on);'. 314 'jQuery("[name=\"evaltype\"]").prop("disabled", !on);'. 315 'jQuery("input, button", jQuery("#tags_table_tags")).prop("disabled", !on);' 316 ); 317 318 if (array_key_exists('show_problems', $this->data)) { 319 $field_show_problems->setValue($this->data['show_problems']); 320 } 321 322 $this->fields[$field_show_problems->getName()] = $field_show_problems; 323 324 // Checkbox: Selected items only. 325 $field_problems = (new CWidgetFieldCheckBox('graph_item_problems', _('Selected items only'))) 326 ->setDefault(SVG_GRAPH_SELECTED_ITEM_PROBLEMS); 327 328 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 329 $field_problems->setFlags(CWidgetField::FLAG_DISABLED); 330 } 331 elseif (array_key_exists('graph_item_problems', $this->data)) { 332 $field_problems->setValue($this->data['graph_item_problems']); 333 } 334 335 $this->fields[$field_problems->getName()] = $field_problems; 336 337 // Problem hosts. 338 $field_problemhosts = (new CWidgetFieldTextArea('problemhosts', _('Problem hosts'))) 339 ->setPlaceholder(_('host pattern')); 340 341 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 342 $field_problemhosts->setFlags(CWidgetField::FLAG_DISABLED); 343 } 344 elseif (array_key_exists('problemhosts', $this->data)) { 345 $field_problemhosts->setValue($this->data['problemhosts']); 346 } 347 348 $this->fields[$field_problemhosts->getName()] = $field_problemhosts; 349 350 // Severity checkboxes list. 351 $field_severities = (new CWidgetFieldSeverities('severities', _('Severity'))) 352 ->setOrientation(CWidgetFieldSeverities::ORIENTATION_HORIZONTAL); 353 354 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 355 $field_severities->setFlags(CWidgetField::FLAG_DISABLED); 356 } 357 elseif (array_key_exists('severities', $this->data)) { 358 $field_severities->setValue($this->data['severities']); 359 } 360 361 $this->fields[$field_severities->getName()] = $field_severities; 362 363 // Problem name input-text field. 364 $field_problem_name = (new CWidgetFieldTextBox('problem_name', _('Problem'))) 365 ->setPlaceholder(_('problem pattern')); 366 367 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 368 $field_problem_name->setFlags(CWidgetField::FLAG_DISABLED); 369 } 370 elseif (array_key_exists('problem_name', $this->data)) { 371 $field_problem_name->setValue($this->data['problem_name']); 372 } 373 374 $this->fields[$field_problem_name->getName()] = $field_problem_name; 375 376 // Problem tag evalype (And/Or). 377 $field_evaltype = (new CWidgetFieldRadioButtonList('evaltype', _('Tags'), [ 378 TAG_EVAL_TYPE_AND_OR => _('And/Or'), 379 TAG_EVAL_TYPE_OR => _('Or') 380 ])) 381 ->setDefault(TAG_EVAL_TYPE_AND_OR) 382 ->setModern(true); 383 384 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 385 $field_evaltype->setFlags(CWidgetField::FLAG_DISABLED); 386 } 387 elseif (array_key_exists('evaltype', $this->data)) { 388 $field_evaltype->setValue($this->data['evaltype']); 389 } 390 391 $this->fields[$field_evaltype->getName()] = $field_evaltype; 392 393 // Problem tags field. 394 $field_tags = new CWidgetFieldTags('tags', ''); 395 396 if ($field_show_problems->getValue() != SVG_GRAPH_PROBLEMS_SHOW) { 397 $field_tags->setFlags(CWidgetField::FLAG_DISABLED); 398 } 399 elseif (array_key_exists('tags', $this->data)) { 400 $field_tags->setValue($this->data['tags']); 401 } 402 403 $this->fields[$field_tags->getName()] = $field_tags; 404 405 /** 406 * Overrides tab. 407 * 408 * Contains single field for override configuration. 409 */ 410 $field_or = (new CWidgetFieldGraphOverride('or', _('Overrides')))->setFlags(CWidgetField::FLAG_NOT_EMPTY); 411 412 if (array_key_exists('or', $this->data)) { 413 $field_or->setValue($this->data['or']); 414 } 415 416 $this->fields[$field_or->getName()] = $field_or; 417 } 418 419 /** 420 * Validate "from" and "to" parameters for allowed period. 421 * 422 * @param string $from 423 * @param string $to 424 * 425 * @return array 426 */ 427 private static function validateTimeSelectorPeriod($from, $to) { 428 $errors = []; 429 $ts = []; 430 $range_time_parser = new CRangeTimeParser(); 431 432 foreach (['from' => $from, 'to' => $to] as $field => $value) { 433 $range_time_parser->parse($value); 434 $ts[$field] = $range_time_parser->getDateTime($field === 'from')->getTimestamp(); 435 } 436 437 $period = $ts['to'] - $ts['from'] + 1; 438 439 if ($period < ZBX_MIN_PERIOD) { 440 $errors[] = _n('Minimum time period to display is %1$s minute.', 441 'Minimum time period to display is %1$s minutes.', (int) (ZBX_MIN_PERIOD / SEC_PER_MIN) 442 ); 443 } 444 elseif ($period > ZBX_MAX_PERIOD) { 445 $errors[] = _n('Maximum time period to display is %1$s day.', 446 'Maximum time period to display is %1$s days.', (int) (ZBX_MAX_PERIOD / SEC_PER_DAY) 447 ); 448 } 449 450 return $errors; 451 } 452 453 /** 454 * Validate form fields. 455 * 456 * @param bool $strict Enables more strict validation of the form fields. 457 * Must be enabled for validation of input parameters in the widget configuration form. 458 * 459 * @return bool 460 */ 461 public function validate($strict = false) { 462 $errors = parent::validate($strict); 463 464 // Test graph custom time period. 465 if ($this->fields['graph_time']->getValue() == SVG_GRAPH_CUSTOM_TIME) { 466 $errors = array_merge($errors, self::validateTimeSelectorPeriod($this->fields['time_from']->getValue(), 467 $this->fields['time_to']->getValue() 468 )); 469 } 470 471 // Validate Min/Max values in Axes tab. 472 if ($this->fields['lefty']->getValue() == SVG_GRAPH_AXIS_SHOW) { 473 $lefty_min = $this->fields['lefty_min']->getValue(); 474 $lefty_max = $this->fields['lefty_max']->getValue(); 475 $lefty_min = ($lefty_min !== '') ? convertFunctionValue($lefty_min, ZBX_UNITS_ROUNDOFF_LOWER_LIMIT) : ''; 476 $lefty_max = ($lefty_max !== '') ? convertFunctionValue($lefty_max, ZBX_UNITS_ROUNDOFF_LOWER_LIMIT) : ''; 477 $compare = true; 478 479 if (strlen(substr(strrchr($lefty_min, '.'), 1)) > 4) { 480 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Min'), _('too many decimal places')); 481 $compare = false; 482 } 483 if (strlen(substr(strrchr($lefty_max, '.'), 1)) > 4) { 484 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Max'), _('too many decimal places')); 485 $compare = false; 486 } 487 488 if ($compare && $lefty_min !== '' && $lefty_max !== '' 489 && bccomp($lefty_min, $lefty_max, 4) >= 0) { 490 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Max'), 491 _('Y axis MAX value must be greater than Y axis MIN value') 492 ); 493 } 494 } 495 496 if ($this->fields['righty']->getValue() == SVG_GRAPH_AXIS_SHOW) { 497 $righty_min = $this->fields['righty_min']->getValue(); 498 $righty_max = $this->fields['righty_max']->getValue(); 499 $righty_min = ($righty_min != '') ? convertFunctionValue($righty_min, ZBX_UNITS_ROUNDOFF_LOWER_LIMIT) : ''; 500 $righty_max = ($righty_max != '') ? convertFunctionValue($righty_max, ZBX_UNITS_ROUNDOFF_LOWER_LIMIT) : ''; 501 $compare = true; 502 503 if (strlen(substr(strrchr($righty_min, '.'), 1)) > 4) { 504 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Min'), _('too many decimal places')); 505 $compare = false; 506 } 507 if (strlen(substr(strrchr($righty_max, '.'), 1)) > 4) { 508 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Max'), _('too many decimal places')); 509 $compare = false; 510 } 511 512 if ($compare && $righty_min !== '' && $righty_max !== '' 513 && bccomp($righty_min, $righty_max, 4) >= 0) { 514 $errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Max'), 515 _('Y axis MAX value must be greater than Y axis MIN value') 516 ); 517 } 518 } 519 520 return $errors; 521 } 522 523 /** 524 * Check if widget configuration is set to use overridden time. 525 * 526 * @param array $fields Widget configuration fields. 527 * @param int $fields['graph_time'] (optional) 528 * 529 * @return bool 530 */ 531 public static function hasOverrideTime($fields) { 532 return (array_key_exists('graph_time', $fields) && $fields['graph_time'] == SVG_GRAPH_CUSTOM_TIME); 533 } 534} 535