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


require_once dirname(__FILE__).'/../../include/CWebTest.php';
require_once dirname(__FILE__).'/../behaviors/CMessageBehavior.php';
require_once dirname(__FILE__).'/../behaviors/CTableBehavior.php';

/**
 * Base class for Host and Template group form.
 */
class testFormGroups extends CWebTest {

	/**
	 * Attach MessageBehavior and TableBehavior to the test.
	 *
	 * @return array
	 */
	public function getBehaviors() {
		return [
			CMessageBehavior::class,
			CTableBehavior::class
		];
	}

	/**
	 * Objects created in dataSource DiscoveredHosts.
	 */
	const DISCOVERED_GROUP = 'Group created from host prototype 1';
	const HOST_PROTOTYPE = 'Host created from host prototype {#KEY}';
	const LLD = 'LLD for Discovered host tests';

	/**
	 * Objects created in dataSource HostTemplateGroups.
	 */
	const DELETE_ONE_GROUP = 'One group belongs to one object for Delete test';
	const DELETE_GROUP = 'Group empty for Delete test';
	const DELETE_GROUP2 = 'First group to one object for Delete test';

	/**
	 * Host and template subgroup name for clone test scenario.
	 */
	const SUBGROUP = 'Group1/Subgroup1/Subgroup2';

	/**
	 * SQL query to get groups to compare hash values.
	 */
	const GROUPS_SQL = 'SELECT * FROM hstgrp g INNER JOIN hosts_groups hg ON g.groupid=hg.groupid'.
			' ORDER BY g.groupid, hg.hostgroupid';

	/**
	 * SQL query to get user group permissions for template and host groups to compare hash values.
	 */
	const PERMISSION_SQL = 'SELECT * FROM rights ORDER BY rightid';

	/**
	 * Flag for group form opened by direct link.
	 */
	protected $standalone = false;

	/**
	 * Link to page for opening group form.
	 */
	protected $link;

	/**
	 * Host or template group.
	 */
	protected $object;

	/**
	 * Group form check on search page.
	 */
	protected $search = false;

	/**
	 * Host and template group name for update test scenario.
	 */
	protected static $update_group;

	/**
	 * User group ID for subgroup permissions scenario.
	 */
	protected static $user_groupid;

	public static function prepareGroupData() {
		// Prepare data for template groups.
		CDataHelper::call('templategroup.create', [
			[
				'name' => 'Group for Update test'
			],
			[
				'name' => 'Templates/Update'
			],
			[
				'name' => 'Group1/Subgroup1/Subgroup2'
			]
		]);

		// Prepare data for host groups.
		CDataHelper::call('hostgroup.create', [
			[
				'name' => 'Group for Update test'
			],
			[
				'name' => 'Hosts/Update'
			],
			[
				'name' => 'Group1/Subgroup1/Subgroup2'
			]
		]);
	}

