<?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.
**/


require_once dirname(__FILE__).'/../../include/forms.inc.php';

class CControllerPopupMassupdateItem extends CController {

	private $opt_interfaceid_expected = false;

	protected function checkInput() {
		$fields = [
			'allow_traps' => 'in '.implode(',', [HTTPCHECK_ALLOW_TRAPS_ON, HTTPCHECK_ALLOW_TRAPS_OFF]),
			'authtype' => 'string',
			'context' => 'required|string|in host,template',
			'delay' => 'string',
			'delay_flex' => 'array',
			'description' => 'string',
			'discover' => 'in '.ZBX_PROTOTYPE_DISCOVER.','.ZBX_PROTOTYPE_NO_DISCOVER,
			'headers' => 'array',
			'history' => 'string',
			'history_mode' => 'in '.implode(',', [ITEM_STORAGE_OFF, ITEM_STORAGE_CUSTOM]),
			'ids' => 'required|array_id',
			'interfaceid' => 'id',
			'jmx_endpoint' => 'string',
			'logtimefmt' => 'string',
			'mass_update_tags' => 'in '.implode(',', [ZBX_ACTION_ADD, ZBX_ACTION_REPLACE, ZBX_ACTION_REMOVE]),
			'master_itemid' => 'id',
			'parent_discoveryid' => 'id',
			'password' => 'string',
			'post_type' => 'in '.implode(',', [ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]),
			'posts' => 'string',
			'preprocessing' => 'array',
			'privatekey' => 'string',
			'prototype' => 'required|in 0,1',
			'publickey' => 'string',
			'status' => 'in '.implode(',', [ITEM_STATUS_ACTIVE, ITEM_STATUS_DISABLED]),
			'tags' => 'array',
			'trapper_hosts' => 'string',
			'trends' => 'string',
			'trends_mode' => 'in '.implode(',', [ITEM_STORAGE_OFF, ITEM_STORAGE_CUSTOM]),
			'timeout' => 'string',
			'type' => 'int32',
			'units' => 'string',
			'update' => 'in 1',
			'url' => 'string',
			'username' => 'string',
			'value_type' => 'in '.implode(',', [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT]),
			'valuemapid' => 'id',
			'visible' => 'array'
		];

		$this->opt_interfaceid_expected = (getRequest('interfaceid') == INTERFACE_TYPE_OPT);

		if ($this->opt_interfaceid_expected) {
			unset($fields['interfaceid']);
			unset($_REQUEST['interfaceid']);
		}

		$ret = $this->validateInput($fields);

		if ($ret && $this->opt_interfaceid_expected) {
			if ($this->hasInput('type')) {
				$item_types = [$this->getInput('type')];
			}
			else {
				$options = [
					'output' => ['type'],
					'itemids' => $this->getInput('ids')
				];
				$item_types = (bool) $this->getInput('prototype')
					? API::ItemPrototype()->get($options)
					: API::Item()->get($options);

				$item_types = array_column($item_types, 'type', 'type');
			}

			foreach ($item_types as $item_type) {
				if (itemTypeInterface($item_type) != INTERFACE_TYPE_OPT) {
					error(_s('Incorrect value for field "%1$s": %2$s.', _('Host interface'),
						interfaceType2str(INTERFACE_TYPE_OPT)
					));
					$ret = false;

					break;
				}
			}
		}

		if (!$ret) {
			$this->setResponse(
				(new CControllerResponseData(['main_block' => json_encode([
					'error' => [
						'messages' => array_column(get_and_clear_messages(), 'message')
					]
				])]))->disableView()
			);
		}

		return $ret;
	}

	protected function checkPermissions() {
		$entity = ($this->getInput('prototype') == 1) ? API::ItemPrototype() : API::Item();

		return (bool) $entity->get([
			'output' => [],
			'itemids' => $this->getInput('ids'),
			'editable' => true,
			'limit' => 1
		]);
	}

