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