<?php /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ class CMultiSelect extends CTag { /** * Default CSS class name for HTML root element. */ const ZBX_STYLE_CLASS = 'multiselect-control'; /** * Search method used for autocomplete requests. */ const SEARCH_METHOD = 'multiselect.get'; const FILTER_PRESELECT_ACCEPT_ID = 'id'; /** * @var array */ protected $params = []; /** * @param array $options['objectOptions'] An array of parameters to be added to the request URL. * @param bool $options['multiple'] Allows multiple selections. * @param bool $options['add_post_js'] * * @see jQuery.multiSelect() */ public function __construct(array $options = []) { parent::__construct('div', true); $options = $this->mapOptions($options); $this ->setId(zbx_formatDomId($options['name'])) ->addClass('multiselect') ->setAttribute('role', 'application') ->addItem((new CDiv()) ->setAttribute('aria-live', 'assertive') ->setAttribute('aria-atomic', 'true') ); if (array_key_exists('disabled', $options) && $options['disabled']) { $this->setAttribute('aria-disabled', 'true'); } if (array_key_exists('readonly', $options) && $options['readonly']) { $this->setAttribute('aria-readonly', 'true'); } $this->params = [ 'name' => $options['name'], 'labels' => [ 'No matches found' => _('No matches found'), 'More matches found...' => _('More matches found...'), 'type here to search' => _('type here to search'), 'new' => _('new'), 'Select' => _('Select') ] ]; if (array_key_exists('object_name', $options)) { // Autocomplete url. $url = (new CUrl('jsrpc.php')) ->setArgument('type', PAGE_TYPE_TEXT_RETURN_JSON) ->setArgument('method', static::SEARCH_METHOD) ->setArgument('object_name', $options['object_name']); if (array_key_exists('objectOptions', $options)) { foreach ($options['objectOptions'] as $option_name => $option_value) { $url->setArgument($option_name, $option_value); } } $this->params['url'] = $url->getUrl(); } if (array_key_exists('multiselect_id', $options)) { $this->params['multiselect_id'] = $options['multiselect_id']; } if (array_key_exists('data', $options)) { $this->params['data'] = zbx_cleanHashes($options['data']); } foreach (['defaultValue', 'disabled', 'selectedLimit', 'addNew', 'styles', 'placeholder', 'hidden', 'readonly'] as $option) { if (array_key_exists($option, $options)) { $this->params[$option] = $options[$option]; } } if (array_key_exists('autosuggest', $options) && array_key_exists('filter_preselect', $options['autosuggest'])) { $this->params['autosuggest']['filter_preselect'] = $options['autosuggest']['filter_preselect']; } if (array_key_exists('custom_select', $options)) { $this->params['custom_select'] = $options['custom_select']; } elseif (array_key_exists('popup', $options)) { if (array_key_exists('filter_preselect', $options['popup'])) { $this->params['popup']['filter_preselect'] = $options['popup']['filter_preselect']; } if (array_key_exists('parameters', $options['popup'])) { $this->params['popup']['parameters'] = $options['popup']['parameters']; } } $this->setAttribute('data-params', $this->params); if (!array_key_exists('add_post_js', $options) || $options['add_post_js']) { zbx_add_post_js($this->getPostJS()); } } public function setWidth($value) { $this->addStyle('width: '.$value.'px;'); return $this; } public function getParams(): array { return $this->params; } public function getPostJS() { return 'jQuery("#'.$this->getAttribute('id').'").multiSelect();'; } /** * Multiselect options mapper for backward compatibility. * * @param array $options * * @return array */ protected function mapOptions(array $options) { $valid_fields = ['name', 'object_name', 'multiselect_id', 'multiple', 'disabled', 'default_value', 'data', 'add_new', 'add_post_js', 'styles', 'popup', 'custom_select', 'placeholder', 'autosuggest', 'hidden', 'readonly' ]; foreach ($options as $field => $value) { if (!in_array($field, $valid_fields)) { error('unsupported multiselect option: $options[\''.$field.'\']'); } } $mapped_options = []; $mappings = [ 'name' => 'name', 'object_name' => 'object_name', 'multiselect_id' => 'multiselect_id', 'disabled' => 'disabled', 'hidden' => 'hidden', 'default_value' => 'defaultValue', 'data' => 'data', 'add_new' => 'addNew', 'add_post_js' => 'add_post_js', 'styles' => 'styles', 'placeholder' => 'placeholder', 'readonly' => 'readonly' ]; foreach ($mappings as $new_field => $old_field) { if (array_key_exists($new_field, $options)) { $mapped_options[$old_field] = $options[$new_field]; } } $multiple = array_key_exists('multiple', $options) ? $options['multiple'] : true; if (!$multiple) { $mapped_options['selectedLimit'] = '1'; } if (array_key_exists('autosuggest', $options)) { $valid_fields = ['filter_preselect']; foreach (array_keys($options['autosuggest']) as $field) { if (!in_array($field, $valid_fields)) { error('unsupported option: $options[\'autosuggest\'][\''.$field.'\']'); } } if (array_key_exists('filter_preselect', $options['autosuggest'])) { if (is_array($options['autosuggest']['filter_preselect'])) { if (self::validateFilterPreselect($options['autosuggest']['filter_preselect'], '$options[\'autosuggest\'][\'filter_preselect\']')) { $mapped_options['autosuggest']['filter_preselect'] = $options['autosuggest']['filter_preselect']; } } else { error('invalid property: $options[\'autosuggest\'][\'filter_preselect\']'); } } } $autocomplete_parameters = []; if (array_key_exists('custom_select', $options)) { $mapped_options['custom_select'] = true; } elseif (array_key_exists('popup', $options)) { $valid_fields = ['parameters', 'filter_preselect']; foreach (array_keys($options['popup']) as $field) { if (!in_array($field, $valid_fields)) { error('unsupported option: $options[\'popup\'][\''.$field.'\']'); } } if (array_key_exists('filter_preselect', $options['popup'])) { if (is_array($options['popup']['filter_preselect'])) { if (self::validateFilterPreselect($options['popup']['filter_preselect'], '$options[\'popup\'][\'filter_preselect\']')) { $mapped_options['popup']['filter_preselect'] = $options['popup']['filter_preselect']; } } else { error('invalid property: $options[\'popup\'][\'filter_preselect\']'); } } $popup_parameters = []; if (array_key_exists('parameters', $options['popup'])) { $parameters = $options['popup']['parameters']; $valid_fields = ['srctbl', 'srcfld1', 'srcfld2', 'dstfrm', 'dstfld1', 'real_hosts', 'with_hosts', 'monitored_hosts', 'with_monitored_triggers', 'editable', 'templated_hosts', 'with_templates', 'hostid', 'parent_discoveryid', 'normal_only', 'numeric', 'with_graphs', 'with_graph_prototypes', 'with_items', 'with_simple_graph_items', 'with_simple_graph_item_prototypes', 'with_triggers', 'value_types', 'excludeids', 'disableids', 'enrich_parent_groups', 'with_monitored_items', 'with_httptests', 'user_type', 'disable_selected', 'hostids', 'with_inherited', 'context', 'enabled_only', 'group_status', 'hide_host_filter', 'resolve_macros', 'exclude_provisioned' ]; foreach ($parameters as $field => $value) { if (!in_array($field, $valid_fields)) { error('unsupported option: $options[\'popup\'][\'parameters\'][\''.$field.'\']'); } } $mappings = [ 'srctbl' => 'srctbl', 'srcfld1' => 'srcfld1', 'srcfld2' => 'srcfld2', 'dstfrm' => 'dstfrm', 'dstfld1' => 'dstfld1' ]; foreach ($mappings as $new_field => $old_field) { if (array_key_exists($new_field, $parameters)) { $popup_parameters[$old_field] = $parameters[$new_field]; } } if ($multiple) { $popup_parameters['multiselect'] = '1'; } if (array_key_exists('hostid', $parameters) && $parameters['hostid'] > 0) { $popup_parameters['only_hostid'] = (string) $parameters['hostid']; $autocomplete_parameters['hostid'] = (string) $parameters['hostid']; } if (array_key_exists('hide_host_filter', $parameters)) { $popup_parameters['hide_host_filter'] = '1'; } if (array_key_exists('groupid', $parameters) && $parameters['groupid'] > 0) { $popup_parameters['groupid'] = (string) $parameters['groupid']; } if (array_key_exists('parent_discoveryid', $parameters) && $parameters['parent_discoveryid'] > 0) { $popup_parameters['parent_discoveryid'] = $parameters['parent_discoveryid']; $autocomplete_parameters['parent_discoveryid'] = $parameters['parent_discoveryid']; } if (array_key_exists('numeric', $parameters) && $parameters['numeric']) { $popup_parameters['numeric'] = '1'; $autocomplete_parameters['filter']['value_type'] = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64]; } if (array_key_exists('normal_only', $parameters) && $parameters['normal_only']) { $popup_parameters['normal_only'] = '1'; $autocomplete_parameters['filter']['flags'] = ZBX_FLAG_DISCOVERY_NORMAL; } if (array_key_exists('value_types', $parameters)) { $popup_parameters['value_types'] = $parameters['value_types']; $autocomplete_parameters['filter']['value_type'] = $parameters['value_types']; } if (array_key_exists('real_hosts', $parameters) && $parameters['real_hosts']) { $popup_parameters['real_hosts'] = '1'; $autocomplete_parameters['real_hosts'] = true; } if (array_key_exists('with_hosts', $parameters) && $parameters['with_hosts']) { $popup_parameters['real_hosts'] = '1'; $autocomplete_parameters['with_hosts'] = true; } if (array_key_exists('templated_hosts', $parameters) && $parameters['templated_hosts']) { $popup_parameters['templated_hosts'] = '1'; $autocomplete_parameters['templated_hosts'] = true; } if ($popup_parameters['srctbl'] == 'template_triggers') { $autocomplete_parameters['templated'] = true; } if (array_key_exists('with_templates', $parameters) && $parameters['with_templates']) { $popup_parameters['templated_hosts'] = '1'; $autocomplete_parameters['with_templates'] = true; } foreach (['with_graphs', 'with_graph_prototypes', 'with_simple_graph_items', 'with_simple_graph_item_prototypes', 'with_triggers', 'with_inherited'] as $name) { if (array_key_exists($name, $parameters) && $parameters[$name]) { $popup_parameters[$name] = '1'; $autocomplete_parameters[$name] = true; } } if (array_key_exists('editable', $parameters) && $parameters['editable']) { $popup_parameters['writeonly'] = '1'; $autocomplete_parameters['editable'] = true; } if (array_key_exists('monitored_hosts', $parameters) && $parameters['monitored_hosts']) { $popup_parameters['monitored_hosts'] = '1'; $autocomplete_parameters['monitored'] = true; } if (array_key_exists('with_monitored_triggers', $parameters) && $parameters['with_monitored_triggers']) { $popup_parameters['with_monitored_triggers'] = '1'; if ($popup_parameters['srctbl'] === 'triggers') { $autocomplete_parameters['monitored'] = true; } else { $autocomplete_parameters['with_monitored_triggers'] = true; } } if (array_key_exists('with_monitored_items', $parameters) && $parameters['with_monitored_items']) { $popup_parameters['with_monitored_items'] = '1'; $autocomplete_parameters['with_monitored_items'] = true; } if (array_key_exists('with_httptests', $parameters) && $parameters['with_httptests']) { $popup_parameters['with_httptests'] = '1'; $autocomplete_parameters['with_httptests'] = true; } if (array_key_exists('with_items', $parameters) && $parameters['with_items']) { $popup_parameters['with_items'] = '1'; $autocomplete_parameters['with_items'] = true; } if (array_key_exists('excludeids', $parameters) && $parameters['excludeids']) { $popup_parameters['excludeids'] = $parameters['excludeids']; } if (array_key_exists('disableids', $parameters) && $parameters['disableids']) { $popup_parameters['disableids'] = $parameters['disableids']; } if (array_key_exists('enrich_parent_groups', $parameters) && $parameters['enrich_parent_groups']) { $popup_parameters['enrich_parent_groups'] = '1'; $autocomplete_parameters['enrich_parent_groups'] = true; } if (array_key_exists('user_type', $parameters) && $parameters['user_type']) { $popup_parameters['user_type'] = (int) $parameters['user_type']; $autocomplete_parameters['user_type'] = (int) $parameters['user_type']; } if (array_key_exists('disable_selected', $parameters) && $parameters['disable_selected']) { $popup_parameters['disable_selected'] = '1'; } if (array_key_exists('hostids', $parameters) && $parameters['hostids']) { $popup_parameters['hostids'] = $parameters['hostids']; $autocomplete_parameters['hostids'] = $parameters['hostids']; } if (array_key_exists('context', $parameters)) { $popup_parameters['context'] = $parameters['context']; $autocomplete_parameters['context'] = $parameters['context']; } if (array_key_exists('enabled_only', $parameters) && $parameters['enabled_only']) { $popup_parameters['enabled_only'] = '1'; $autocomplete_parameters['enabled_only'] = true; } if (array_key_exists('group_status', $parameters)) { $popup_parameters['group_status'] = $parameters['group_status']; $autocomplete_parameters['group_status'] = $parameters['group_status']; } if (array_key_exists('resolve_macros', $parameters) && $parameters['resolve_macros']) { $popup_parameters['resolve_macros'] = '1'; $autocomplete_parameters['resolve_macros'] = true; } if (array_key_exists('exclude_provisioned', $parameters) && $parameters['exclude_provisioned']) { $popup_parameters['exclude_provisioned'] = 1; $autocomplete_parameters['exclude_provisioned'] = 1; } } $mapped_options['popup']['parameters'] = $popup_parameters; } $mapped_options['objectOptions'] = $autocomplete_parameters; return $mapped_options; } protected static function validateFilterPreselect(array $field, string $path): bool { $is_valid = true; foreach (array_keys($field) as $option) { if (!in_array($option, ['id', 'accept', 'submit_as', 'submit_parameters', 'multiple'])) { error('unsupported option: '.$path.'[\''.$option.'\']'); $is_valid = false; } } if (!array_key_exists('id', $field) || !is_string($field['id']) || $field['id'] === '') { error('invalid property: '.$path.'[\'id\']'); $is_valid = false; } if (array_key_exists('accept', $field) && $field['accept'] !== self::FILTER_PRESELECT_ACCEPT_ID) { error('invalid property: '.$path.'[\'accept\']'); $is_valid = false; } if (!array_key_exists('submit_as', $field) || !is_string($field['submit_as']) || $field['submit_as'] === '') { error('invalid property: '.$path.'[\'submit_as\']'); $is_valid = false; } if (array_key_exists('submit_parameters', $field) && !is_array($field['submit_parameters'])) { error('invalid property: '.$path.'[\'submit_parameters\']'); $is_valid = false; } if (array_key_exists('multiple', $field) && !is_bool($field['multiple'])) { error('invalid property: '.$path.'[\'multiple\']'); $is_valid = false; } return $is_valid; } }