	/**
	 * Test for checking group form layout.
	 *
	 * @param string  $name        host or template group name
	 * @param boolean $discovered  discovered host group or not
	 */
	public function layout($name, $discovered = false) {
		// Check existing group form.
		$form = $this->openForm($name, $discovered);
		if ($this->standalone) {
			$this->page->assertHeader(ucfirst($this->object).' group');
			$footer = $form;
		}
		else {
			$dialog = COverlayDialogElement::find()->one()->waitUntilReady();
			$this->assertEquals(ucfirst($this->object).' group', $dialog->getTitle());
			$footer = $dialog->getFooter();
		}

		$this->assertTrue($form->isRequired('Group name'));
		$form->checkValue(['Group name' => $name]);
		$this->assertEquals(['Update', 'Clone', 'Delete','Cancel'], $footer->query('button')->all()
				->filter(CElementFilter::CLICKABLE)->asText()
		);

		if ($discovered) {
			$this->assertEquals(['Discovered by', 'Group name', 'Apply permissions and tag filters to all subgroups'],
					$form->getLabels(CElementFilter::VISIBLE)->asText()
			);
			$this->assertTrue($form->getField('Group name')->isAttributePresent('readonly'));
			$this->assertEquals(self::LLD, $form->getField('Discovered by')->query('tag:a')->one()->getText());
			$form->query('link', self::LLD)->one()->click();
			// TODO: temporarily commented out due webdriver issue #351858989, alert is not displayed while leaving page during test execution
//			if (!$this->standalone) {
//				$this->page->acceptAlert();
//				$this->page->waitUntilReady();
//			}
			$this->page->assertHeader('Host prototypes');
			$this->query('id:host')->one()->checkValue(self::HOST_PROTOTYPE);

			return;
		}

		$this->assertEquals(['Group name', ($this->object === 'host')
			? 'Apply permissions and tag filters to all subgroups'
			: 'Apply permissions to all subgroups'],
				$form->getLabels(CElementFilter::VISIBLE)->asText()
		);

		// There is no group creation on the search page.
		if ($this->search) {
			$dialog->close();

			return;
		}

		// Check new group form.
		if ($this->standalone) {
			$this->page->open('zabbix.php?action='.$this->object.'group.edit')->waitUntilReady();
			$this->page->assertHeader('New '.$this->object.' group');
		}
		else {
			$dialog->close();

			// Open group create form.
			$this->query('button', 'Create '.$this->object.' group')->one()->click();
			$this->assertEquals('New '.$this->object.' group', $dialog->waitUntilReady()->getTitle());
		}

		$form->invalidate();
		$this->assertTrue($form->getField('Group name')->isAttributePresent(['maxlength' => '255', 'value' => '']));
		$this->assertTrue($form->isRequired('Group name'));
		$this->assertEquals(['Group name'], $form->getLabels(CElementFilter::VISIBLE)->asText());
		$this->assertEquals(['Add', 'Cancel'], $footer->query('button')->all()->filter(CElementFilter::CLICKABLE)->asText());
		$footer->query('button:Cancel')->one()->click();

		if ($this->standalone) {
			$form->waitUntilNotVisible();
			$this->page->assertHeader(ucfirst($this->object).' groups');
			$this->assertEquals(PHPUNIT_URL.'zabbix.php?action='.$this->object.'group.list', $this->page->getCurrentUrl());
		}
		else {
			$dialog->ensureNotPresent();
		}
	}

	/**
	 * Function for opening group form.
	 *
	 * @param string  $name        host or template group name to open
	 * @param boolean $discovered  discovered host group or not
	 *
	 * @return CForm
	 */
	public function openForm($name = null, $discovered = false) {
		if ($this->standalone) {
			if ($name) {
				$groupid = CDBHelper::getValue('SELECT groupid FROM hstgrp WHERE name='.zbx_dbstr($name).
						' AND type='.constant('HOST_GROUP_TYPE_'.strtoupper($this->object).'_GROUP')
				);
				$this->page->login()->open($this->link.$groupid)->waitUntilReady();
			}
			else {
				$this->page->login()->open('zabbix.php?action='.$this->object.'group.edit')->waitUntilReady();
			}

			return $this->query('id', $this->object.'groupForm')->asForm()->waitUntilVisible()->one();
		}
		else {
			$this->page->login()->open($this->link)->waitUntilReady();

			if ($name) {
				$column_name = $this->search ? ucfirst($this->object).' group' : 'Name';
				$table_selector = $this->search ? 'xpath://section[@id="search_'.$this->object.'group"]//table' : 'class:list-table';
				$table = $this->query($table_selector)->asTable()->one();
				$table->findRow($column_name, ($discovered && !$this->search) ? self::LLD.': '.$name : $name)
						->getColumn($column_name)->query('link', $name)->one()->click();
			}
			else {
				$this->query('button', 'Create '.$this->object.' group')->one()->click();
			}

			return COverlayDialogElement::find()->one()->waitUntilReady()->asForm();
		}
	}

