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 CScreenBuilder {
23
24	/**
25	 * Switch on/off flicker-free screens auto refresh.
26	 *
27	 * @var boolean
28	 */
29	public $isFlickerfree;
30
31	/**
32	 * Page file.
33	 *
34	 * @var string
35	 */
36	public $pageFile;
37
38	/**
39	 * Screen data
40	 *
41	 * @var array
42	 */
43	public $screen;
44
45	/**
46	 * Display mode
47	 *
48	 * @var int
49	 */
50	public $mode;
51
52	/**
53	 * @see Request timestamp
54	 */
55	public $timestamp;
56
57	/**
58	 * Host id
59	 *
60	 * @var string
61	 */
62	public $hostid;
63
64	/**
65	 * Profile table entity name #1
66	 *
67	 * @var string
68	 */
69	public $profileIdx;
70
71	/**
72	 * Profile table record id belongs to #1
73	 *
74	 * @var int
75	 */
76	public $profileIdx2;
77
78	/**
79	 * Time control timeline
80	 *
81	 * @var array
82	 */
83	public $timeline;
84
85	/**
86	 * Init screen data.
87	 *
88	 * @param array		$options
89	 * @param boolean	$options['isFlickerfree']
90	 * @param string	$options['pageFile']
91	 * @param int		$options['mode']
92	 * @param int		$options['timestamp']
93	 * @param int		$options['hostid']
94	 * @param string	$options['profileIdx']      Profile idx value.
95	 * @param int		$options['profileIdx2']     Profile idx2 value.
96	 * @param string	$options['from']            Start time of selected time period.
97	 * @param string	$options['to']              End time of selected time period.
98	 * @param array		$options['screen']
99	 */
100	public function __construct(array $options = []) {
101		$this->isFlickerfree = isset($options['isFlickerfree']) ? $options['isFlickerfree'] : true;
102		$this->mode = isset($options['mode']) ? $options['mode'] : SCREEN_MODE_SLIDESHOW;
103		$this->timestamp = !empty($options['timestamp']) ? $options['timestamp'] : time();
104		$this->hostid = !empty($options['hostid']) ? $options['hostid'] : null;
105
106		// get page file
107		if (!empty($options['pageFile'])) {
108			$this->pageFile = $options['pageFile'];
109		}
110		else {
111			global $page;
112			$this->pageFile = $page['file'];
113		}
114
115		// get screen
116		if (!empty($options['screen'])) {
117			$this->screen = $options['screen'];
118		}
119		elseif (array_key_exists('screenid', $options) && $options['screenid'] > 0) {
120			$this->screen = API::Screen()->get([
121				'screenids' => $options['screenid'],
122				'output' => API_OUTPUT_EXTEND,
123				'selectScreenItems' => API_OUTPUT_EXTEND,
124				'editable' => ($this->mode == SCREEN_MODE_EDIT)
125			]);
126
127			if (!empty($this->screen)) {
128				$this->screen = reset($this->screen);
129			}
130			else {
131				access_deny();
132			}
133		}
134
135		// calculate time
136		$this->profileIdx = !empty($options['profileIdx']) ? $options['profileIdx'] : '';
137		$this->profileIdx2 = !empty($options['profileIdx2']) ? $options['profileIdx2'] : null;
138
139		$this->timeline = getTimeSelectorPeriod([
140			'profileIdx' => $this->profileIdx,
141			'profileIdx2' => $this->profileIdx2,
142			'from' => array_key_exists('from', $options) ? $options['from'] : null,
143			'to' => array_key_exists('to', $options) ? $options['to'] : null
144		]);
145	}
146
147	/**
148	 * Get particular screen object.
149	 *
150	 * @static
151	 *
152	 * @param array		$options
153	 * @param int		$options['resourcetype']
154	 * @param int		$options['screenitemid']
155	 * @param int		$options['hostid']
156	 * @param array		$options['screen']
157	 * @param int		$options['screenid']
158	 *
159	 * @return CScreenBase
160	 */
161	public static function getScreen(array $options = []) {
162		if (!array_key_exists('resourcetype', $options)) {
163			$options['resourcetype'] = null;
164
165			// get resourcetype from screenitem
166			if (!array_key_exists('screenitem', $options) && array_key_exists('screenitemid', $options)) {
167				if (array_key_exists('hostid', $options) && $options['hostid'] > 0) {
168					$options['screenitem'] = API::TemplateScreenItem()->get([
169						'screenitemids' => $options['screenitemid'],
170						'hostids' => $options['hostid'],
171						'output' => API_OUTPUT_EXTEND
172					]);
173				}
174				else {
175					$options['screenitem'] = API::ScreenItem()->get([
176						'screenitemids' => $options['screenitemid'],
177						'output' => API_OUTPUT_EXTEND
178					]);
179				}
180				$options['screenitem'] = reset($options['screenitem']);
181			}
182
183			if (is_array($options['screenitem']) && array_key_exists('screenitem', $options)
184					&& array_key_exists('resourcetype', $options['screenitem'])) {
185				$options['resourcetype'] = $options['screenitem']['resourcetype'];
186			}
187			else {
188				return null;
189			}
190		}
191
192		if ($options['resourcetype'] === null) {
193			return null;
194		}
195
196		// get screen
197		switch ($options['resourcetype']) {
198			case SCREEN_RESOURCE_GRAPH:
199				return new CScreenGraph($options);
200
201			case SCREEN_RESOURCE_SIMPLE_GRAPH:
202				return new CScreenSimpleGraph($options);
203
204			case SCREEN_RESOURCE_MAP:
205				return new CScreenMap($options);
206
207			case SCREEN_RESOURCE_PLAIN_TEXT:
208				return new CScreenPlainText($options);
209
210			case SCREEN_RESOURCE_HOST_INFO:
211				return new CScreenHostsInfo($options);
212
213			case SCREEN_RESOURCE_TRIGGER_INFO:
214				return new CScreenTriggersInfo($options);
215
216			case SCREEN_RESOURCE_SERVER_INFO:
217				return new CScreenServerInfo($options);
218
219			case SCREEN_RESOURCE_CLOCK:
220				return new CScreenClock($options);
221
222			case SCREEN_RESOURCE_TRIGGER_OVERVIEW:
223				return new CScreenTriggersOverview($options);
224
225			case SCREEN_RESOURCE_DATA_OVERVIEW:
226				return new CScreenDataOverview($options);
227
228			case SCREEN_RESOURCE_URL:
229				$options = self::appendTemplatedScreenOption($options);
230				return new CScreenUrl($options);
231
232			case SCREEN_RESOURCE_ACTIONS:
233				return new CScreenActions($options);
234
235			case SCREEN_RESOURCE_EVENTS:
236				return new CScreenEvents($options);
237
238			case SCREEN_RESOURCE_HOSTGROUP_TRIGGERS:
239				return new CScreenHostgroupTriggers($options);
240
241			case SCREEN_RESOURCE_SYSTEM_STATUS:
242				return new CScreenSystemStatus($options);
243
244			case SCREEN_RESOURCE_HOST_TRIGGERS:
245				return new CScreenHostTriggers($options);
246
247			case SCREEN_RESOURCE_HISTORY:
248				return new CScreenHistory($options);
249
250			case SCREEN_RESOURCE_LLD_GRAPH:
251				$options = self::appendTemplatedScreenOption($options);
252				return new CScreenLldGraph($options);
253
254			case SCREEN_RESOURCE_LLD_SIMPLE_GRAPH:
255				$options = self::appendTemplatedScreenOption($options);
256				return new CScreenLldSimpleGraph($options);
257
258			case SCREEN_RESOURCE_HTTPTEST_DETAILS:
259				return new CScreenHttpTestDetails($options);
260
261			case SCREEN_RESOURCE_DISCOVERY:
262				return new CScreenDiscovery($options);
263
264			case SCREEN_RESOURCE_HTTPTEST:
265				return new CScreenHttpTest($options);
266
267			case SCREEN_RESOURCE_PROBLEM:
268				return new CScreenProblem($options);
269
270			default:
271				return null;
272		}
273	}
274
275	/**
276	 * Appends boolean option 'isTemplatedScreen' to output options.
277	 *
278	 * @param array $options
279	 *
280	 * @return array
281	 */
282	protected static function appendTemplatedScreenOption(array $options) {
283		if (array_key_exists('screen', $options)) {
284			$options['isTemplatedScreen'] = (bool) array_key_exists('templateid', $options['screen']);
285		}
286		elseif (array_key_exists('screenid', $options) && $options['screenid'] > 0) {
287			$options['isTemplatedScreen'] = (bool) API::TemplateScreen()->get([
288				'screenids' => [$options['screenid']],
289				'output' => []
290			]);
291		}
292
293		return $options;
294	}
295
296	/**
297	 * Process screen with particular screen objects.
298	 *
299	 * @return CTable
300	 */
301	public function show() {
302		if (empty($this->screen)) {
303			return new CTableInfo();
304		}
305
306		$skipedFields = [];
307		$screenitems = [];
308		$emptyScreenColumns = [];
309
310		// calculate table columns and rows
311		foreach ($this->screen['screenitems'] as $screenitem) {
312			$screenitems[] = $screenitem;
313
314			for ($i = 0; $i < $screenitem['rowspan'] || $i == 0; $i++) {
315				for ($j = 0; $j < $screenitem['colspan'] || $j == 0; $j++) {
316					if ($i != 0 || $j != 0) {
317						if (!isset($skipedFields[$screenitem['y'] + $i])) {
318							$skipedFields[$screenitem['y'] + $i] = [];
319						}
320						$skipedFields[$screenitem['y'] + $i][$screenitem['x'] + $j] = 1;
321					}
322				}
323			}
324		}
325
326		// create screen table
327		$screenTable = (new CTable())
328			->setId(self::makeScreenTableId($this->screen['screenid']))
329			->addClass(ZBX_STYLE_SCREEN_TABLE);
330
331		if ($this->mode == SCREEN_MODE_EDIT) {
332			$screenTable->addClass(ZBX_STYLE_DASHED_BORDER);
333		}
334
335		// action top row
336		if ($this->mode == SCREEN_MODE_EDIT) {
337			$newColumns = [(new CCol())->addClass(ZBX_STYLE_CELL_WIDTH)];
338
339			for ($i = 0, $size = $this->screen['hsize']; $i < $size; $i++) {
340				if ($this->screen['hsize'] >= SCREEN_MAX_SIZE) {
341					$link = (new CDiv('+'))
342						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
343						->addClass(ZBX_STYLE_DISABLED);
344				}
345				else {
346					$link = (new CLink('+', 'screenedit.php?screenid='.$this->screen['screenid'].
347						url_param('templateid').'&add_col='.$i
348					))
349						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
350						->addSID();
351				}
352
353				$newColumns[] = (new CCol($link))
354					->addClass(ZBX_STYLE_CENTER)
355					->addClass(ZBX_STYLE_MIDDLE);
356			}
357
358			if ($this->screen['hsize'] >= SCREEN_MAX_SIZE) {
359				$link = (new CDiv('+'))
360					->addClass(ZBX_STYLE_TREEVIEW_PLUS)
361					->addClass(ZBX_STYLE_DISABLED);
362			}
363			else {
364				$link = (new CLink('+', 'screenedit.php?screenid='.$this->screen['screenid'].url_param('templateid').
365					'&add_col='.$this->screen['hsize']
366				))
367					->addClass(ZBX_STYLE_TREEVIEW_PLUS)
368					->addSID();
369			}
370
371			$newColumns[] = (new CCol($link))
372				->addClass(ZBX_STYLE_CENTER)
373				->addClass(ZBX_STYLE_MIDDLE)
374				->addClass(ZBX_STYLE_CELL_WIDTH);
375
376			$screenTable->addRow($newColumns);
377		}
378
379		for ($r = 0; $r < $this->screen['vsize']; $r++) {
380			$newColumns = [];
381			$emptyScreenRow = true;
382
383			// action left cell
384			if ($this->mode == SCREEN_MODE_EDIT) {
385				if ($this->screen['vsize'] >= SCREEN_MAX_SIZE) {
386					$link = (new CDiv('+'))
387						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
388						->addClass(ZBX_STYLE_DISABLED);
389				}
390				else {
391					$link = (new CLink('+', 'screenedit.php?screenid='.$this->screen['screenid'].
392						url_param('templateid').'&add_row='.$r
393					))
394						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
395						->addSID();
396				}
397
398				$newColumns[] = (new CCol($link))
399					->addClass(ZBX_STYLE_CENTER)
400					->addClass(ZBX_STYLE_MIDDLE);
401			}
402
403			for ($c = 0; $c < $this->screen['hsize']; $c++) {
404				if (isset($skipedFields[$r][$c])) {
405					continue;
406				}
407
408				// screen item
409				$isEditForm = false;
410				$screenitem = [];
411
412				foreach ($screenitems as $tmprow) {
413					if ($tmprow['x'] == $c && $tmprow['y'] == $r) {
414						$screenitem = $tmprow;
415						break;
416					}
417				}
418
419				if (empty($screenitem)) {
420					$screenitem = [
421						'screenitemid' => 0,
422						'resourcetype' => 0,
423						'resourceid' => 0,
424						'width' => 0,
425						'height' => 0,
426						'colspan' => 1,
427						'rowspan' => 1,
428						'elements' => 0,
429						'valign' => VALIGN_DEFAULT,
430						'halign' => HALIGN_DEFAULT,
431						'style' => 0,
432						'url' => '',
433						'dynamic' => 0,
434						'sort_triggers' => SCREEN_SORT_TRIGGERS_DATE_DESC
435					];
436				}
437
438				if (!empty($screenitem['screenitemid'])) {
439					$emptyScreenRow = false;
440					$emptyScreenColumns[$c] = true;
441				}
442
443				// action
444				if ($this->mode == SCREEN_MODE_EDIT) {
445					if ($screenitem['screenitemid'] != 0) {
446						$action = 'screenedit.php?form=update'.url_params(['screenid', 'templateid']).
447							'&screenitemid='.$screenitem['screenitemid'];
448					}
449					else {
450						$action = 'screenedit.php?form=update'.url_params(['screenid', 'templateid']).'&x='.$c.'&y='.$r;
451					}
452				}
453				else {
454					$action = null;
455				}
456
457				// edit form cell
458				if ($this->mode == SCREEN_MODE_EDIT
459						&& (isset($_REQUEST['form']) && $_REQUEST['form'] == 'update')
460						&& ((isset($_REQUEST['x']) && $_REQUEST['x'] == $c && isset($_REQUEST['y']) && $_REQUEST['y'] == $r)
461								|| (isset($_REQUEST['screenitemid']) && bccomp($_REQUEST['screenitemid'], $screenitem['screenitemid']) == 0))) {
462					$item = new CObject(
463						(new CView('monitoring.screen.constructor.edit', ['screen' => $this->screen]))->getOutput()
464					);
465					$isEditForm = true;
466				}
467				// screen cell
468				elseif (!empty($screenitem['screenitemid']) && isset($screenitem['resourcetype'])) {
469					$screenBase = CScreenBuilder::getScreen([
470						'screen' => $this->screen,
471						'screenid' => $this->screen['screenid'],
472						'isFlickerfree' => $this->isFlickerfree,
473						'pageFile' => $this->pageFile,
474						'mode' => $this->mode,
475						'timestamp' => $this->timestamp,
476						'hostid' => $this->hostid,
477						'profileIdx' => $this->profileIdx,
478						'profileIdx2' => $this->profileIdx2,
479						'timeline' => $this->timeline,
480						'resourcetype' => $screenitem['resourcetype'],
481						'screenitem' => $screenitem
482					]);
483
484					if (!empty($screenBase)) {
485						$screenBase->action = $action;
486
487						$item = $screenBase->get();
488					}
489					else {
490						$item = null;
491					}
492				}
493				// change/empty cell
494				elseif ($this->mode == SCREEN_MODE_EDIT) {
495					$item =[
496						(new CDiv(
497							(new CLink(_x('Change', 'verb'), $action))->addClass('empty_change_link')
498						))->addClass(ZBX_STYLE_CENTER)
499					];
500				}
501				else {
502					$item = null;
503				}
504
505				if ($this->mode == SCREEN_MODE_EDIT && !$isEditForm) {
506					$item = (new CDiv($item))
507						->addClass('draggable')
508						->setId('position_'.$r.'_'.$c)
509						->setAttribute('data-xcoord', $c)
510						->setAttribute('data-ycoord', $r);
511				}
512
513				// colspan/rowspan
514				$newColumn = (new CCol($item))->addClass('screenitem');
515
516				if ($screenitem['halign'] == HALIGN_CENTER || $isEditForm) {
517					$newColumn->addClass(ZBX_STYLE_CENTER);
518				}
519				elseif ($screenitem['halign'] == HALIGN_LEFT) {
520					$newColumn->addClass(ZBX_STYLE_LEFT);
521				}
522				elseif ($screenitem['halign'] == HALIGN_RIGHT) {
523					$newColumn->addClass(ZBX_STYLE_RIGHT);
524				}
525
526				if ($screenitem['valign'] == VALIGN_MIDDLE || $isEditForm) {
527					$newColumn->addClass(ZBX_STYLE_MIDDLE);
528				}
529				elseif ($screenitem['valign'] == VALIGN_TOP) {
530					$newColumn->addClass(ZBX_STYLE_TOP);
531				}
532				elseif ($screenitem['valign'] == VALIGN_BOTTOM) {
533					$newColumn->addClass(ZBX_STYLE_BOTTOM);
534				}
535
536				if ($screenitem['colspan'] > 1) {
537					$newColumn->setColSpan($screenitem['colspan']);
538				}
539				if ($screenitem['rowspan'] > 1) {
540					$newColumn->setRowSpan($screenitem['rowspan']);
541				}
542				$newColumns[] = $newColumn;
543			}
544
545			// action right cell
546			if ($this->mode == SCREEN_MODE_EDIT) {
547				if ($this->screen['vsize'] == SCREEN_MIN_SIZE) {
548					$link = (new CDiv('−'))
549						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
550						->addClass(ZBX_STYLE_DISABLED);
551				}
552				else {
553					$link = (new CLink('−', 'screenedit.php?screenid='.$this->screen['screenid'].
554						url_param('templateid').'&rmv_row='.$r
555					))
556						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
557						->addSID();
558					if (!$emptyScreenRow) {
559						$link->addConfirmation(_('This screen row is not empty. Delete it?'));
560					}
561				}
562
563				$newColumns[] = (new CCol($link))
564					->addClass(ZBX_STYLE_CENTER)
565					->addClass(ZBX_STYLE_MIDDLE);
566			}
567			$screenTable->addRow(new CRow($newColumns));
568		}
569
570		// action bottom row
571		if ($this->mode == SCREEN_MODE_EDIT) {
572			if ($this->screen['vsize'] >= SCREEN_MAX_SIZE) {
573				$link = (new CDiv('+'))
574					->addClass(ZBX_STYLE_TREEVIEW_PLUS)
575					->addClass(ZBX_STYLE_DISABLED);
576			}
577			else {
578				$link = (new CLink('+', 'screenedit.php?screenid='.$this->screen['screenid'].url_param('templateid').
579					'&add_row='.$this->screen['vsize']
580				))
581					->addClass(ZBX_STYLE_TREEVIEW_PLUS)
582					->addSID();
583			}
584
585			$newColumns = [
586				(new CCol($link))
587					->addClass(ZBX_STYLE_CENTER)
588					->addClass(ZBX_STYLE_MIDDLE)
589			];
590
591			for ($i = 0; $i < $this->screen['hsize']; $i++) {
592				if ($this->screen['hsize'] == SCREEN_MIN_SIZE) {
593					$link = (new CDiv('−'))
594						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
595						->addClass(ZBX_STYLE_DISABLED);
596				}
597				else {
598					$link = (new CLink('−', 'screenedit.php?screenid='.$this->screen['screenid'].
599						url_param('templateid').'&rmv_col='.$i
600					))
601						->addClass(ZBX_STYLE_TREEVIEW_PLUS)
602						->addSID();
603
604					if (array_key_exists($i, $emptyScreenColumns)) {
605						$link->addConfirmation(_('This screen column is not empty. Delete it?'));
606					}
607				}
608
609				$newColumns[] = (new CCol($link))
610					->addClass(ZBX_STYLE_CENTER)
611					->addClass(ZBX_STYLE_MIDDLE);
612			}
613
614			$newColumns[] = '';
615			$screenTable->addRow($newColumns);
616		}
617
618		return $screenTable;
619	}
620
621	/**
622	 * Insert javascript to create scroll in time control.
623	 *
624	 * @static
625	 *
626	 * @param array $timeline
627	 */
628	private static function insertScreenScrollJs(array $timeline) {
629		$obj_data = [
630			'id' => 'scrollbar',
631			'mainObject' => 1
632		];
633
634		zbx_add_post_js('timeControl.addObject("scrollbar", '.zbx_jsvalue($timeline).', '.zbx_jsvalue($obj_data).');');
635	}
636
637	/**
638	 * Insert javascript to init screens.
639	 *
640	 * @static
641	 *
642	 * @param string $screenid
643	 */
644	public static function insertInitScreenJs($screenid) {
645		zbx_add_post_js('init_screen("'.$screenid.'", "'.self::makeScreenTableId($screenid).'", "'.$screenid.'");');
646	}
647
648	/**
649	 * Insert javascript to start time control rendering.
650	 *
651	 * @static
652	 */
653	public static function insertProcessObjectsJs() {
654		zbx_add_post_js('timeControl.processObjects();');
655	}
656
657	/**
658	 * Insert javascript to clean all screen items.
659	 *
660	 * @static
661	 */
662	public static function insertScreenCleanJs() {
663		zbx_add_post_js('window.flickerfreeScreen.cleanAll();');
664	}
665
666	/**
667	 * Insert javascript for standard screens.
668	 *
669	 * @param array $timeline
670	 *
671	 * @static
672	 */
673	public static function insertScreenStandardJs(array $timeline) {
674		CScreenBuilder::insertScreenScrollJs($timeline);
675		CScreenBuilder::insertProcessObjectsJs();
676	}
677
678	/**
679	 * Creates a string for screen table ID attribute.
680	 *
681	 * @param string $screenId
682	 *
683	 * @return string
684	 */
685	protected static function makeScreenTableId($screenId) {
686		return 'screentable_'.$screenId;
687	}
688}
689