	protected function doAction() {
		$this->setResponse($this->hasInput('update') ? $this->update() : $this->form());
	}

	/**
	 * Get array of updated items or item prototypes.
	 *
	 * @return array
	 */
	protected function getItemsOrPrototypes(): array {
		$options = [
			'output' => ['itemid', 'type'],
			'selectTags' => ['tag', 'value'],
			'itemids' => $this->getInput('ids'),
			'preservekeys' => true
		];

		if ($this->getInput('prototype')) {
			$result = API::ItemPrototype()->get($options);
		}
		else {
			$options['output'][] = 'flags';
			$result = API::Item()->get($options);
		}

		return $result;
	}

	/**
	 * Update item or item prototype, return update action status.
	 *
	 * @param array $data  Array of item or item prototypes data to update.
	 * @return bool
	 */
	protected function updateItemOrPrototype(array $data): bool {
		return (bool) ($this->getInput('prototype') ? API::ItemPrototype()->update($data) : API::Item()->update($data));
	}

	/**
	 * Handle item mass update action.
	 *
	 * @return CControllerResponse
	 */
	protected function update(): CControllerResponse {
		$result = true;
		$ids = $this->getInput('ids');
		$prototype = (bool) $this->getInput('prototype');
		$input = [
			'allow_traps' => HTTPCHECK_ALLOW_TRAPS_OFF,
			'authtype' => '',
			'delay' => DB::getDefault('items', 'delay'),
			'description' => '',
			'discover' => ZBX_PROTOTYPE_DISCOVER,
			'headers' => [],
			'history' => ITEM_NO_STORAGE_VALUE,
			'jmx_endpoint' => '',
			'logtimefmt' => '',
			'master_itemid' => 0,
			'password' => '',
			'post_type' => ZBX_POSTTYPE_RAW,
			'posts' => '',
			'preprocessing' => [],
			'privatekey' => '',
			'publickey' => '',
			'status' => ITEM_STATUS_ACTIVE,
			'tags' => [],
			'timeout' => '',
			'trapper_hosts' => '',
			'trends' => ITEM_NO_STORAGE_VALUE,
			'type' => 0,
			'units' => '',
			'url' => '',
			'username' => '',
			'value_type' => ITEM_VALUE_TYPE_UINT64,
			'valuemapid' => 0,
			'interfaceid' => $this->opt_interfaceid_expected ? 0 : ''
		];
		$this->getInputs($input, array_keys($input));

		if ($this->getInput('trends_mode', ITEM_STORAGE_CUSTOM) == ITEM_STORAGE_OFF) {
			$input['trends'] = ITEM_NO_STORAGE_VALUE;
		}

		if ($this->getInput('history_mode', ITEM_STORAGE_CUSTOM) == ITEM_STORAGE_OFF) {
			$input['history'] = ITEM_NO_STORAGE_VALUE;
		}

		$input = array_intersect_key($input, $this->getInput('visible', []));

		if (array_key_exists('tags', $input)) {
			$input['tags'] = array_filter($input['tags'], function ($tag) {
				return ($tag['tag'] !== '' || $tag['value'] !== '');
			});
		}

		try {
			DBstart();
			$delay_flex = $this->getInput('delay_flex', []);

			if (array_key_exists('delay', $input) && $delay_flex) {
				$simple_interval_parser = new CSimpleIntervalParser(['usermacros' => true]);
				$time_period_parser = new CTimePeriodParser(['usermacros' => true]);
				$scheduling_interval_parser = new CSchedulingIntervalParser(['usermacros' => true]);

				foreach ($delay_flex as $interval) {
					if ($interval['type'] == ITEM_DELAY_FLEXIBLE) {
						if ($interval['delay'] === '' && $interval['period'] === '') {
							continue;
						}

						if ($simple_interval_parser->parse($interval['delay']) != CParser::PARSE_SUCCESS) {
							info(_s('Invalid interval "%1$s".', $interval['delay']));
							throw new Exception();
						}
						elseif ($time_period_parser->parse($interval['period']) != CParser::PARSE_SUCCESS) {
							info(_s('Invalid interval "%1$s".', $interval['period']));
							throw new Exception();
						}

						$input['delay'] .= ';'.$interval['delay'].'/'.$interval['period'];
					}
					else {
						if ($interval['schedule'] === '') {
							continue;
						}

						if ($scheduling_interval_parser->parse($interval['schedule']) != CParser::PARSE_SUCCESS) {
							info(_s('Invalid interval "%1$s".', $interval['schedule']));
							throw new Exception();
						}

						$input['delay'] .= ';'.$interval['schedule'];
					}
				}
			}

			if (array_key_exists('headers', $input) && $input['headers']) {
				$input['headers']['value'] += array_fill_keys(array_keys($input['headers']['name']), '');

				$headers = [];
				foreach ($input['headers']['name'] as $i => $header_name) {
					if ($header_name !== '' || $input['headers']['value'][$i] !== '') {
						$headers[$header_name] = $input['headers']['value'][$i];
					}
				}
				$input['headers'] = $headers;
			}

			if (array_key_exists('preprocessing', $input) && $input['preprocessing']) {
				$input['preprocessing'] = normalizeItemPreprocessingSteps($input['preprocessing']);
			}

			$items_to_update = [];
			$items = $this->getItemsOrPrototypes();

			foreach ($ids as $id) {
				$update_item = [];

				if (array_key_exists('tags', $input)) {
					switch ($this->getInput('mass_update_tags', ZBX_ACTION_ADD)) {
						case ZBX_ACTION_ADD:
							$unique_tags = [];
							foreach (array_merge($items[$id]['tags'], $input['tags']) as $tag) {
								$unique_tags[$tag['tag']][$tag['value']] = $tag;
							}

							foreach ($unique_tags as $tags_by_name) {
								foreach ($tags_by_name as $tag) {
									$update_item['tags'][] = $tag;
								}
							}
							break;

						case ZBX_ACTION_REPLACE:
							$update_item['tags'] = $input['tags'];
							break;

						case ZBX_ACTION_REMOVE:
							$diff_tags = [];
							foreach ($items[$id]['tags'] as $a) {
								foreach ($input['tags'] as $b) {
									if ($a['tag'] === $b['tag'] && $a['value'] === $b['value']) {
										continue 2;
									}
								}

								$diff_tags[] = $a;
							}
							$update_item['tags'] = $diff_tags;
							break;
					}
				}

				if ($prototype || $items[$id]['flags'] == ZBX_FLAG_DISCOVERY_NORMAL) {
					$update_item += $input;

					$type = array_key_exists('type', $input) ? $input['type'] : $items[$id]['type'];

					if ($type != ITEM_TYPE_JMX) {
						unset($update_item['jmx_endpoint']);
					}

					if ($type != ITEM_TYPE_HTTPAGENT && $type != ITEM_TYPE_SCRIPT) {
						unset($update_item['timeout']);
					}
				}
				else if (array_key_exists('status', $input)) {
					$items_to_update[] = ['itemid' => $id, 'status' => $input['status']];
				}

				if ($update_item) {
					$items_to_update[] = ['itemid' => $id] + $update_item;
				}
			}

			if ($items_to_update && !$this->updateItemOrPrototype($items_to_update)) {
				throw new Exception();
			}
		}
		catch (Exception $e) {
			$result = false;
			CMessageHelper::setErrorTitle($prototype ? _('Cannot update item prototypes') : _('Cannot update items'));
		}

		if (DBend($result)) {
			$messages = CMessageHelper::getMessages();
			$output = ['title' => $prototype ? _('Item prototypes updated') : _('Items updated')];

			if (count($messages)) {
				$output['messages'] = array_column($messages, 'message');
			}
		}
		else {
			$output['error'] = [
				'title' => CMessageHelper::getTitle(),
				'messages' => array_column(get_and_clear_messages(), 'message')
			];
		}

		return (new CControllerResponseData(['main_block' => json_encode($output)]))->disableView();
	}

