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