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
22require_once dirname(__FILE__).'/CAutoloader.php';
23
24class ZBase {
25	const EXEC_MODE_DEFAULT = 'default';
26	const EXEC_MODE_SETUP = 'setup';
27	const EXEC_MODE_API = 'api';
28
29	/**
30	 * An instance of the current Z object.
31	 *
32	 * @var Z
33	 */
34	protected static $instance;
35
36	/**
37	 * The absolute path to the root directory.
38	 *
39	 * @var string
40	 */
41	protected $rootDir;
42
43	/**
44	 * @var array of config data from zabbix config file
45	 */
46	protected $config = [];
47
48	/**
49	 * Returns the current instance of Z.
50	 *
51	 * @static
52	 *
53	 * @return Z
54	 */
55	public static function getInstance() {
56		if (self::$instance === null) {
57			self::$instance = new Z();
58		}
59
60		return self::$instance;
61	}
62
63	/**
64	 * Init modules required to run frontend.
65	 */
66	protected function init() {
67		$this->rootDir = $this->findRootDir();
68		$this->registerAutoloader();
69
70		// initialize API classes
71		$apiServiceFactory = new CApiServiceFactory();
72
73		$client = new CLocalApiClient();
74		$client->setServiceFactory($apiServiceFactory);
75		$wrapper = new CFrontendApiWrapper($client);
76		$wrapper->setProfiler(CProfiler::getInstance());
77		API::setWrapper($wrapper);
78		API::setApiServiceFactory($apiServiceFactory);
79
80		// system includes
81		require_once $this->getRootDir().'/include/debug.inc.php';
82		require_once $this->getRootDir().'/include/gettextwrapper.inc.php';
83		require_once $this->getRootDir().'/include/defines.inc.php';
84		require_once $this->getRootDir().'/include/func.inc.php';
85		require_once $this->getRootDir().'/include/html.inc.php';
86		require_once $this->getRootDir().'/include/perm.inc.php';
87		require_once $this->getRootDir().'/include/audit.inc.php';
88		require_once $this->getRootDir().'/include/js.inc.php';
89		require_once $this->getRootDir().'/include/users.inc.php';
90		require_once $this->getRootDir().'/include/validate.inc.php';
91		require_once $this->getRootDir().'/include/profiles.inc.php';
92		require_once $this->getRootDir().'/include/locales.inc.php';
93		require_once $this->getRootDir().'/include/db.inc.php';
94
95		// page specific includes
96		require_once $this->getRootDir().'/include/actions.inc.php';
97		require_once $this->getRootDir().'/include/discovery.inc.php';
98		require_once $this->getRootDir().'/include/draw.inc.php';
99		require_once $this->getRootDir().'/include/events.inc.php';
100		require_once $this->getRootDir().'/include/graphs.inc.php';
101		require_once $this->getRootDir().'/include/hostgroups.inc.php';
102		require_once $this->getRootDir().'/include/hosts.inc.php';
103		require_once $this->getRootDir().'/include/httptest.inc.php';
104		require_once $this->getRootDir().'/include/ident.inc.php';
105		require_once $this->getRootDir().'/include/images.inc.php';
106		require_once $this->getRootDir().'/include/items.inc.php';
107		require_once $this->getRootDir().'/include/maintenances.inc.php';
108		require_once $this->getRootDir().'/include/maps.inc.php';
109		require_once $this->getRootDir().'/include/media.inc.php';
110		require_once $this->getRootDir().'/include/services.inc.php';
111		require_once $this->getRootDir().'/include/sounds.inc.php';
112		require_once $this->getRootDir().'/include/triggers.inc.php';
113		require_once $this->getRootDir().'/include/valuemap.inc.php';
114	}
115
116	/**
117	 * Initializes the application.
118	 */
119	public function run($mode) {
120		$this->init();
121
122		$this->setMaintenanceMode();
123		set_error_handler('zbx_err_handler');
124
125		switch ($mode) {
126			case self::EXEC_MODE_DEFAULT:
127				if (getRequest('action', '') === 'notifications.get') {
128					CWebUser::disableSessionExtension();
129				}
130
131				$this->loadConfigFile();
132				$this->initDB();
133				$this->authenticateUser();
134				$this->initLocales(CWebUser::$data);
135				$this->setLayoutModeByUrl();
136				break;
137
138			case self::EXEC_MODE_API:
139				$this->loadConfigFile();
140				$this->initDB();
141				$this->initLocales(['lang' => 'en_gb']);
142				break;
143
144			case self::EXEC_MODE_SETUP:
145				try {
146					// try to load config file, if it exists we need to init db and authenticate user to check permissions
147					$this->loadConfigFile();
148					$this->initDB();
149					$this->authenticateUser();
150					$this->initLocales(CWebUser::$data);
151				}
152				catch (ConfigFileException $e) {}
153				break;
154		}
155
156		// new MVC processing, otherwise we continue execution old style
157		if (hasRequest('action')) {
158			$router = new CRouter(getRequest('action'));
159
160			if ($router->getController() !== null) {
161				CProfiler::getInstance()->start();
162				$this->processRequest($router);
163				exit;
164			}
165		}
166	}
167
168	/**
169	 * Returns the absolute path to the root dir.
170	 *
171	 * @return string
172	 */
173	public static function getRootDir() {
174		return self::getInstance()->rootDir;
175	}
176
177	/**
178	 * Returns the path to the frontend's root dir.
179	 *
180	 * @return string
181	 */
182	private function findRootDir() {
183		return realpath(dirname(__FILE__).'/../../..');
184	}
185
186	/**
187	 * Register autoloader.
188	 */
189	private function registerAutoloader() {
190		$autoloader = new CAutoloader($this->getIncludePaths());
191		$autoloader->register();
192	}
193
194	/**
195	 * An array of directories to add to the autoloader include paths.
196	 *
197	 * @return array
198	 */
199	private function getIncludePaths() {
200		return [
201			$this->rootDir.'/include/classes/core',
202			$this->rootDir.'/include/classes/mvc',
203			$this->rootDir.'/include/classes/api',
204			$this->rootDir.'/include/classes/api/services',
205			$this->rootDir.'/include/classes/api/managers',
206			$this->rootDir.'/include/classes/api/clients',
207			$this->rootDir.'/include/classes/api/wrappers',
208			$this->rootDir.'/include/classes/db',
209			$this->rootDir.'/include/classes/debug',
210			$this->rootDir.'/include/classes/validators',
211			$this->rootDir.'/include/classes/validators/schema',
212			$this->rootDir.'/include/classes/validators/string',
213			$this->rootDir.'/include/classes/validators/object',
214			$this->rootDir.'/include/classes/validators/hostgroup',
215			$this->rootDir.'/include/classes/validators/host',
216			$this->rootDir.'/include/classes/validators/hostprototype',
217			$this->rootDir.'/include/classes/validators/event',
218			$this->rootDir.'/include/classes/export',
219			$this->rootDir.'/include/classes/export/writers',
220			$this->rootDir.'/include/classes/export/elements',
221			$this->rootDir.'/include/classes/graph',
222			$this->rootDir.'/include/classes/graphdraw',
223			$this->rootDir.'/include/classes/import',
224			$this->rootDir.'/include/classes/import/converters',
225			$this->rootDir.'/include/classes/import/importers',
226			$this->rootDir.'/include/classes/import/preprocessors',
227			$this->rootDir.'/include/classes/import/readers',
228			$this->rootDir.'/include/classes/import/validators',
229			$this->rootDir.'/include/classes/items',
230			$this->rootDir.'/include/classes/triggers',
231			$this->rootDir.'/include/classes/server',
232			$this->rootDir.'/include/classes/screens',
233			$this->rootDir.'/include/classes/services',
234			$this->rootDir.'/include/classes/sysmaps',
235			$this->rootDir.'/include/classes/helpers',
236			$this->rootDir.'/include/classes/helpers/trigger',
237			$this->rootDir.'/include/classes/macros',
238			$this->rootDir.'/include/classes/tree',
239			$this->rootDir.'/include/classes/html',
240			$this->rootDir.'/include/classes/html/pageheader',
241			$this->rootDir.'/include/classes/html/svg',
242			$this->rootDir.'/include/classes/html/widget',
243			$this->rootDir.'/include/classes/html/interfaces',
244			$this->rootDir.'/include/classes/parsers',
245			$this->rootDir.'/include/classes/parsers/results',
246			$this->rootDir.'/include/classes/controllers',
247			$this->rootDir.'/include/classes/routing',
248			$this->rootDir.'/include/classes/json',
249			$this->rootDir.'/include/classes/user',
250			$this->rootDir.'/include/classes/setup',
251			$this->rootDir.'/include/classes/regexp',
252			$this->rootDir.'/include/classes/ldap',
253			$this->rootDir.'/include/classes/pagefilter',
254			$this->rootDir.'/include/classes/widgets/fields',
255			$this->rootDir.'/include/classes/widgets/forms',
256			$this->rootDir.'/include/classes/widgets',
257			$this->rootDir.'/local/app/controllers',
258			$this->rootDir.'/app/controllers'
259		];
260	}
261
262	/**
263	 * An array of available themes.
264	 *
265	 * @return array
266	 */
267	public static function getThemes() {
268		return [
269			'blue-theme' => _('Blue'),
270			'dark-theme' => _('Dark'),
271			'hc-light' => _('High-contrast light'),
272			'hc-dark' => _('High-contrast dark')
273		];
274	}
275
276	/**
277	 * Check if maintenance mode is enabled.
278	 *
279	 * @throws Exception
280	 */
281	protected function setMaintenanceMode() {
282		require_once $this->getRootDir().'/conf/maintenance.inc.php';
283
284		if (defined('ZBX_DENY_GUI_ACCESS')) {
285			$user_ip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']))
286					? $_SERVER['HTTP_X_FORWARDED_FOR']
287					: $_SERVER['REMOTE_ADDR'];
288			if (!isset($ZBX_GUI_ACCESS_IP_RANGE) || !in_array($user_ip, $ZBX_GUI_ACCESS_IP_RANGE)) {
289				throw new Exception($_REQUEST['warning_msg']);
290			}
291		}
292	}
293
294	/**
295	 * Load zabbix config file.
296	 */
297	protected function loadConfigFile() {
298		$configFile = $this->getRootDir().CConfigFile::CONFIG_FILE_PATH;
299		$config = new CConfigFile($configFile);
300		$this->config = $config->load();
301	}
302
303	/**
304	 * Check if frontend can connect to DB.
305	 * @throws DBException
306	 */
307	protected function initDB() {
308		$error = null;
309		if (!DBconnect($error)) {
310			throw new DBException($error);
311		}
312	}
313
314	/**
315	 * Initialize translations.
316	 *
317	 * @param array  $user_data          Array of user data.
318	 * @param string $user_data['lang']  Language.
319	 */
320	protected function initLocales(array $user_data) {
321		init_mbstrings();
322
323		$defaultLocales = [
324			'C', 'POSIX', 'en', 'en_US', 'en_US.UTF-8', 'English_United States.1252', 'en_GB', 'en_GB.UTF-8'
325		];
326
327		if (function_exists('bindtextdomain')) {
328			// initializing gettext translations depending on language selected by user
329			$locales = zbx_locale_variants($user_data['lang']);
330			$locale_found = false;
331			foreach ($locales as $locale) {
332				// since LC_MESSAGES may be unavailable on some systems, try to set all of the locales
333				// and then revert some of them back
334				putenv('LC_ALL='.$locale);
335				putenv('LANG='.$locale);
336				putenv('LANGUAGE='.$locale);
337				setlocale(LC_TIME, $locale);
338
339				if (setlocale(LC_ALL, $locale)) {
340					$locale_found = true;
341					break;
342				}
343			}
344
345			// reset the LC_CTYPE locale so that case transformation functions would work correctly
346			// it is also required for PHP to work with the Turkish locale (https://bugs.php.net/bug.php?id=18556)
347			// WARNING: this must be done before executing any other code, otherwise code execution could fail!
348			// this will be unnecessary in PHP 5.5
349			setlocale(LC_CTYPE, $defaultLocales);
350
351			if (!$locale_found && $user_data['lang'] != 'en_GB' && $user_data['lang'] != 'en_gb') {
352				error('Locale for language "'.$user_data['lang'].'" is not found on the web server. Tried to set: '.implode(', ', $locales).'. Unable to translate Zabbix interface.');
353			}
354			bindtextdomain('frontend', 'locale');
355			bind_textdomain_codeset('frontend', 'UTF-8');
356			textdomain('frontend');
357		}
358
359		// reset the LC_NUMERIC locale so that PHP would always use a point instead of a comma for decimal numbers
360		setlocale(LC_NUMERIC, $defaultLocales);
361
362		// should be after locale initialization
363		require_once $this->getRootDir().'/include/translateDefines.inc.php';
364	}
365
366	/**
367	 * Authenticate user.
368	 */
369	protected function authenticateUser() {
370		$sessionid = CWebUser::checkAuthentication(CWebUser::getSessionCookie());
371
372		if (!$sessionid) {
373			CWebUser::setDefault();
374		}
375
376		// set the authentication token for the API
377		API::getWrapper()->auth = $sessionid;
378
379		// enable debug mode in the API
380		API::getWrapper()->debug = CWebUser::getDebugMode();
381	}
382
383	/**
384	 * Process request and generate response. Main entry for all processing.
385	 *
386	 * @param CRouter $rourer
387	 */
388	private function processRequest(CRouter $router) {
389		$controller = $router->getController();
390
391		/** @var \CController $controller */
392		$controller = new $controller();
393		$controller->setAction($router->getAction());
394		$response = $controller->run();
395
396		// Controller returned data
397		if ($response instanceof CControllerResponseData) {
398			// if no view defined we pass data directly to layout
399			if ($router->getView() === null || !$response->isViewEnabled()) {
400				$layout = new CView($router->getLayout(), $response->getData());
401				echo $layout->getOutput();
402			}
403			else {
404				$view = new CView($router->getView(), $response->getData());
405				$data['page']['title'] = $response->getTitle();
406				$data['page']['file'] = $response->getFileName();
407				$data['controller']['action'] = $router->getAction();
408				$data['main_block'] = $view->getOutput();
409				$data['javascript']['files'] = $view->getAddedJS();
410				$data['javascript']['pre'] = $view->getIncludedJS();
411				$data['javascript']['post'] = $view->getPostJS();
412				$layout = new CView($router->getLayout(), $data);
413				echo $layout->getOutput();
414			}
415		}
416		// Controller returned redirect to another page
417		else if ($response instanceof CControllerResponseRedirect) {
418			header('Content-Type: text/html; charset=UTF-8');
419			if ($response->getMessageOk() !== null) {
420				CSession::setValue('messageOk', $response->getMessageOk());
421			}
422			if ($response->getMessageError() !== null) {
423				CSession::setValue('messageError', $response->getMessageError());
424			}
425			global $ZBX_MESSAGES;
426			if (isset($ZBX_MESSAGES)) {
427				CSession::setValue('messages', $ZBX_MESSAGES);
428			}
429			if ($response->getFormData() !== null) {
430				CSession::setValue('formData', $response->getFormData());
431			}
432
433			redirect($response->getLocation());
434		}
435		// Controller returned fatal error
436		else if ($response instanceof CControllerResponseFatal) {
437			header('Content-Type: text/html; charset=UTF-8');
438
439			global $ZBX_MESSAGES;
440			$messages = (isset($ZBX_MESSAGES) && $ZBX_MESSAGES) ? filter_messages($ZBX_MESSAGES) : [];
441			foreach ($messages as $message) {
442				$response->addMessage($message['message']);
443			}
444
445			$response->addMessage('Controller: '.$router->getAction());
446			ksort($_REQUEST);
447			foreach ($_REQUEST as $key => $value) {
448				// do not output SID
449				if ($key != 'sid') {
450					$response->addMessage(is_scalar($value) ? $key.': '.$value : $key.': '.gettype($value));
451				}
452			}
453			CSession::setValue('messages', $response->getMessages());
454
455			redirect('zabbix.php?action=system.warning');
456		}
457	}
458
459	/**
460	 * Set layout to fullscreen or kiosk mode if URL contains 'fullscreen' and/or 'kiosk' arguments.
461	 */
462	private function setLayoutModeByUrl() {
463		if (array_key_exists('kiosk', $_GET) && $_GET['kiosk'] === '1') {
464			CView::setLayoutMode(ZBX_LAYOUT_KIOSKMODE);
465		}
466		elseif (array_key_exists('fullscreen', $_GET)) {
467			CView::setLayoutMode($_GET['fullscreen'] === '1' ? ZBX_LAYOUT_FULLSCREEN : ZBX_LAYOUT_NORMAL);
468		}
469
470		// Remove $_GET arguments to prevent CUrl from generating URL with 'fullscreen'/'kiosk' arguments.
471		unset($_GET['fullscreen'], $_GET['kiosk']);
472	}
473}
474