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 CScreenImporter extends CAbstractScreenImporter {
23
24	/**
25	 * Import screens.
26	 *
27	 * @param array $screens
28	 *
29	 * @return mixed
30	 */
31	public function import(array $screens) {
32		$screens = zbx_toHash($screens, 'name');
33
34		$this->checkCircularScreenReferences($screens);
35
36		do {
37			$independentScreens = $this->getIndependentScreens($screens);
38
39			$screensToCreate = [];
40			$screensToUpdate = [];
41			foreach ($independentScreens as $name) {
42				$screen = $screens[$name];
43				unset($screens[$name]);
44
45				$screen = $this->resolveScreenReferences($screen);
46
47				if ($screenId = $this->referencer->resolveScreen($screen['name'])) {
48					$screen['screenid'] = $screenId;
49					$screensToUpdate[] = $screen;
50				}
51				else {
52					$screensToCreate[] = $screen;
53				}
54			}
55
56			if ($this->options['screens']['createMissing'] && $screensToCreate) {
57				$newScreenIds = API::Screen()->create($screensToCreate);
58				foreach ($screensToCreate as $num => $newScreen) {
59					$screenidId = $newScreenIds['screenids'][$num];
60					$this->referencer->addScreenRef($newScreen['name'], $screenidId);
61				}
62			}
63			if ($this->options['screens']['updateExisting'] && $screensToUpdate) {
64				API::Screen()->update($screensToUpdate);
65			}
66		} while (!empty($independentScreens));
67
68		// if there are screens left in $screens, then they have unresolved references
69		foreach ($screens as $screen) {
70			$unresolvedReferences = [];
71			foreach ($screen['screenitems'] as $screenItem) {
72				if ($screenItem['resourcetype'] == SCREEN_RESOURCE_SCREEN
73						&& !$this->referencer->resolveScreen($screenItem['resource']['name'])) {
74					$unresolvedReferences[] = $screenItem['resource']['name'];
75				}
76			}
77			$unresolvedReferences = array_unique($unresolvedReferences);
78			throw new Exception(_n('Cannot import screen "%1$s": subscreen "%2$s" does not exist.',
79				'Cannot import screen "%1$s": subscreens "%2$s" do not exist.',
80				$screen['name'], implode(', ', $unresolvedReferences), count($unresolvedReferences)));
81		}
82	}
83
84	/**
85	 * Check if screens have circular references.
86	 * Circular references can be only in screen items that represent another screen.
87	 *
88	 * @throws Exception
89	 * @see checkCircularRecursive
90	 *
91	 * @param array $screens
92	 */
93	protected function checkCircularScreenReferences(array $screens) {
94		foreach ($screens as $screenName => $screen) {
95			if (empty($screen['screenitems'])) {
96				continue;
97			}
98
99			foreach ($screen['screenitems'] as $screenItem) {
100				$checked = [$screenName];
101				if ($circScreens = $this->checkCircularRecursive($screenItem, $screens, $checked)) {
102					throw new Exception(_s('Circular reference in screens: %1$s.', implode(' - ', $circScreens)));
103				}
104			}
105		}
106	}
107
108	/**
109	 * Recursive function for searching for circular screen references.
110	 * If circular reference exist it return array with screens names that fort it.
111	 *
112	 * @param array $screenItem screen to inspect on current recursive loop
113	 * @param array $screens    all screens where circular references should be searched
114	 * @param array $checked    screen names that already were processed,
115	 *                          should contain unique values if no circular references exist
116	 *
117	 * @return array|bool
118	 */
119	protected function checkCircularRecursive(array $screenItem, array $screens, array $checked) {
120		// if element is not map element, recursive reference cannot happen
121		if ($screenItem['resourcetype'] != SCREEN_RESOURCE_SCREEN) {
122			return false;
123		}
124
125		$screenName = $screenItem['resource']['name'];
126
127		// if current screen name is already in list of checked screen names,
128		// circular reference exists
129		if (in_array($screenName, $checked)) {
130			// to have nice result containing only screens that have circular reference,
131			// remove everything that was added before repeated screen name
132			$checked = array_slice($checked, array_search($screenName, $checked));
133			// add repeated name to have nice loop like s1->s2->s3->s1
134			$checked[] = $screenName;
135			return $checked;
136		}
137		else {
138			$checked[] = $screenName;
139		}
140
141		// we need to find screen that current element reference to
142		// and if it has screen items check all them recursively
143		if (!empty($screens[$screenName]['screenitems'])) {
144			foreach ($screens[$screenName]['screenitems'] as $sItem) {
145				return $this->checkCircularRecursive($sItem, $screens, $checked);
146			}
147		}
148
149		return false;
150	}
151
152	/**
153	 * Get screens that don't have screen items that reference not existing screen i.e. screen items references can be resolved.
154	 * Returns array with screen names.
155	 *
156	 * @param array $screens
157	 *
158	 * @return array
159	 */
160	protected function getIndependentScreens(array $screens) {
161		foreach ($screens as $num => $screen) {
162			if (empty($screen['screenitems'])) {
163				continue;
164			}
165
166			foreach ($screen['screenitems'] as $screenItem) {
167				if ($screenItem['resourcetype'] == SCREEN_RESOURCE_SCREEN) {
168					if (!$this->referencer->resolveScreen($screenItem['resource']['name'])) {
169						unset($screens[$num]);
170						continue 2;
171					}
172				}
173			}
174		}
175
176		return zbx_objectValues($screens, 'name');
177	}
178}
179