	public static function getCreateData() {
		return [
			[
				[
					'expected' => TEST_BAD,
					'error' => 'Invalid parameter "/1/name": cannot be empty.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => [
						'Group name' => ' '
					],
					'error' => 'Invalid parameter "/1/name": cannot be empty.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => [
						'Group name' => 'Test/Test/'
					],
					'error' => 'Invalid parameter "/1/name": invalid host group name.',
					'template_error' => 'Invalid parameter "/1/name": invalid template group name.'
				]
			],
			[
				[
					'expected' => TEST_BAD,
					'fields' => [
						'Group name' => 'Test/Test\/'
					],
					'error' => 'Invalid parameter "/1/name": invalid host group name.',
					'template_error' => 'Invalid parameter "/1/name": invalid template group name.'
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'fields' => [
						'Group name' => '~!@#$%^&*()_+=[]{}null{$A}{#B}'
					]
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'fields' => [
						'Group name' => 'æ㓴🙂'
					]
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'fields' => [
						'Group name' => '   trim    '
					],
					'trim' => true
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'fields' => [
						'Group name' => 'Group/Subgroup1/Subgroup2'
					]
				]
			]
		];
	}

	public function getUpdateData() {
		$data = [];

		// Add 'update' word to group name and change group name in test case with trim.
		foreach ($this->getCreateData() as $group) {
			if ($group[0]['expected'] === TEST_GOOD) {
				$group[0]['fields']['Group name'] = CTestArrayHelper::get($group[0], 'trim', false)
					? '   trim update    '
					: $group[0]['fields']['Group name'].'update';
			}

			$data[] = $group;
		}

		return $data;
	}

	/**
	 * Test for checking group creation and update.
	 *
	 * @param array  $data    data provider
	 * @param string $action  create or update action
	 */
	protected function checkForm($data, $action) {
		$good_message = ucfirst($this->object).' group '.(($action === 'create') ? 'added' : 'updated');
		$bad_message = 'Cannot '.(($action === 'create') ? 'add' : 'update').' '.$this->object.' group';

		if ($data['expected'] === TEST_BAD) {
			$old_hash = CDBHelper::getHash(self::GROUPS_SQL);
			$permission_old_hash = CDBHelper::getHash(self::PERMISSION_SQL);
		}

		$form = $this->openForm(($action === 'update') ? static::$update_group : null);
		$form->fill(CTestArrayHelper::get($data, 'fields', []));

		// Clear name for update scenario.
		if ($action === 'update' && !CTestArrayHelper::get($data, 'fields', false)) {
			$form->getField('Group name')->clear();
		}

		$form->submit();

		if ($data['expected'] === TEST_GOOD) {
			if (!$this->standalone) {
				COverlayDialogElement::ensureNotPresent();
			}
			$this->assertMessage(TEST_GOOD, $good_message);

			// Trim trailing and leading spaces in expected values before comparison.
			if (CTestArrayHelper::get($data, 'trim', false)) {
				$data['fields']['Group name'] = trim($data['fields']['Group name']);
			}

			$form = $this->openForm($data['fields']['Group name']);
			$form->checkValue($data['fields']['Group name']);

			// Change group name after successful update scenario.
			if ($action === 'update') {
				static::$update_group = $data['fields']['Group name'];
			}
		}
		else {
			$this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL));
			$this->assertEquals($permission_old_hash, CDBHelper::getHash(self::PERMISSION_SQL));
			$error_details =  ($this->object == 'template')
				? CTestArrayHelper::get($data, 'template_error', $data['error'])
				: $data['error'];
			$this->assertMessage(TEST_BAD, $bad_message, $error_details);
		}

		if (!$this->standalone) {
			COverlayDialogElement::find()->one()->close();
		}
	}

	/**
	 * Update group without changing data.
	 *
	 * @param string  $name        group name to be opened for check
	 * @param bollean $discovered  discovered host group or not
	 */
	public function simpleUpdate($name, $discovered = false) {
		$old_hash = CDBHelper::getHash(self::GROUPS_SQL);
		$form = $this->openForm($name, $discovered);
		$values = $form->getValues();
		$form->submit();
		$this->assertMessage(TEST_GOOD, ucfirst($this->object).' group updated');
		$this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL));

		// Check form values.
		$this->openForm($name, $discovered);
		$form->invalidate();
		$this->assertEquals($values, $form->getValues());

		if (!$this->standalone) {
			COverlayDialogElement::find()->one()->close();
		}
	}

	public static function getCloneData() {
		return [
			[
				[
					'expected' => TEST_BAD,
					'name' => self::DELETE_GROUP,
					'error' => ' group "'.self::DELETE_GROUP.'" already exists.'
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'name' => self::DELETE_GROUP,
					'fields'  => [
						'Group name' => microtime().' cloned group'
					]
				]
			],
			[
				[
					'expected' => TEST_GOOD,
					'name' => self::SUBGROUP,
					'fields'  => [
						'Group name' => microtime().'/cloned/subgroup'
					]
				]
			]
		];
	}

	public function clone($data) {
		if ($data['expected'] === TEST_BAD) {
			$old_hash = CDBHelper::getHash(self::GROUPS_SQL);
		}

		$form = $this->openForm($data['name'], CTestArrayHelper::get($data, 'discovered', false));
		$footer = ($this->standalone) ? $form : COverlayDialogElement::find()->one()->waitUntilReady()->getFooter();
		$footer->query('button:Clone')->one()->waitUntilClickable()->click();
		$form->invalidate();

		// Check that the group creation form is open after cloning.
		$title = 'New '.$this->object.' group';
		if ($this->standalone) {
			$this->page->assertHeader($title);
		}
		else {
			$this->assertEquals($title, COverlayDialogElement::find()->one()->waitUntilReady()->getTitle());
		}

		$this->assertEquals(PHPUNIT_URL.'zabbix.php?action='.$this->object.'group.edit', $this->page->getCurrentUrl());
		$this->assertEquals(['Add', 'Cancel'], $footer->query('button')->all()->filter(CElementFilter::CLICKABLE)->asText());
		$form->fill(CTestArrayHelper::get($data, 'fields', []));
		$form->submit();

		if ($data['expected'] === TEST_GOOD) {
			if (!$this->standalone) {
				COverlayDialogElement::ensureNotPresent();
			}
			$this->assertMessage(TEST_GOOD, ucfirst($this->object).' group added');
			$this->page->assertHeader($this->search ? 'Search: group' : ucfirst($this->object).' groups');
			$url = PHPUNIT_URL.($this->standalone ? 'zabbix.php?action='.$this->object.'group.list' : $this->link);
			$this->assertEquals($url, $this->page->getCurrentUrl());

			$form = $this->openForm($data['fields']['Group name']);
			$form->checkValue($data['fields']['Group name']);

			foreach ([$data['name'], $data['fields']['Group name']] as $name) {
				$this->assertEquals(1, CDBHelper::getCount('SELECT NULL FROM hstgrp WHERE name='.zbx_dbstr($name).
						' AND type='.constant('HOST_GROUP_TYPE_'.strtoupper($this->object).'_GROUP'))
				);
			}
		}
		else {
			$this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL));
			$this->assertMessage(TEST_BAD, 'Cannot add '.$this->object.' group', ucfirst($this->object).$data['error']);
		}

		if (!$this->standalone) {
			COverlayDialogElement::find()->one()->close();
		}
	}

	public static function getCancelData() {
		return [
			[
				[
					'action' => 'Add'
				]
			],
			[
				[
					'action' => 'Update'
				]
			],
			[
				[
					'action' => 'Clone'
				]
			],
			[
				[
					'action' => 'Delete'
				]
			]
		];
	}

	/**
	 * Test for checking group actions cancelling.
	 *
	 * @param array $data  data provider with fields values
	 */
	public function cancel($data) {
		// There is no group creation on the search page.
		if ($this->search && $data['action'] === 'Add') {
			return;
		}

		$old_hash = CDBHelper::getHash(self::GROUPS_SQL);
		$new_name = microtime(true).' Cancel '.self::DELETE_GROUP;
		$form = $this->openForm(($data['action'] === 'Add') ? null : self::DELETE_GROUP);

		// Change name.
		$form->fill(['Group name' => $new_name]);
		$footer = ($this->standalone) ? $form : COverlayDialogElement::find()->one()->waitUntilReady()->getFooter();

		if (in_array($data['action'], ['Clone', 'Delete'])) {
			$footer->query('button', $data['action'])->one()->click();
		}

		if ($data['action'] === 'Delete') {
			$this->page->dismissAlert();
		}

		// Refresh element after opening new form after cloning.
		if ($data['action'] === 'Clone') {
			$footer = ($this->standalone)
				? $form->invalidate()
				: COverlayDialogElement::find()->one()->waitUntilReady()->getFooter();
		}

		$footer->query('button:Cancel')->waitUntilClickable()->one()->click();

		if (!$this->standalone) {
			COverlayDialogElement::ensureNotPresent();
		}

		$this->page->assertHeader($this->search ? 'Search: group' : ucfirst($this->object).' groups');
		$url = PHPUNIT_URL.($this->standalone ? 'zabbix.php?action='.$this->object.'group.list' : $this->link);
		$this->assertEquals($url, $this->page->getCurrentUrl());
		$this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL));

	}

	public static function getDeleteData() {
		return [
			// Empty group without Host/Template.
			[
				[
					'expected' => TEST_GOOD,
					'name' => self::DELETE_GROUP
				]
			],
			// Host/Template has two groups, one of them can be deleted.
			[
				[
					'expected' => TEST_GOOD,
					'name' => self::DELETE_GROUP2
				]
			]
		];
	}

	/**
	 * Test for checking group deletion.
	 *
	 * @param array $data  data provider
	 */
	public function delete($data) {
		if ($data['expected'] === TEST_BAD) {
			$old_hash = CDBHelper::getHash(self::GROUPS_SQL);
		}

		$form = $this->openForm($data['name']);
		$footer = ($this->standalone) ? $form : COverlayDialogElement::find()->one()->waitUntilReady()->getFooter();
		$footer->query('button:Delete')->one()->waitUntilClickable()->click();
		$this->assertEquals('Delete selected '.$this->object.' group?', $this->page->getAlertText());
		$this->page->acceptAlert();

		if ($data['expected'] === TEST_GOOD) {
			if (!$this->standalone) {
				COverlayDialogElement::ensureNotPresent();
			}

			$this->assertMessage(TEST_GOOD, ucfirst($this->object).' group deleted');
			$this->assertEquals(0, CDBHelper::getCount('SELECT NULL FROM hstgrp WHERE name='.zbx_dbstr($data['name']).
					' AND type='.constant('HOST_GROUP_TYPE_'.strtoupper($this->object).'_GROUP'))
			);
		}
		else {
			$this->assertEquals($old_hash, CDBHelper::getHash(self::GROUPS_SQL));
			$this->assertMessage(TEST_BAD, 'Cannot delete '.$this->object.' group', $data['error']);

			if (!$this->standalone) {
				COverlayDialogElement::find()->one()->close();
			}
		}
	}

	public static function prepareSubgroupData() {
		CDataHelper::call('hostgroup.create', [
			[
				'name' => 'Europe'
			],
			[
				'name' => 'Europe/Latvia'
			],
			[
				'name' => 'Europe/Latvia/Riga/Zabbix'
			],
			[
				'name' => 'Europe/Test'
			],
			[
				'name' => 'Europe/Test/Zabbix'
			],
			// Groups to check inherited permissions when creating a parent or subgroup.
			[
				'name' => 'Streets'
			],
			[
				'name' => 'Cities/Cesis'
			],
			[
				'name' => 'Europe group for test on search page'
			]
		]);
		$host_groupids = CDataHelper::getIds('name');

		CDataHelper::call('templategroup.create', [
			[
				'name' => 'Europe'
			],
			[
				'name' => 'Europe/Latvia'
			],
			[
				'name' => 'Europe/Latvia/Riga/Zabbix'
			],
			[
				'name' => 'Europe/Test'
			],
			[
				'name' => 'Europe/Test/Zabbix'
			],
			[
				'name' => 'Streets'
			],
			[
				'name' => 'Cities/Cesis'
			],
			[
				'name' => 'Europe group for test on search page'
			]
		]);
		$template_groupids = CDataHelper::getIds('name');

		$response = CDataHelper::call('usergroup.create', [
			[
				'name' => 'User group to check subgroup permissions',
				'hostgroup_rights' => [
					[
						'permission' => PERM_DENY,
						'id' => $host_groupids['Europe']
					],
					[
						'permission' => PERM_READ,
						'id' => $host_groupids['Europe/Latvia']
					],
					[
						'permission' => PERM_READ_WRITE,
						'id' => $host_groupids['Europe/Test']
					],
					[
						'permission' => PERM_DENY,
						'id' => $host_groupids['Streets']
					],
					[
						'permission' => PERM_READ,
						'id' => $host_groupids['Cities/Cesis']
					]
				],
				'templategroup_rights' => [
					[
						'permission' => PERM_DENY,
						'id' => $template_groupids['Europe']
					],
					[
						'permission' => PERM_READ,
						'id' => $template_groupids['Europe/Latvia']
					],
					[
						'permission' => PERM_READ_WRITE,
						'id' => $template_groupids['Europe/Test']
					],
					[
						'permission' => PERM_DENY,
						'id' => $template_groupids['Streets']
					],
					[
						'permission' => PERM_READ,
						'id' => $template_groupids['Cities/Cesis']
					]
				],
				'tag_filters' => [
					[
						'groupid' => $host_groupids['Europe'],
						'tag' => 'world',
						'value' => ''
					],
					[
						'groupid' => $host_groupids['Europe/Test'],
						'tag' => 'country',
						'value' => 'test'
					],
					[
						'groupid' => $host_groupids['Streets'],
						'tag' => 'street',
						'value' => ''
					],
					[
						'groupid' => $host_groupids['Cities/Cesis'],
						'tag' => 'city',
						'value' => 'Cesis'
					]
				]
			]
		]);
		self::$user_groupid = $response['usrgrpids'][0];
	}

	public static function getSubgroupsData() {
		return [
			[
				[
					// All 'Europe/Test' subgroups are changing permissions.
					'apply_permissions' => 'Europe/Test',
					// Permissions do not apply to a first-level group from existing subgroup.
					'create' => 'Cities',
					// "groups_before" parameter isn't used in test, but groups are listed here for test clarity.
					'groups_before' => [
						[
							'groups' => 'Europe/Test',
							'Permissions' => 'Read-write'
						],
						[
							'groups' => ['Cities/Cesis', 'Europe/Latvia'],
							'Permissions' => 'Read'
						],
						[
							'groups' => ['Europe', 'Streets'],
							'Permissions' => 'Deny'
						]
					],
					'tags_before' => [
						['Host groups' => 'Cities/Cesis', 'Tags' => 'city: Cesis'],
						['Host groups' => 'Europe', 'Tags' => 'world'],
						['Host groups' => 'Europe/Test', 'Tags' => 'country: test'],
						['Host groups' => 'Streets', 'Tags' => 'street']
					],
					'groups_after' => [
						[
							'groups' => ['Europe/Test', 'Europe/Test/Zabbix'],
							'Permissions' => 'Read-write'
						],
						[
							'groups' => ['Cities/Cesis', 'Europe/Latvia'],
							'Permissions' => 'Read'
						],
						[
							'groups' => ['Europe', 'Streets'],
							'Permissions' => 'Deny'
						]
					],
					'tags_after' => [
						['Host groups' => 'Cities/Cesis', 'Tags' => 'city: Cesis'],
						['Host groups' => 'Europe', 'Tags' => 'world'],
						['Host groups' => 'Europe/Test', 'Tags' => 'country: test'],
						['Host groups' => 'Europe/Test/Zabbix', 'Tags' => 'country: test'],
						['Host groups' => 'Streets', 'Tags' => 'street']
					]
				]
			],
			[
				[
					// All 'Europe' subgroups are changing permissions.
					'apply_permissions' => 'Europe',
					// The new subgroup inherits the permissions of the first-level group.
					'create' => 'Streets/Dzelzavas',
					'groups_before' => [
						[
							'groups' => ['Europe/Test', 'Europe/Test/Zabbix'],
							'Permissions' => 'Read-write'
						],
						[
							'groups' => ['Cities/Cesis', 'Europe/Latvia'],
							'Permissions' => 'Read'
						],
						[
							'groups' => ['Europe', 'Streets'],
							'Permissions' => 'Deny'
						]
					],
					'tags_before' => [
						['Host groups' => 'Cities/Cesis', 'Tags' => 'city: Cesis'],
						['Host groups' => 'Europe', 'Tags' => 'world'],
						['Host groups' => 'Europe/Test', 'Tags' => 'country: test'],
						['Host groups' => 'Streets', 'Tags' => 'street']
					],
					'groups_after' => [
						[
							'groups' => 'Cities/Cesis',
							'Permissions' => 'Read'
						],
						[
							'groups' => ['Europe', 'Europe/Latvia', 'Europe/Latvia/Riga/Zabbix',
								'Europe/Test', 'Europe/Test/Zabbix', 'Streets', 'Streets/Dzelzavas'],
							'Permissions' => 'Deny'
						]
					],
					'tags_after' => [
						['Host groups' => 'Cities/Cesis', 'Tags' => 'city: Cesis'],
						['Host groups' => 'Europe', 'Tags' => 'world'],
						['Host groups' => 'Europe/Latvia', 'Tags' => 'world'],
						['Host groups' => 'Europe/Latvia/Riga/Zabbix', 'Tags' => 'world'],
						['Host groups' => 'Europe/Test', 'Tags' => 'world'],
						['Host groups' => 'Europe/Test/Zabbix', 'Tags' => 'world'],
						['Host groups' => 'Streets', 'Tags' => 'street'],
						['Host groups' => 'Streets/Dzelzavas', 'Tags' => 'street']
					]
				]
			]
		];
	}

	/**
	 * Apply the same level of permissions/tag filters to all nested host groups.
	 *
	 * @param array $data  data provider
	 */
	public function checkSubgroupsPermissions($data) {
		// Prepare groups array, change key to 'Host groups' or 'Template groups'.
		foreach ($data['groups_after'] as &$group_permissions) {
			$group_permissions[ucfirst($this->object).' groups'] = $group_permissions['groups'];
			unset($group_permissions['groups']);
		}
		unset($group_permissions);

		// Create new parent or subgroup to check nested permissions.
		if (array_key_exists('create', $data)) {
			$form = $this->openForm(CTestArrayHelper::get($data, 'open_form', null));
			$form->fill(['Group name' => $data['create']]);
			$form->submit();

			if (!$this->standalone) {
				COverlayDialogElement::ensureNotPresent();
			}

			// Permission inheritance doesn't apply when changing the name of existing group, only when creating a new group.
			$this->assertMessage(TEST_GOOD,
					ucfirst($this->object).' group '.(array_key_exists('open_form', $data) ? 'updated' : 'added')
			);
		}

		// Apply permissions to subgroups.
		$form = $this->openForm($data['apply_permissions']);
		$form->fill([(($this->object === 'host')
			? 'Apply permissions and tag filters to all subgroups'
			: 'Apply permissions to all subgroups') => true
		]);
		$form->submit();

		if (!$this->standalone) {
			COverlayDialogElement::ensureNotPresent();
		}
		$this->assertMessage(TEST_GOOD, ucfirst($this->object).' group updated');

		// Check group and tag permissions in user group.
		$this->page->open('zabbix.php?action=usergroup.edit&usrgrpid='.self::$user_groupid)->waitUntilReady();
		$group_form = $this->query('id:user-group-form')->asForm()->one();
		$group_form->selectTab(ucfirst($this->object).' permissions');
		$group_form->getField('Permissions')->asMultifieldTable()->checkValue($data['groups_after']);
		$group_form->selectTab('Problem tag filter');
		// Tag permissions do not change for template groups.
		$this->assertTableData(($this->object === 'template') ? $data['tags_before'] : $data['tags_after'],
				'id:tag-filter-table'
		);
	}
}