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	 * @return void
94	 */
95	protected function checkCircularScreenReferences(array $screens) {
96		foreach ($screens as $screenName => $screen) {
97			if (empty($screen['screenitems'])) {
98				continue;
99			}
100
101			foreach ($screen['screenitems'] as $screenItem) {
102				$checked = [$screenName];
103				if ($circScreens = $this->checkCircularRecursive($screenItem, $screens, $checked)) {
104					throw new Exception(_s('Circular reference in screens: %1$s.', implode(' - ', $circScreens)));
105				}
106			}
107		}
108	}
109
110	/**
111	 * Recursive function for searching for circular screen references.
112	 * If circular reference exist it return array with screens names that fort it.
113	 *
114	 * @param array $screenItem screen to inspect on current recursive loop
115	 * @param array $screens    all screens where circular references should be searched
116	 * @param array $checked    screen names that already were processed,
117	 *                          should contain unique values if no circular references exist
118	 *
119	 * @return array|bool
120	 */
121	protected function checkCircularRecursive(array $screenItem, array $screens, array $checked) {
122		// if element is not map element, recursive reference cannot happen
123		if ($screenItem['resourcetype'] != SCREEN_RESOURCE_SCREEN) {
124			return false;
125		}
126
127		$screenName = $screenItem['resource']['name'];
128
129		// if current screen name is already in list of checked screen names,
130		// circular reference exists
131		if (in_array($screenName, $checked)) {
132			// to have nice result containing only screens that have circular reference,
133			// remove everything that was added before repeated screen name
134			$checked = array_slice($checked, array_search($screenName, $checked));
135			// add repeated name to have nice loop like s1->s2->s3->s1
136			$checked[] = $screenName;
137			return $checked;
138		}
139		else {
140			$checked[] = $screenName;
141		}
142
143		// we need to find screen that current element reference to
144		// and if it has screen items check all them recursively
145		if (!empty($screens[$screenName]['screenitems'])) {
146			foreach ($screens[$screenName]['screenitems'] as $sItem) {
147				return $this->checkCircularRecursive($sItem, $screens, $checked);
148			}
149		}
150
151		return false;
152	}
153
154	/**
155	 * Get screens that don't have screen items that reference not existing screen i.e. screen items references can be resolved.
156	 * Returns array with screen names.
157	 *
158	 * @param array $screens
159	 *
160	 * @return array
161	 */
162	protected function getIndependentScreens(array $screens) {
163		foreach ($screens as $num => $screen) {
164			if (empty($screen['screenitems'])) {
165				continue;
166			}
167
168			foreach ($screen['screenitems'] as $screenItem) {
169				if ($screenItem['resourcetype'] == SCREEN_RESOURCE_SCREEN) {
170					if (!$this->referencer->resolveScreen($screenItem['resource']['name'])) {
171						unset($screens[$num]);
172						continue 2;
173					}
174				}
175			}
176		}
177
178		return zbx_objectValues($screens, 'name');
179	}
180}
181