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