You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
474 lines
16 KiB
474 lines
16 KiB
1 year ago
|
<?php declare(strict_types = 0);
|
||
|
/*
|
||
|
** Zabbix
|
||
|
** Copyright (C) 2001-2023 Zabbix SIA
|
||
|
**
|
||
|
** This program is free software; you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation; either version 2 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program is distributed in the hope that it will be useful,
|
||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
** GNU General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program; if not, write to the Free Software
|
||
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
**/
|
||
|
|
||
|
|
||
|
namespace Widgets\SvgGraph\Includes;
|
||
|
|
||
|
use CNumberParser,
|
||
|
CParser,
|
||
|
CRangeTimeParser,
|
||
|
CSettingsHelper;
|
||
|
|
||
|
use Zabbix\Widgets\{
|
||
|
CWidgetField,
|
||
|
CWidgetForm
|
||
|
};
|
||
|
|
||
|
use Zabbix\Widgets\Fields\{
|
||
|
CWidgetFieldCheckBox,
|
||
|
CWidgetFieldDatePicker,
|
||
|
CWidgetFieldGraphDataSet,
|
||
|
CWidgetFieldGraphOverride,
|
||
|
CWidgetFieldHostPatternSelect,
|
||
|
CWidgetFieldNumericBox,
|
||
|
CWidgetFieldRadioButtonList,
|
||
|
CWidgetFieldRangeControl,
|
||
|
CWidgetFieldSelect,
|
||
|
CWidgetFieldSeverities,
|
||
|
CWidgetFieldTags,
|
||
|
CWidgetFieldTextBox
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Graph widget form view.
|
||
|
*/
|
||
|
class WidgetForm extends CWidgetForm {
|
||
|
|
||
|
private const PERCENTILE_MIN = 1;
|
||
|
private const PERCENTILE_MAX = 100;
|
||
|
|
||
|
private bool $percentile_left_on = false;
|
||
|
private bool $percentile_right_on = false;
|
||
|
|
||
|
private bool $graph_time_on = false;
|
||
|
|
||
|
private bool $lefty_on = true;
|
||
|
private bool $lefty_units_static = false;
|
||
|
private bool $righty_on = true;
|
||
|
private bool $righty_units_static = false;
|
||
|
|
||
|
private bool $legend_on = true;
|
||
|
private bool $legend_statistic_on = false;
|
||
|
|
||
|
private bool $problems_on = false;
|
||
|
|
||
|
public function validate(bool $strict = false): array {
|
||
|
$errors = parent::validate($strict);
|
||
|
|
||
|
$number_parser_w_suffix = new CNumberParser(['with_size_suffix' => true, 'with_time_suffix' => true]);
|
||
|
$number_parser_wo_suffix = new CNumberParser();
|
||
|
|
||
|
// Percentiles
|
||
|
if ($this->getFieldValue('percentile_left') == SVG_GRAPH_PERCENTILE_LEFT_ON) {
|
||
|
$percentile_left_value = $this->getFieldValue('percentile_left_value');
|
||
|
|
||
|
if ($percentile_left_value !== '') {
|
||
|
$percentile_left_value_calculated =
|
||
|
$number_parser_wo_suffix->parse($percentile_left_value) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_wo_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
if ($percentile_left_value_calculated === null
|
||
|
|| $percentile_left_value_calculated < self::PERCENTILE_MIN
|
||
|
|| $percentile_left_value_calculated > self::PERCENTILE_MAX) {
|
||
|
$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Percentile line (left)'),
|
||
|
_s('value must be between "%1$s" and "%2$s"', self::PERCENTILE_MIN, self::PERCENTILE_MAX)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($this->getFieldValue('percentile_right') == SVG_GRAPH_PERCENTILE_RIGHT_ON) {
|
||
|
$percentile_right_value = $this->getFieldValue('percentile_right_value');
|
||
|
|
||
|
if ($percentile_right_value !== '') {
|
||
|
$percentile_right_value_calculated =
|
||
|
$number_parser_wo_suffix->parse($percentile_right_value) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_wo_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
if ($percentile_right_value_calculated === null
|
||
|
|| $percentile_right_value_calculated < self::PERCENTILE_MIN
|
||
|
|| $percentile_right_value_calculated > self::PERCENTILE_MAX) {
|
||
|
$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Percentile line (right)'),
|
||
|
_s('value must be between "%1$s" and "%2$s"', self::PERCENTILE_MIN, self::PERCENTILE_MAX)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Test graph custom time period.
|
||
|
if ($this->getFieldValue('graph_time') == SVG_GRAPH_CUSTOM_TIME_ON) {
|
||
|
$errors = array_merge($errors, self::validateTimeSelectorPeriod($this->getFieldValue('time_from'),
|
||
|
$this->getFieldValue('time_to')
|
||
|
));
|
||
|
}
|
||
|
|
||
|
// Validate Min/Max values in Axes tab.
|
||
|
if ($this->getFieldValue('lefty') == SVG_GRAPH_AXIS_ON) {
|
||
|
$lefty_min = $number_parser_w_suffix->parse($this->getFieldValue('lefty_min')) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_w_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
$lefty_max = $number_parser_w_suffix->parse($this->getFieldValue('lefty_max')) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_w_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
if ($lefty_min !== null && $lefty_max !== null && $lefty_min >= $lefty_max) {
|
||
|
$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Left Y').'/'._('Max'),
|
||
|
_('Y axis MAX value must be greater than Y axis MIN value')
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($this->getFieldValue('righty') == SVG_GRAPH_AXIS_ON) {
|
||
|
$righty_min = $number_parser_w_suffix->parse($this->getFieldValue('righty_min')) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_w_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
$righty_max = $number_parser_w_suffix->parse($this->getFieldValue('righty_max')) == CParser::PARSE_SUCCESS
|
||
|
? $number_parser_w_suffix->calcValue()
|
||
|
: null;
|
||
|
|
||
|
if ($righty_min !== null && $righty_max !== null && $righty_min >= $righty_max) {
|
||
|
$errors[] = _s('Invalid parameter "%1$s": %2$s.', _('Right Y').'/'._('Max'),
|
||
|
_('Y axis MAX value must be greater than Y axis MIN value')
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $errors;
|
||
|
}
|
||
|
|
||
|
protected function normalizeValues(array $values): array {
|
||
|
$values = parent::normalizeValues($values);
|
||
|
|
||
|
if (array_key_exists('percentile_left', $values)) {
|
||
|
$this->percentile_left_on = $values['percentile_left'] == SVG_GRAPH_PERCENTILE_LEFT_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->percentile_left_on) {
|
||
|
unset($values['percentile_left_value']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('percentile_right', $values)) {
|
||
|
$this->percentile_right_on = $values['percentile_right'] == SVG_GRAPH_PERCENTILE_RIGHT_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->percentile_right_on) {
|
||
|
unset($values['percentile_right_value']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('graph_time', $values)) {
|
||
|
$this->graph_time_on = $values['graph_time'] == SVG_GRAPH_CUSTOM_TIME_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->graph_time_on) {
|
||
|
unset($values['time_from'], $values['time_to']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('lefty', $values)) {
|
||
|
$this->lefty_on = $values['lefty'] == SVG_GRAPH_AXIS_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->lefty_on) {
|
||
|
unset($values['lefty_min'], $values['lefty_max'], $values['lefty_units']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('lefty_units', $values)) {
|
||
|
$this->lefty_units_static = $values['lefty_units'] == SVG_GRAPH_AXIS_UNITS_STATIC;
|
||
|
}
|
||
|
|
||
|
if (!$this->lefty_on || !$this->lefty_units_static) {
|
||
|
unset($values['lefty_static_units']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('righty', $values)) {
|
||
|
$this->righty_on = $values['righty'] == SVG_GRAPH_AXIS_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->righty_on) {
|
||
|
unset($values['righty_min'], $values['righty_max'], $values['righty_units']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('righty_units', $values)) {
|
||
|
$this->righty_units_static = $values['righty_units'] == SVG_GRAPH_AXIS_UNITS_STATIC;
|
||
|
}
|
||
|
|
||
|
if (!$this->righty_on || !$this->righty_units_static) {
|
||
|
unset($values['righty_static_units']);
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('legend', $values)) {
|
||
|
$this->legend_on = $values['legend'] == SVG_GRAPH_LEGEND_ON;
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('legend_statistic', $values)) {
|
||
|
$this->legend_statistic_on = $values['legend_statistic'] == SVG_GRAPH_LEGEND_STATISTIC_ON;
|
||
|
}
|
||
|
|
||
|
if (array_key_exists('show_problems', $values)) {
|
||
|
$this->problems_on = $values['show_problems'] == SVG_GRAPH_PROBLEMS_ON;
|
||
|
}
|
||
|
|
||
|
if (!$this->problems_on) {
|
||
|
unset($values['graph_item_problems'], $values['problemhosts'], $values['severities'],
|
||
|
$values['problem_name'], $values['evaltype'], $values['tags']
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $values;
|
||
|
}
|
||
|
|
||
|
public function addFields(): self {
|
||
|
return $this
|
||
|
->initDataSetFields()
|
||
|
->initDisplayingOptionsFields()
|
||
|
->initTimePeriodFields()
|
||
|
->initAxesFields()
|
||
|
->initLegendFields()
|
||
|
->initProblemsFields()
|
||
|
->initOverridesFields();
|
||
|
}
|
||
|
|
||
|
private function initDataSetFields(): self {
|
||
|
return $this->addField(
|
||
|
(new CWidgetFieldGraphDataSet('ds', _('Data set')))->setFlags(CWidgetField::FLAG_NOT_EMPTY)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initDisplayingOptionsFields(): self {
|
||
|
return $this
|
||
|
->addField(
|
||
|
(new CWidgetFieldRadioButtonList('source', _('History data selection'), [
|
||
|
SVG_GRAPH_DATA_SOURCE_AUTO => _x('Auto', 'history source selection method'),
|
||
|
SVG_GRAPH_DATA_SOURCE_HISTORY => _('History'),
|
||
|
SVG_GRAPH_DATA_SOURCE_TRENDS => _('Trends')
|
||
|
]))->setDefault(SVG_GRAPH_DATA_SOURCE_AUTO)
|
||
|
)
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('simple_triggers', _('Simple triggers'))
|
||
|
)
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('working_time', _('Working time'))
|
||
|
)
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('percentile_left', _('Percentile line (left)'))
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTextBox('percentile_left_value', null))
|
||
|
->setFlags(!$this->percentile_left_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('percentile_right', _('Percentile line (right)'))
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTextBox('percentile_right_value', null))
|
||
|
->setFlags(!$this->percentile_right_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initTimePeriodFields(): self {
|
||
|
return $this
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('graph_time', _('Set custom time period'))
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldDatePicker('time_from', _('From')))
|
||
|
->setDefault('now-1h')
|
||
|
->setFlags($this->graph_time_on
|
||
|
? CWidgetField::FLAG_NOT_EMPTY
|
||
|
: CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_DISABLED
|
||
|
)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldDatePicker('time_to', _('To')))
|
||
|
->setDefault('now')
|
||
|
->setFlags($this->graph_time_on
|
||
|
? CWidgetField::FLAG_NOT_EMPTY
|
||
|
: CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_DISABLED
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initAxesFields(): self {
|
||
|
return $this
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('lefty', _('Left Y'), _('Show')))->setDefault(SVG_GRAPH_AXIS_ON)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldNumericBox('lefty_min', _('Min')))
|
||
|
->setFullName(_('Left Y').'/'._('Min'))
|
||
|
->setFlags(!$this->lefty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldNumericBox('lefty_max', _('Max')))
|
||
|
->setFullName(_('Left Y').'/'._('Max'))
|
||
|
->setFlags(!$this->lefty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldSelect('lefty_units', _('Units'), [
|
||
|
SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'),
|
||
|
SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method')
|
||
|
]))
|
||
|
->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO)
|
||
|
->setFlags(!$this->lefty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTextBox('lefty_static_units'))
|
||
|
->setFlags(!$this->lefty_on || !$this->lefty_units_static ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
->setMaxLength(255)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('righty', _('Right Y'), _('Show')))->setDefault(SVG_GRAPH_AXIS_ON)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldNumericBox('righty_min', _('Min')))
|
||
|
->setFullName(_('Right Y').'/'._('Min'))
|
||
|
->setFlags(!$this->righty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldNumericBox('righty_max', _('Max')))
|
||
|
->setFullName(_('Right Y').'/'._('Max'))
|
||
|
->setFlags(!$this->righty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldSelect('righty_units', _('Units'), [
|
||
|
SVG_GRAPH_AXIS_UNITS_AUTO => _x('Auto', 'history source selection method'),
|
||
|
SVG_GRAPH_AXIS_UNITS_STATIC => _x('Static', 'history source selection method')
|
||
|
]))
|
||
|
->setDefault(SVG_GRAPH_AXIS_UNITS_AUTO)
|
||
|
->setFlags(!$this->righty_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTextBox('righty_static_units', null))
|
||
|
->setFlags(!$this->righty_on || !$this->righty_units_static ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
->setMaxLength(255)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('axisx', _('X-Axis'), _('Show')))->setDefault(SVG_GRAPH_AXIS_ON)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initLegendFields(): self {
|
||
|
return $this
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('legend', _('Show legend')))->setDefault(SVG_GRAPH_LEGEND_ON)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('legend_statistic', _('Display min/max/avg')))
|
||
|
->setFlags(!$this->legend_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldRangeControl('legend_lines', _('Number of rows'),
|
||
|
SVG_GRAPH_LEGEND_LINES_MIN, SVG_GRAPH_LEGEND_LINES_MAX
|
||
|
))
|
||
|
->setDefault(SVG_GRAPH_LEGEND_LINES_MIN)
|
||
|
->setFlags(!$this->legend_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldRangeControl('legend_columns', _('Number of columns'),
|
||
|
SVG_GRAPH_LEGEND_COLUMNS_MIN, SVG_GRAPH_LEGEND_COLUMNS_MAX
|
||
|
))
|
||
|
->setDefault(SVG_GRAPH_LEGEND_COLUMNS_MAX)
|
||
|
->setFlags(!$this->legend_on || $this->legend_statistic_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initProblemsFields(): self {
|
||
|
return $this
|
||
|
->addField(
|
||
|
new CWidgetFieldCheckBox('show_problems', _('Show problems'))
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldCheckBox('graph_item_problems', _('Selected items only')))
|
||
|
->setDefault(SVG_GRAPH_SELECTED_ITEM_PROBLEMS)
|
||
|
->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField($this->isTemplateDashboard()
|
||
|
? null
|
||
|
: (new CWidgetFieldHostPatternSelect('problemhosts', _('Problem hosts')))
|
||
|
->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldSeverities('severities', _('Severity')))
|
||
|
->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTextBox('problem_name', _('Problem')))
|
||
|
->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldRadioButtonList('evaltype', _('Problem tags'), [
|
||
|
TAG_EVAL_TYPE_AND_OR => _('And/Or'),
|
||
|
TAG_EVAL_TYPE_OR => _('Or')
|
||
|
]))
|
||
|
->setDefault(TAG_EVAL_TYPE_AND_OR)
|
||
|
->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
)
|
||
|
->addField(
|
||
|
(new CWidgetFieldTags('tags'))->setFlags(!$this->problems_on ? CWidgetField::FLAG_DISABLED : 0x00)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function initOverridesFields(): self {
|
||
|
return $this->addField(
|
||
|
(new CWidgetFieldGraphOverride('or', _('Overrides')))->setFlags(CWidgetField::FLAG_NOT_EMPTY)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if widget configuration is set to use overridden time.
|
||
|
*/
|
||
|
public static function hasOverrideTime(array $fields_values): bool {
|
||
|
return array_key_exists('graph_time', $fields_values)
|
||
|
&& $fields_values['graph_time'] == SVG_GRAPH_CUSTOM_TIME_ON;
|
||
|
}
|
||
|
|
||
|
private static function validateTimeSelectorPeriod(string $from, string $to): array {
|
||
|
$errors = [];
|
||
|
$ts = [];
|
||
|
$ts['now'] = time();
|
||
|
$range_time_parser = new CRangeTimeParser();
|
||
|
|
||
|
foreach (['from' => $from, 'to' => $to] as $field => $value) {
|
||
|
$range_time_parser->parse($value);
|
||
|
$ts[$field] = $range_time_parser->getDateTime($field === 'from')->getTimestamp();
|
||
|
}
|
||
|
|
||
|
$period = $ts['to'] - $ts['from'] + 1;
|
||
|
$range_time_parser->parse('now-'.CSettingsHelper::get(CSettingsHelper::MAX_PERIOD));
|
||
|
$max_period = 1 + $ts['now'] - $range_time_parser->getDateTime(true)->getTimestamp();
|
||
|
|
||
|
if ($period < ZBX_MIN_PERIOD) {
|
||
|
$errors[] = _n('Minimum time period to display is %1$s minute.',
|
||
|
'Minimum time period to display is %1$s minutes.', (int) (ZBX_MIN_PERIOD / SEC_PER_MIN)
|
||
|
);
|
||
|
}
|
||
|
elseif ($period > $max_period) {
|
||
|
$errors[] = _n('Maximum time period to display is %1$s day.',
|
||
|
'Maximum time period to display is %1$s days.', (int) round($max_period / SEC_PER_DAY)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $errors;
|
||
|
}
|
||
|
}
|