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 22/** 23 * Controller class to maintain server-side notification generation tasks. 24 */ 25class CControllerNotificationsGet extends CController { 26 27 protected function init() { 28 parent::init(); 29 30 $this->notifications = []; 31 $this->settings = getMessageSettings(); 32 $ok_timeout = (int) timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::OK_PERIOD)); 33 $timeout = (int) timeUnitToSeconds($this->settings['timeout']); 34 $this->settings['timeout'] = $timeout; 35 $this->settings['ok_timeout'] = min([$timeout, $ok_timeout]); 36 $this->settings['show_recovered'] = (bool) $this->settings['triggers.recovery']; 37 $this->settings['show_suppressed'] = (bool) $this->settings['show_suppressed']; 38 if (!$this->settings['triggers.severities']) { 39 $this->settings['enabled'] = true; 40 } 41 42 $this->timeout_time = time() - $this->settings['timeout']; 43 $this->time_from = max([$this->settings['last.clock'], $this->timeout_time]); 44 } 45 46 protected function checkInput() { 47 $fields = [ 48 'known_eventids' => 'array_db events.eventid' 49 ]; 50 51 $ret = $this->validateInput($fields); 52 53 if (!$ret) { 54 return $this->setResponse( 55 new CControllerResponseData([ 56 'main_block' => json_encode(['error' => true]) 57 ]) 58 ); 59 } 60 61 return $ret; 62 } 63 64 protected function checkPermissions() { 65 return (!CWebUser::isGuest() && $this->getUserType() >= USER_TYPE_ZABBIX_USER); 66 } 67 68 protected function doAction() { 69 if (!$this->settings['enabled']) { 70 return $this->setResponse(new CControllerResponseData(['main_block' => $this->makeResponseData()])); 71 } 72 73 // Server returns only basic details for events already known by client-side. 74 $this->known_eventids = array_flip($this->getInput('known_eventids', [])); 75 $this->loadNotifications(); 76 77 $this->setResponse(new CControllerResponseData(['main_block' => $this->makeResponseData()])); 78 } 79 80 protected function loadNotifications() { 81 // Select problem events. 82 $options = [ 83 'output' => ['eventid', 'r_eventid', 'objectid', 'severity', 'clock', 'r_clock', 'name'], 84 'source' => EVENT_SOURCE_TRIGGERS, 85 'object' => EVENT_OBJECT_TRIGGER, 86 'severities' => array_keys($this->settings['triggers.severities']), 87 'suppressed' => $this->settings['show_suppressed'] ? null : false, 88 'sortorder' => ZBX_SORT_DOWN, 89 'sortfield' => 'eventid', 90 'limit' => 15, 91 'preservekeys' => true 92 ]; 93 94 $options += $this->settings['show_recovered'] 95 ? ['recent' => true] 96 : ['time_from' => $this->time_from]; 97 98 $events = API::Problem()->get($options); 99 100 // Select latest status for already known events that are no longer available in problems table. 101 $other_still_shown_eventids = $this->settings['show_recovered'] 102 ? array_diff(array_keys($this->known_eventids), array_keys($events)) 103 : []; 104 105 if ($other_still_shown_eventids) { 106 $resolved_events = API::Event()->get([ 107 'output' => ['eventid', 'r_eventid', 'clock', 'severity'], 108 'eventids' => $other_still_shown_eventids, 109 'sortfield' => 'clock', 110 'sortorder' => ZBX_SORT_DOWN, 111 'preservekeys' => true 112 ]); 113 114 $r_eventids = []; 115 116 foreach ($resolved_events as $eventid => $resolved_event) { 117 if ($resolved_event['r_eventid'] != 0) { 118 $r_eventids[$eventid] = $resolved_event['r_eventid']; 119 } 120 } 121 122 if ($r_eventids) { 123 $r_clocks = API::Event()->get([ 124 'output' => ['clock'], 125 'eventids' => array_values($r_eventids), 126 'sortfield' => 'clock', 127 'sortorder' => ZBX_SORT_DOWN, 128 'preservekeys' => true 129 ]); 130 131 foreach ($r_eventids as $eventid => &$r_eventid) { 132 $resolved_events[$eventid]['r_clock'] = $r_clocks[$r_eventid]['clock']; 133 } 134 unset($r_eventid); 135 } 136 137 $events += $resolved_events; 138 } 139 140 // Append selected events to notifications array. 141 $problems_by_triggerid = []; 142 143 foreach ($events as $eventid => $event) { 144 if ($this->settings['show_recovered']) { 145 if (array_key_exists('r_clock', $event) && $event['r_clock'] >= $this->time_from) { 146 /* 147 * This happens if trigger is recovered and is already removed from the list of known eventids. 148 * Do nothing here. This statement is needed just to catch specific case before next IF statement. 149 */ 150 } 151 elseif (array_key_exists($event['eventid'], $this->known_eventids) 152 && $event['clock'] < $this->timeout_time) { 153 /* 154 * This exception is needed to add notifications that are delayed in front-end, for example, in case 155 * if user has logged in between 30th and 60th second after event was generated. Since notification 156 * is still in response, front-end will remove that message using client side timeout. 157 */ 158 } 159 // Filter by problem start time, because that is not done by API if show_recovered is enabled. 160 elseif ($event['clock'] < $this->time_from && !in_array($eventid, $other_still_shown_eventids)) { 161 continue; 162 } 163 } 164 165 // Trigger API is used to select hostname only for notifications that client cannot recover from cache. 166 if (!array_key_exists($event['eventid'], $this->known_eventids)) { 167 $problems_by_triggerid[$event['objectid']][] = $eventid; 168 } 169 170 $this->notifications[$eventid] = [ 171 'eventid' => $event['eventid'], 172 'resolved' => (int) ($event['r_eventid'] != 0), 173 'severity' => (int) $event['severity'], 174 'clock' => ((int) $event['r_eventid'] == 0) ? $event['clock'] : $event['r_clock'], 175 'name' => array_key_exists('name', $event) ? $event['name'] : '' 176 ]; 177 } 178 179 // Add additional details newly discovered events. 180 if ($problems_by_triggerid) { 181 $triggers = API::Trigger()->get([ 182 'output' => [], 183 'selectHosts' => ['hostid', 'name'], 184 'triggerids' => array_keys($problems_by_triggerid), 185 'lastChangeSince' => $this->time_from, 186 'preservekeys' => true 187 ]); 188 189 foreach ($problems_by_triggerid as $triggerid => $notification_eventids) { 190 $trigger = $triggers[$triggerid]; 191 192 $url_problems = (new CUrl('zabbix.php')) 193 ->setArgument('action', 'problem.view') 194 ->setArgument('filter_name', '') 195 ->setArgument('hostids[]', $trigger['hosts'][0]['hostid']) 196 ->getUrl(); 197 198 $url_events = (new CUrl('zabbix.php')) 199 ->setArgument('action', 'problem.view') 200 ->setArgument('filter_name', '') 201 ->setArgument('triggerids[]', $triggerid) 202 ->getUrl(); 203 204 $url_trigger_events_pt = (new CUrl('tr_events.php'))->setArgument('triggerid', $triggerid); 205 206 foreach ($notification_eventids as $eventid) { 207 $notification = &$this->notifications[$eventid]; 208 209 $url_trigger_events = $url_trigger_events_pt 210 ->setArgument('eventid', $notification['eventid']) 211 ->getUrl(); 212 213 $notification += [ 214 'title' => sprintf('[url=%s]%s[/url]', $url_problems, 215 CHtml::encode($trigger['hosts'][0]['name']) 216 ), 217 'body' => [ 218 '[url='.$url_events.']'.CHtml::encode($notification['name']).'[/url]', 219 '[url='.$url_trigger_events.']'. 220 zbx_date2str(DATE_TIME_FORMAT_SECONDS, $notification['clock']). 221 '[/url]' 222 ] 223 ]; 224 } 225 } 226 } 227 228 $this->notifications = array_values($this->notifications); 229 } 230 231 protected function makeResponseData() { 232 CArrayHelper::sort($this->notifications, [ 233 ['field' => 'clock', 'order' => ZBX_SORT_DOWN], 234 ['field' => 'severity', 'order' => ZBX_SORT_DOWN], 235 ['field' => 'eventid', 'order' => ZBX_SORT_DOWN] 236 ]); 237 238 $this->notifications = array_values($this->notifications); 239 240 foreach ($this->notifications as &$notification) { 241 unset($notification['clock']); 242 unset($notification['name']); 243 if (!array_key_exists('title', $notification)) { 244 unset($notification['severity']); 245 } 246 } 247 unset($notification); 248 249 return json_encode([ 250 'notifications' => $this->notifications, 251 'settings' => [ 252 'enabled' => (bool) $this->settings['enabled'], 253 'alarm_timeout' => (int) $this->settings['sounds.repeat'], 254 'msg_recovery_timeout' => $this->settings['ok_timeout'], 255 'msg_timeout' => $this->settings['timeout'], 256 'muted' => (bool) $this->settings['sounds.mute'], 257 'severity_styles' => [ 258 -1 => ZBX_STYLE_NORMAL_BG, 259 TRIGGER_SEVERITY_AVERAGE => ZBX_STYLE_AVERAGE_BG, 260 TRIGGER_SEVERITY_DISASTER => ZBX_STYLE_DISASTER_BG, 261 TRIGGER_SEVERITY_HIGH => ZBX_STYLE_HIGH_BG, 262 TRIGGER_SEVERITY_INFORMATION => ZBX_STYLE_INFO_BG, 263 TRIGGER_SEVERITY_NOT_CLASSIFIED => ZBX_STYLE_NA_BG, 264 TRIGGER_SEVERITY_WARNING => ZBX_STYLE_WARNING_BG 265 ], 266 'files' => [ 267 -1 => $this->settings['sounds.recovery'], 268 TRIGGER_SEVERITY_AVERAGE => $this->settings['sounds.'.TRIGGER_SEVERITY_AVERAGE], 269 TRIGGER_SEVERITY_DISASTER => $this->settings['sounds.'.TRIGGER_SEVERITY_DISASTER], 270 TRIGGER_SEVERITY_HIGH => $this->settings['sounds.'.TRIGGER_SEVERITY_HIGH], 271 TRIGGER_SEVERITY_INFORMATION => $this->settings['sounds.'.TRIGGER_SEVERITY_INFORMATION], 272 TRIGGER_SEVERITY_NOT_CLASSIFIED => $this->settings['sounds.'.TRIGGER_SEVERITY_NOT_CLASSIFIED], 273 TRIGGER_SEVERITY_WARNING => $this->settings['sounds.'.TRIGGER_SEVERITY_WARNING] 274 ] 275 ] 276 ]); 277 } 278} 279