	/**
	 * Handle item mass update form initialization.
	 *
	 * @return CControllerResponse
	 */
	protected function form(): CControllerResponse {
		$data = [
			'action' => $this->getAction(),
			'context' => $this->getInput('context'),
			'delay_flex' => [['delay' => '', 'period' => '', 'type' => ITEM_DELAY_FLEXIBLE]],
			'ids' => $this->getInput('ids'),
			'initial_item_type' => null,
			'interfaceids' => [],
			'interfaces' => [],
			'multiple_interface_types' => false,
			'prototype' => $this->getInput('prototype'),
			'user' => ['debug_mode' => $this->getDebugMode()],
			'title' => _('Mass update')
		];

		if ($data['prototype']) {
			$data['parent_discoveryid'] = $this->getInput('parent_discoveryid', 0);
			$data += [
				'location_url' => (new CUrl('disc_prototypes.php'))
					->setArgument('context', $this->getInput('context'))
					->setArgument('parent_discoveryid', $data['parent_discoveryid'])
					->getUrl(),
				'preprocessing_test_type' => CControllerPopupItemTestEdit::ZBX_TEST_TYPE_ITEM_PROTOTYPE,
				'preprocessing_types' => CItemPrototype::SUPPORTED_PREPROCESSING_TYPES
			];
		}
		else {
			$data += [
				'location_url' => (new CUrl('items.php'))
					->setArgument('context', $this->getInput('context'))
					->getUrl(),
				'preprocessing_test_type' => CControllerPopupItemTestEdit::ZBX_TEST_TYPE_ITEM,
				'preprocessing_types' => CItem::SUPPORTED_PREPROCESSING_TYPES
			];
		}

		if ($data['context'] === 'host') {
			$hosts = API::Host()->get([
				'output' => ['hostid', 'flags'],
				'itemids' => $data['ids'],
				'selectInterfaces' => ['interfaceid', 'main', 'type', 'useip', 'ip', 'dns', 'port', 'details'],
				'limit' => 2
			]);
			$host = reset($hosts);
			$data['discovered_host'] = ($host['flags'] == ZBX_FLAG_DISCOVERY_CREATED);
			$data['hostid'] = $host['hostid'];
			$data['interfaces'] = $host['interfaces'];
			CArrayHelper::sort($data['interfaces'], [['field' => 'main', 'order' => ZBX_SORT_DOWN]]);

			// Interfaceids for js.
			foreach ($host['interfaces'] as $interface) {
				$data['interfaceids'][$interface['type']][] = $interface['interfaceid'];
			}
		}
		else {
			$hosts = API::Template()->get([
				'output' => ['templateid'],
				'itemids' => $data['ids'],
				'limit' => 2
			]);
			$host = reset($hosts);
			$data['hostid'] = $host['templateid'];
		}

		$data['single_host_selected'] = (count($hosts) == 1);

		if ($data['context'] === 'host' && $data['single_host_selected']) {
			$entity = $data['prototype'] ? API::ItemPrototype() : API::Item();
			$items = $entity->get([
				'output' => ['itemid', 'type'],
				'itemids' => $data['ids']
			]);

			$item_types = array_column($items, 'type', 'type');
			$item_interface_types = array_intersect_key(
				itemTypeInterface() + array_fill_keys($item_types, false),
				$item_types
			);
			$initial_type = count($item_interface_types) ? min(array_keys($item_interface_types)) : 0;
			$data['initial_item_type'] = $initial_type;
			$data['multiple_interface_types'] = (count(array_unique($item_interface_types)) > 1);
			$data['type'] = $initial_type;
		}

		$data['item_types'] = item_type2str();
		unset($data['item_types'][ITEM_TYPE_HTTPTEST], $data['item_types'][ITEM_TYPE_SCRIPT]);

		return new CControllerResponseData($data);
	}
}
