Completed
Branch master (b1f4b2)
by Nathan
19:58
created

Framework::_print_menu()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware API - framework baseclass
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> rewrite in 12/2006
7
 * @author Pim Snel <[email protected]> author of the idots template set
8
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
9
 * @package api
10
 * @subpackage framework
11
 * @access public
12
 * @version $Id$
13
 */
14
15
namespace EGroupware\Api;
16
17
/**
18
 * Framework: virtual base class for all template sets
19
 *
20
 * This class creates / renders the eGW framework:
21
 *  a) Html header
22
 *  b) navbar
23
 *  c) sidebox menu
24
 *  d) main application area
25
 *  e) footer
26
 * It replaces several methods in the common class and the diverse templates.
27
 *
28
 * Existing apps either set $GLOBALS['egw_info']['flags']['noheader'] and call common::egw_header() and
29
 * (if $GLOBALS['egw_info']['flags']['nonavbar'] is true) parse_navbar() or it's done by the header.inc.php include.
30
 * The app's hook_sidebox then calls the public function display_sidebox().
31
 * And the app calls common::egw_footer().
32
 *
33
 * This are the authors (and their copyrights) of the original egw_header, egw_footer methods of the common class:
34
 * This file written by Dan Kuykendall <[email protected]>
35
 * and Joseph Engo <[email protected]>
36
 * and Mark Peters <[email protected]>
37
 * and Lars Kneschke <[email protected]>
38
 * Copyright (C) 2000, 2001 Dan Kuykendall
39
 * Copyright (C) 2003 Lars Kneschke
40
 */
41
abstract class Framework extends Framework\Extra
42
{
43
	/**
44
	 * Name of the template set, eg. 'idots'
45
	 *
46
	 * @var string
47
	 */
48
	var $template;
49
50
	/**
51
	 * Path relative to EGW_SERVER_ROOT for the template directory
52
	 *
53
	 * @var string
54
	 */
55
	var $template_dir;
56
57
	/**
58
	 * Application specific template directories to try in given order for CSS
59
	 *
60
	 * @var string
61
	 */
62
	var $template_dirs = array();
63
64
	/**
65
	* true if $this->header() was called
66
	*
67
	* @var boolean
68
	*/
69
	static $header_done = false;
70
	/**
71
	* true if $this->navbar() was called
72
	*
73
	* @var boolean
74
	*/
75
	static $navbar_done = false;
76
77
	/**
78
	 * Constructor
79
	 *
80
	 * The constructor instanciates the class in $GLOBALS['egw']->framework, from where it should be used
81
	 */
82
	function __construct($template)
83
	{
84
		$this->template = $template;
85
86
		if (!isset($GLOBALS['egw']->framework))
87
		{
88
			$GLOBALS['egw']->framework = $this;
89
		}
90
		$this->template_dir = '/api/templates/'.$template;
91
92
		$this->template_dirs[] = $template;
93
		$this->template_dirs[] = 'default';
94
	}
95
96
	/**
97
	 * Factory method to instanciate framework object
98
	 *
99
	 * @return egw_framwork
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\egw_framwork was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
100
	 */
101
	public static function factory()
102
	{
103
		// we prefer Pixelegg template, if it is available
104
		if (file_exists(EGW_SERVER_ROOT.'/pixelegg') &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (file_exists(EGroupware\...ray('idots', 'jerryr')), Probably Intended Meaning: file_exists(EGroupware\A...ay('idots', 'jerryr')))
Loading history...
105
			(empty($GLOBALS['egw_info']['flags']['deny_mobile']) && Header\UserAgent::mobile() ||
106
			$GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile' ||
107
			empty($GLOBALS['egw_info']['server']['template_set'])) ||
108
			// change old idots and jerryr to our standard template (pixelegg)
109
			in_array($GLOBALS['egw_info']['server']['template_set'], array('idots', 'jerryr')))
110
		{
111
			$GLOBALS['egw_info']['server']['template_set'] = 'pixelegg';
112
		}
113
		// then jdots aka Stylite template
114
		if (file_exists(EGW_SERVER_ROOT.'/jdots') && empty($GLOBALS['egw_info']['server']['template_set']))
115
		{
116
			$GLOBALS['egw_info']['server']['template_set'] = 'jdots';
117
		}
118
		// eg. "default" is only used for login at the moment
119
		if (!class_exists($class=$GLOBALS['egw_info']['server']['template_set'].'_framework'))
120
		{
121
			$class = __CLASS__.'\\Minimal';
122
		}
123
		return new $class($GLOBALS['egw_info']['server']['template_set']);
124
	}
125
126
	/**
127
	 * Check if we have a valid and installed EGroupware template
128
	 *
129
	 * Templates are installed in their own directory and contain a setup/setup.inc.php file
130
	 *
131
	 * @param string $template
132
	 * @return boolean
133
	 */
134
	public static function validTemplate($template)
135
	{
136
		return preg_match('/^[A-Z0-9_-]+$/i', $template) &&
137
			file_exists(EGW_SERVER_ROOT.'/'.$template) &&
138
			file_exists($file=EGW_SERVER_ROOT.'/'.$template.'/setup/setup.inc.php') &&
139
			include_once($file) && !empty($GLOBALS['egw_info']['template'][$template]);
140
	}
141
142
	/**
143
	 * Send HTTP headers: Content-Type and Content-Security-Policy
144
	 */
145
	public function send_headers()
146
	{
147
		// add a content-type header to overwrite an existing default charset in apache (AddDefaultCharset directiv)
148
		header('Content-type: text/html; charset='.Translation::charset());
149
150
		Header\ContentSecurityPolicy::send();
151
152
		// allow client-side to detect first load aka just logged in
153
		$reload_count =& Cache::getSession(__CLASS__, 'framework-reload');
154
		self::$extra['framework-reload'] = (int)(bool)$reload_count++;
155
	}
156
157
	/**
158
	 * Constructor for static variables
159
	 */
160
	public static function init_static()
161
	{
162
		self::$js_include_mgr = new Framework\IncludeMgr(array(
163
			// We need LABjs, but putting it through Framework\IncludeMgr causes it to re-load itself
164
			//'/api/js/labjs/LAB.src.js',
165
166
			// allways load jquery (not -ui) first
167
			'/vendor/bower-asset/jquery/dist/jquery.js',
168
			'/api/js/jquery/jquery.noconflict.js',
169
			// always include javascript helper functions
170
			'/api/js/jsapi/jsapi.js',
171
			'/api/js/jsapi/egw.js',
172
		));
173
	}
174
175
	/**
176
	 * Link url generator
177
	 *
178
	 * @param string $url	The url the link is for
179
	 * @param string|array	$extravars	Extra params to be passed to the url
180
	 * @param string $link_app =null if appname or true, some templates generate a special link-handler url
181
	 * @return string	The full url after processing
182
	 */
183
	static function link($url, $extravars = '', $link_app=null)
184
	{
185
		unset($link_app);	// not used by required by function signature
186
		return $GLOBALS['egw']->session->link($url, $extravars);
187
	}
188
189
	/**
190
	 * Get a full / externally usable URL from an EGroupware link
191
	 *
192
	 * Code is only used, if the Setup defined webserver_url is only a path!
193
	 *
194
	 * The following HTTP Headers / $_SERVER variables and EGroupware configuration
195
	 * is taken into account to determine if URL should use https schema:
196
	 * - $_SERVER['HTTPS'] !== off
197
	 * - $GLOBALS['egw_info']['server']['enforce_ssl'] (EGroupware Setup)
198
	 * - $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' (X-Forwarded-Proto HTTP header)
199
	 *
200
	 * Host is determined in the following order / priority:
201
	 * 1. $GLOBALS['egw_info']['server']['hostname'] !== 'localhost' (EGroupware Setup)
202
	 * 2. $_SERVER['HTTP_X_FORWARDED_HOST'] (X-Forwarded-Host HTTP header)
203
	 * 3. $_SERVER['HTTP_HOST'] (Host HTTP header)
204
	 *
205
	 * @param string $link
206
	 */
207
	static function getUrl($link)
208
	{
209
		if ($link[0] === '/')
210
		{
211
			$link = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443 ||
212
				!empty($GLOBALS['egw_info']['server']['enforce_ssl']) || $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ?
213
				'https://' : 'http://').
214
				($GLOBALS['egw_info']['server']['hostname'] && $GLOBALS['egw_info']['server']['hostname'] !== 'localhost' ?
215
					$GLOBALS['egw_info']['server']['hostname'] :
216
					(isset($_SERVER['HTTP_X_FORWARDED_HOST']) ?  $_SERVER['HTTP_X_FORWARDED_HOST'] : $_SERVER['HTTP_HOST'])).
217
				$link;
218
		}
219
		return $link;
220
	}
221
222
	/**
223
	 * Handles redirects under iis and apache, it does NOT return (calls exit)
224
	 *
225
	 * This function handles redirects under iis and apache it assumes that $phpgw->link() has already been called
226
	 *
227
	 * @param string $url url to redirect to
228
	 * @param string $link_app =null appname to redirect for, default currentapp
229
	 */
230
	static function redirect($url, $link_app=null)
231
	{
232
		// Determines whether the current output buffer should be flushed
233
		$do_flush = true;
234
235
		if (Json\Response::isJSONResponse() || Json\Request::isJSONRequest())
236
		{
237
			Json\Response::get()->redirect($url, false, $link_app);
238
239
			// check if we have a message, in which case send it along too
240
			$extra = self::get_extra();
241
			if ($extra['message'])
242
			{
243
				Json\Response::get()->apply('egw.message', $extra['message']);
244
			}
245
246
			// If we are in a json request, we should not flush the current output!
247
			$do_flush = false;
248
		}
249
		else
250
		{
251
			$file = $line = null;
252
			if (headers_sent($file,$line))
253
			{
254
				throw new Exception\AssertionFailed(__METHOD__."('".htmlspecialchars($url)."') can NOT redirect, output already started at $file line $line!");
255
			}
256
			if ($GLOBALS['egw']->framework instanceof Framework\Ajax && !empty($link_app))
257
			{
258
				self::set_extra('egw', 'redirect', array($url, $link_app));
259
				$GLOBALS['egw']->framework->render('');
260
			}
261
			else
262
			{
263
				Header("Location: $url");
264
				print("\n\n");
265
			}
266
		}
267
268
		if ($do_flush)
269
		{
270
			@ob_flush(); flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_flush(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

270
			/** @scrutinizer ignore-unhandled */ @ob_flush(); flush();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Bug introduced by
Are you sure the usage of ob_flush() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
271
		}
272
273
		// commit session (if existing), to fix timing problems sometimes preventing session creation ("Your session can not be verified")
274
		if (isset($GLOBALS['egw']->session)) $GLOBALS['egw']->session->commit_session();
275
276
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
277
	}
278
279
	/**
280
	 * Redirects direct to a generated link
281
	 *
282
	 * @param string $url	The url the link is for
283
	 * @param string|array	$extravars	Extra params to be passed to the url
284
	 * @param string $link_app =null if appname or true, some templates generate a special link-handler url
285
	 * @return string	The full url after processing
286
	 */
287
	static function redirect_link($url, $extravars='', $link_app=null)
288
	{
289
		self::redirect(self::link($url, $extravars), $link_app);
290
	}
291
292
	/**
293
	 * Renders an applicaton page with the complete eGW framework (header, navigation and menu)
294
	 *
295
	 * This is the (new) prefered way to render a page in eGW!
296
	 *
297
	 * @param string $content Html of the main application area
298
	 * @param string $app_header =null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header']
299
	 * @param string $navbar =null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu
300
	 *
301
	 */
302
	function render($content,$app_header=null,$navbar=null)
303
	{
304
		if (!is_null($app_header)) $GLOBALS['egw_info']['flags']['app_header'] = $app_header;
305
		if (!is_null($navbar)) $GLOBALS['egw_info']['flags']['nonavbar'] = !$navbar;
306
307
		echo $this->header();
308
309
		if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || !$GLOBALS['egw_info']['flags']['nonavbar'])
310
		{
311
			echo $this->navbar();
312
		}
313
		echo $content;
314
315
		echo $this->footer();
316
	}
317
318
	/**
319
	 * Returns the html-header incl. the opening body tag
320
	 *
321
	 * @return string with Html
322
	 */
323
	abstract function header(array $extra=array());
324
325
	/**
326
	 * Returns the Html from the body-tag til the main application area (incl. opening div tag)
327
	 *
328
	 * If header has NOT been called, also return header content!
329
	 * No need to manually call header, this allows to postpone header so navbar / sidebox can include JS or CSS.
330
	 *
331
	 * @return string with Html
332
	 */
333
	abstract function navbar();
334
335
	/**
336
	 * Return true if we are rendering the top-level EGroupware window
337
	 *
338
	 * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself
339
	 *
340
	 * @return boolean $consider_navbar_not_yet_called_as_true=true
341
	 * @return boolean
342
	 */
343
	abstract function isTop($consider_navbar_not_yet_called_as_true=true);
344
345
	/**
346
	 * Returns the content of one sidebox
347
	 *
348
	 * @param string $appname
349
	 * @param string $menu_title
350
	 * @param array $file
351
	 * @param string $type =null 'admin', 'preferences', 'favorites', ...
352
	 */
353
	abstract function sidebox($appname,$menu_title,$file,$type=null);
354
355
	/**
356
	 * Returns the Html from the closing div of the main application area to the closing html-tag
357
	 *
358
	 * @return string
359
	 */
360
	abstract function footer();
361
362
	/**
363
	 * Displays the login screen
364
	 *
365
	 * @param string $extra_vars for login url
366
	 * @param string $change_passwd =null string with message to render input fields for password change
367
	 */
368
	function login_screen($extra_vars, $change_passwd=null)
369
	{
370
		(new Framework\Login($this))->screen($extra_vars, $change_passwd);
371
	}
372
373
	/**
374
	 * displays a login denied message
375
	 */
376
	function denylogin_screen()
377
	{
378
		(new Framework\Login($this))->deny_screen();
0 ignored issues
show
Bug introduced by
The method deny_screen() does not exist on EGroupware\Api\Framework\Login. Did you maybe mean screen()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

378
		(new Framework\Login($this))->/** @scrutinizer ignore-call */ deny_screen();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
379
	}
380
381
	/**
382
	 * Calculate page-generation- and session-restore times
383
	 *
384
	 * @return array values for keys 'page_generation_time' and 'session_restore_time', if display is an
385
	 */
386
	public static function get_page_generation_time()
387
	{
388
		$times = array(
389
			'page_generation_time' => sprintf('%4.2lf', microtime(true) - $GLOBALS['egw_info']['flags']['page_start_time']),
390
		);
391
		if ($GLOBALS['egw_info']['flags']['session_restore_time'])
392
		{
393
			$times['session_restore_time'] = sprintf('%4.2lf', $GLOBALS['egw_info']['flags']['session_restore_time']);
394
		}
395
		return $times;
396
	}
397
398
	/**
399
	 * Get footer as array to eg. set as vars for a template (from idots' head.inc.php)
400
	 *
401
	 * @return array
402
	 */
403
	public function _get_footer()
404
	{
405
		$var = Array(
406
			'img_root'       => $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images',
407
			'version'        => $GLOBALS['egw_info']['server']['versions']['api']
408
		);
409
		$var['page_generation_time'] = '';
410
		if($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time'])
411
		{
412
			$times = self::get_page_generation_time();
413
414
			$var['page_generation_time'] = '<div class="pageGenTime" id="divGenTime_'.$GLOBALS['egw_info']['flags']['currentapp'].'"><span>'.
415
				lang('Page was generated in %1 seconds', $times['page_generation_time']);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $times['page_generation_time']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

415
				/** @scrutinizer ignore-call */ 
416
    lang('Page was generated in %1 seconds', $times['page_generation_time']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
416
417
			if (isset($times['session_restore_time']))
418
			{
419
				$var['page_generation_time'] .= ' '.lang('(session restored in %1 seconds)',
420
					$times['session_restore_time']);
421
			}
422
			$var['page_generation_time'] .= '</span></div>';
423
		}
424
		if (empty($GLOBALS['egw_info']['server']['versions']['maintenance_release']))
425
		{
426
			$GLOBALS['egw_info']['server']['versions']['maintenance_release'] = self::api_version();
427
		}
428
		$var['powered_by'] = '<a href="http://www.egroupware.org/" target="_blank">'.
429
			lang('Powered by').' EGroupware '.
430
			$GLOBALS['egw_info']['server']['versions']['maintenance_release'].'</a>';
431
432
		return $var;
433
	}
434
435
	/**
436
	 * Body tags for onLoad, onUnload and onResize
437
	 *
438
	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
439
	 * @var array
440
	 */
441
	protected static $body_tags = array();
442
443
	/**
444
	 * Adds on(Un)Load= attributes to the body tag of a page
445
	 *
446
	 * Can only be set via egw_framework::set_on* methods.
447
	 *
448
	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
449
	 * @returns string the attributes to be used
450
	 */
451
	static public function _get_body_attribs()
452
	{
453
		$js = '';
454
		foreach(self::$body_tags as $what => $data)
455
		{
456
			if (!empty($data))
457
			{
458
				if($what == 'onLoad')
459
				{
460
					$js .= 'onLoad="egw_LAB.wait(function() {'. htmlspecialchars($data).'})"';
461
					continue;
462
				}
463
				$js .= ' '.$what.'="' . htmlspecialchars($data) . '"';
464
			}
465
		}
466
		return $js;
467
	}
468
469
	/**
470
	 * Get header as array to eg. set as vars for a template (from idots' head.inc.php)
471
	 *
472
	 * @param array $extra =array() extra attributes passed as data-attribute to egw.js
473
	 * @return array
474
	 */
475
	protected function _get_header(array $extra=array())
476
	{
477
		// display password expires in N days message once per session
478
		$message = null;
479
		if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' &&
480
			Auth::check_password_change($message) !== true)
481
		{
482
			self::message($message, 'info');
483
		}
484
485
		// get used language code (with a little xss check, if someone tries to sneak something in)
486
		if (preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$GLOBALS['egw_info']['user']['preferences']['common']['lang']))
487
		{
488
			$lang_code = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
489
		}
490
		// IE specific fixes
491
		if (Header\UserAgent::type() == 'msie')
492
		{
493
			// tell IE to use it's own mode, not old compatibility modes (set eg. via group policy for all intranet sites)
494
			// has to be before any other header tags, but meta and title!!!
495
			$pngfix = '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'."\n";
496
		}
497
498
		$app = $GLOBALS['egw_info']['flags']['currentapp'];
499
		$app_title = isset($GLOBALS['egw_info']['apps'][$app]) ? $GLOBALS['egw_info']['apps'][$app]['title'] : lang($app);
500
		$app_header = $GLOBALS['egw_info']['flags']['app_header'] ? $GLOBALS['egw_info']['flags']['app_header'] : $app_title;
501
		$site_title = strip_tags($GLOBALS['egw_info']['server']['site_title'].' ['.($app_header ? $app_header : $app_title).']');
502
503
		// send appheader to clientside
504
		$extra['app-header'] = $app_header;
505
506
		if($GLOBALS['egw_info']['flags']['currentapp'] != 'wiki') $robots ='<meta name="robots" content="none" />';
507
508
		$var['favicon_file'] = self::get_login_logo_or_bg_url('favicon_file', 'favicon.ico');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$var was never initialized. Although not strictly required by PHP, it is generally a good practice to add $var = array(); before regardless.
Loading history...
509
510
		if ($GLOBALS['egw_info']['flags']['include_wz_tooltip'] &&
511
			file_exists(EGW_SERVER_ROOT.($wz_tooltip = '/phpgwapi/js/wz_tooltip/wz_tooltip.js')))
512
		{
513
			$include_wz_tooltip = '<script src="'.$GLOBALS['egw_info']['server']['webserver_url'].
514
				$wz_tooltip.'?'.filemtime(EGW_SERVER_ROOT.$wz_tooltip).'" type="text/javascript"></script>';
515
		}
516
		return $this->_get_css()+array(
517
			'img_icon'			=> $var['favicon_file'],
518
			'img_shortcut'		=> $var['favicon_file'],
519
			'pngfix'        	=> $pngfix,
520
			'lang_code'			=> $lang_code,
521
			'charset'       	=> Translation::charset(),
522
			'website_title' 	=> $site_title,
523
			'body_tags'         => self::_get_body_attribs(),
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Framework::_get_body_attribs() has been deprecated: since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

523
			'body_tags'         => /** @scrutinizer ignore-deprecated */ self::_get_body_attribs(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
524
			'java_script'   	=> self::_get_js($extra),
525
			'meta_robots'		=> $robots,
526
			'dir_code'			=> lang('language_direction_rtl') != 'rtl' ? '' : ' dir="rtl"',
527
			'include_wz_tooltip'=> $include_wz_tooltip,
528
			'webserver_url'     => $GLOBALS['egw_info']['server']['webserver_url'],
529
		);
530
	}
531
532
	/**
533
	 * Get navbar as array to eg. set as vars for a template (from idots' navbar.inc.php)
534
	 *
535
	 * @param array $apps navbar apps from _get_navbar_apps
536
	 * @return array
537
	 */
538
	protected function _get_navbar($apps)
539
	{
540
		$var['img_root'] = $GLOBALS['egw_info']['server']['webserver_url'] . '/phpgwapi/templates/'.$this->template.'/images';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$var was never initialized. Although not strictly required by PHP, it is generally a good practice to add $var = array(); before regardless.
Loading history...
541
542
		if(isset($GLOBALS['egw_info']['flags']['app_header']))
543
		{
544
			$var['current_app_title'] = $GLOBALS['egw_info']['flags']['app_header'];
545
		}
546
		else
547
		{
548
			$var['current_app_title']=$apps[$GLOBALS['egw_info']['flags']['currentapp']]['title'];
549
		}
550
		$var['currentapp'] = $GLOBALS['egw_info']['flags']['currentapp'];
551
552
		// quick add selectbox
553
		$var['quick_add'] = $this->_get_quick_add();
554
555
		$var['user_info'] = $this->_user_time_info();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $var['user_info'] is correct as $this->_user_time_info() targeting EGroupware\Api\Framework::_user_time_info() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
556
557
		if($GLOBALS['egw_info']['user']['account_lastpwd_change'] == 0)
558
		{
559
			$api_messages = lang('You are required to change your password during your first login').'<br />'.
0 ignored issues
show
Unused Code introduced by
The assignment to $api_messages is dead and can be removed.
Loading history...
560
				lang('Click this image on the navbar: %1','<img src="'.Image::find('preferences','navbar.gif').'">');
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with '<img src="' . EGroupwar...', 'navbar.gif') . '">'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

560
				/** @scrutinizer ignore-call */ 
561
    lang('Click this image on the navbar: %1','<img src="'.Image::find('preferences','navbar.gif').'">');

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
561
		}
562
		elseif($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] && $GLOBALS['egw_info']['user']['account_lastpwd_change'] < time() - (86400*$GLOBALS['egw_info']['server']['change_pwd_every_x_days']))
563
		{
564
			$api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']);
565
		}
566
567
		$var['logo_header'] = $var['logo_file'] = self::get_login_logo_or_bg_url('login_logo_file', 'logo');
568
569
		if ($GLOBALS['egw_info']['server']['login_logo_header'])
570
		{
571
			$var['logo_header'] = self::get_login_logo_or_bg_url('login_logo_header', 'logo');
572
		}
573
574
		$var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.eGroupWare.org';
575
576
		if (substr($var['logo_url'],0,4) != 'http')
577
		{
578
			$var['logo_url'] = 'http://'.$var['logo_url'];
579
		}
580
		$var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.eGroupWare.org';
581
582
		return $var;
583
	}
584
585
	/**
586
	 * Get login logo or background image base on requested config type
587
	 *
588
	 * @param type $type config type to fetch. e.g.: "login_logo_file"
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
589
	 * @param type $find_type type of image to search on as alternative option. e.g.: "logo"
590
	 *
591
	 * @return string returns full url of the image
592
	 */
593
	static function get_login_logo_or_bg_url ($type, $find_type)
594
	{
595
		$url = is_array($GLOBALS['egw_info']['server'][$type]) ?
596
			$GLOBALS['egw_info']['server'][$type][0] :
597
			$GLOBALS['egw_info']['server'][$type];
598
599
		if (substr($url, 0, 4) == 'http' ||
600
			$url[0] == '/')
601
		{
602
			return $url;
603
		}
604
		else
605
		{
606
			return Image::find('api',$url ? $url : $find_type, '', null);
0 ignored issues
show
Unused Code introduced by
The call to EGroupware\Api\Image::find() has too many arguments starting with null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

606
			return Image::/** @scrutinizer ignore-call */ find('api',$url ? $url : $find_type, '', null);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
607
		}
608
	}
609
610
	/**
611
	 * Returns Html with user and time
612
	 *
613
	 * @return void
614
	 */
615
	protected static function _user_time_info()
616
	{
617
		$now = new DateTime();
618
		$user_info = '<b>'.Accounts::format_username() .'</b>'. ' - ' . lang($now->format('l')) . ' ' . $now->format(true);
619
620
		$user_tzs = DateTime::getUserTimezones();
621
		if (count($user_tzs) > 1)
622
		{
623
			$tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz'];
624
			$user_info .= Html::form(Html::select('tz',$tz,$user_tzs,true),array(),
625
				'/index.php','','tz_selection',' style="display: inline;"','GET');
626
		}
627
		return $user_info;
628
	}
629
630
	/**
631
	 * Returns user avatar menu
632
	 *
633
	 * @return string
634
	 */
635
	protected static function _user_avatar_menu()
636
	{
637
		return '<span title="'.Accounts::format_username().'" class="avatar"><img src="'.Egw::link('/api/avatar.php', array(
638
								'account_id' => $GLOBALS['egw_info']['user']['account_id'],
639
							)).'"/></span>';
640
	}
641
642
	/**
643
	 * Returns logout menu
644
	 *
645
	 * @return string
646
	 */
647
	protected static function _logout_menu()
648
	{
649
		return '<a href="'.Egw::link('/logout.php').'" title="'.lang("Logout").'" ></a>';
650
	}
651
652
	/**
653
	 * Returns print menu
654
	 *
655
	 * @return string
656
	 */
657
	protected static function _print_menu()
658
	{
659
		return '<span title="'.lang("Print current view").'"</span>';
660
	}
661
662
663
	/**
664
	 * Prepare the current users
665
	 *
666
	 * @return array
667
	 */
668
	protected static function _current_users()
669
	{
670
	   if( $GLOBALS['egw_info']['user']['apps']['admin'] && $GLOBALS['egw_info']['user']['preferences']['common']['show_currentusers'])
671
	   {
672
		   return [
673
			   'name' => 'current_user',
674
			   'title' => lang('Current users').':'.$GLOBALS['egw']->session->session_count(),
675
			   'url' => self::link('/index.php','menuaction=admin.admin_accesslog.sessions')
676
		   ];
677
	   }
678
	}
679
680
	/**
681
	 * Prepare the quick add selectbox
682
	 *
683
	 * @return string
684
	 */
685
	protected static function _get_quick_add()
686
	{
687
		return '<span id="quick_add" title="'.lang('Quick add').'"></span>';
688
	}
689
690
	/**
691
	 * Prepare notification signal (blinking bell)
692
	 *
693
	 * @return string
694
	 */
695
	protected static function _get_notification_bell()
696
	{
697
		return Html::image('notifications', 'notificationbell', lang('notifications'),
698
			'id="notificationbell" style="display: none"');
699
	}
700
701
	/**
702
	 * Get context to use with file_get_context or fopen to use our proxy settings from setup
703
	 *
704
	 * @param string $username =null username for regular basic Auth
705
	 * @param string $password =null password --------- " ----------
706
	 * @param array $opts =array() further params for http(s) context, eg. array('timeout' => 123)
707
	 * @return resource|null context to use with file_get_context/fopen or null if no proxy configured
708
	 */
709
	public static function proxy_context($username=null, $password=null, array $opts = array())
710
	{
711
		$opts += array(
712
			'method' => 'GET',
713
		);
714
		if (!empty($GLOBALS['egw_info']['server']['httpproxy_server']))
715
		{
716
			$opts += array (
717
				'proxy'  => 'tcp://'.$GLOBALS['egw_info']['server']['httpproxy_server'].':'.
718
					($GLOBALS['egw_info']['server']['httpproxy_port'] ? $GLOBALS['egw_info']['server']['httpproxy_port'] : 8080),
719
				'request_fulluri' => true,
720
			);
721
			// proxy authentication
722
			if (!empty($GLOBALS['egw_info']['server']['httpproxy_server_username']))
723
			{
724
				$opts['header'][] = 'Proxy-Authorization: Basic '.base64_encode($GLOBALS['egw_info']['server']['httpproxy_server_username'].':'.
725
					$GLOBALS['egw_info']['server']['httpproxy_server_password']);
726
			}
727
		}
728
		// optional authentication
729
		if (isset($username))
730
		{
731
			$opts['header'][] = 'Authorization: Basic '.base64_encode($username.':'.$password);
732
		}
733
		return stream_context_create(array(
734
			'http' => $opts,
735
			'https' => $opts,
736
		));
737
	}
738
739
	/**
740
	 * Get API version from changelog or database, whichever is bigger
741
	 *
742
	 * @param string &$changelog on return path to changelog
743
	 * @return string
744
	 */
745
	public static function api_version(&$changelog=null)
746
	{
747
		return Framework\Updates::api_version($changelog);
748
	}
749
750
	/**
751
	 * Get the link to an application's index page
752
	 *
753
	 * @param string $app
754
	 * @return string
755
	 */
756
	public static function index($app)
757
	{
758
		$data =& $GLOBALS['egw_info']['user']['apps'][$app];
759
		if (!isset($data))
760
		{
761
			throw new Exception\WrongParameter("'$app' not a valid app for this user!");
762
		}
763
		$index = '/'.$app.'/index.php';
764
		if (isset($data['index']))
765
		{
766
			if ($data['index'][0] == '/')
767
			{
768
				$index = $data['index'];
769
			}
770
			else
771
			{
772
				$index = '/index.php?menuaction='.$data['index'];
773
			}
774
		}
775
		return self::link($index,$GLOBALS['egw_info']['flags']['params'][$app]);
776
	}
777
778
	/**
779
	 * Used internally to store unserialized value of $GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']
780
	 */
781
	private static $user_apporder = array();
782
783
	/**
784
	 * Internal usort callback function used to sort an array according to the
785
	 * user sort order
786
	 */
787
	private static function _sort_apparray($a, $b)
788
	{
789
		//Unserialize the user_apporder array
790
		$arr = self::$user_apporder;
791
792
		$ind_a = isset($arr[$a['name']]) ? $arr[$a['name']] : null;
793
		$ind_b = isset($arr[$b['name']]) ? $arr[$b['name']] : null;
794
795
		if ($ind_a == $ind_b)
796
			return 0;
797
798
		if ($ind_a == null)
799
			return -1;
800
801
		if ($ind_b == null)
802
			return 1;
803
804
		return $ind_a > $ind_b ? 1 : -1;
805
	}
806
807
	/**
808
	 * Prepare an array with apps used to render the navbar
809
	 *
810
	 * This is similar to the former common::navbar() method - though it returns the vars and does not place them in global scope.
811
	 *
812
	 * @return array
813
	 */
814
	protected static function _get_navbar_apps()
815
	{
816
		$first = key($GLOBALS['egw_info']['user']['apps']);
817
		if(is_array($GLOBALS['egw_info']['user']['apps']['admin']) && $first != 'admin')
818
		{
819
			$newarray['admin'] = $GLOBALS['egw_info']['user']['apps']['admin'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$newarray was never initialized. Although not strictly required by PHP, it is generally a good practice to add $newarray = array(); before regardless.
Loading history...
820
			foreach($GLOBALS['egw_info']['user']['apps'] as $index => $value)
821
			{
822
				if($index != 'admin')
823
				{
824
					$newarray[$index] = $value;
825
				}
826
			}
827
			$GLOBALS['egw_info']['user']['apps'] = $newarray;
828
			reset($GLOBALS['egw_info']['user']['apps']);
829
		}
830
		unset($index);
831
		unset($value);
832
		unset($newarray);
833
834
		$apps = array();
835
		foreach($GLOBALS['egw_info']['user']['apps'] as $app => $data)
836
		{
837
			if (is_long($app))
838
			{
839
				continue;
840
			}
841
842
			if ($app == 'preferences' || $GLOBALS['egw_info']['apps'][$app]['status'] != 2 && $GLOBALS['egw_info']['apps'][$app]['status'] != 3)
843
			{
844
				$apps[$app]['title'] = $GLOBALS['egw_info']['apps'][$app]['title'];
845
				$apps[$app]['url']   = self::index($app);
846
				$apps[$app]['name']  = $app;
847
848
				// create popup target
849
				if ($data['status'] == 4)
850
				{
851
					$apps[$app]['target'] = ' target="'.$app.'" onClick="'."if (this != '') { window.open(this+'".
852
						(strpos($apps[$app]['url'],'?') !== false ? '&' : '?').
853
						"referer='+encodeURIComponent(location),this.target,'width=800,height=600,scrollbars=yes,resizable=yes'); return false; } else { return true; }".'"';
854
				}
855
				elseif(isset($GLOBALS['egw_info']['flags']['navbar_target']) && $GLOBALS['egw_info']['flags']['navbar_target'])
856
				{
857
					$apps[$app]['target'] = 'target="' . $GLOBALS['egw_info']['flags']['navbar_target'] . '"';
858
				}
859
				else
860
				{
861
					$apps[$app]['target'] = '';
862
				}
863
864
				// take status flag into account as we might use it on client-side.
865
				// for instance: applications with status 5 will run in background
866
				$apps[$app]['status'] = $data['status'];
867
868
				$icon = isset($data['icon']) ?  $data['icon'] : 'navbar';
869
				$icon_app = isset($data['icon_app']) ? $data['icon_app'] : $app;
870
				$apps[$app]['icon']  = $apps[$app]['icon_hover']  = Image::find($icon_app,Array($icon,'nonav'),'');
871
			}
872
		}
873
874
		//Sort the applications accordingly to their user sort setting
875
		if ($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder'])
876
		{
877
			//Sort the application array using the user_apporder array as sort index
878
			self::$user_apporder =
879
				unserialize($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']);
880
			uasort($apps, __CLASS__.'::_sort_apparray');
881
		}
882
883
		if ($GLOBALS['egw_info']['flags']['currentapp'] == 'preferences' || $GLOBALS['egw_info']['flags']['currentapp'] == 'about')
884
		{
885
			$app = $app_title = 'EGroupware';
0 ignored issues
show
Unused Code introduced by
The assignment to $app_title is dead and can be removed.
Loading history...
Unused Code introduced by
The assignment to $app is dead and can be removed.
Loading history...
886
		}
887
		else
888
		{
889
			$app = $GLOBALS['egw_info']['flags']['currentapp'];
890
			$app_title = $GLOBALS['egw_info']['apps'][$app]['title'];
891
		}
892
893
		if ($GLOBALS['egw_info']['user']['apps']['preferences'])	// Preferences last
894
		{
895
			$prefs = $apps['preferences'];
896
			unset($apps['preferences']);
897
			$apps['preferences'] = $prefs;
898
		}
899
900
		// We handle this here because its special
901
		$apps['about']['title'] = 'EGroupware';
902
		$apps['about']['url']   = self::link('/about.php');
903
		$apps['about']['icon']  = $apps['about']['icon_hover'] = Image::find('api',Array('about','nonav'));
904
		$apps['about']['name']  = 'about';
905
906
		$apps['logout']['title'] = lang('Logout');
907
		$apps['logout']['name']  = 'logout';
908
		$apps['logout']['url']   = self::link('/logout.php');
909
		$apps['logout']['icon']  = $apps['logout']['icon_hover'] = Image::find('api',Array('logout','nonav'));
910
911
		return $apps;
912
	}
913
914
	/**
915
	 * Used by template headers for including CSS in the header
916
	 *
917
	 * 'app_css'   - css styles from a) the menuaction's css-method and b) the $GLOBALS['egw_info']['flags']['css']
918
	 * 'file_css'  - link tag of the app.css file of the current app
919
	 * 'theme_css' - url of the theme css file
920
	 * 'print_css' - url of the print css file
921
	 *
922
	 * @author Dave Hall (*based* on verdilak? css inclusion code)
923
	 * @return array with keys 'app_css' from the css method of the menuaction-class and 'file_css' (app.css file of the application)
924
	 */
925
	public function _get_css()
926
	{
927
		$app_css = '';
928
		if (isset($GLOBALS['egw_info']['flags']['css']))
929
		{
930
			$app_css = $GLOBALS['egw_info']['flags']['css'];
931
		}
932
933
		if (self::$load_default_css)
934
		{
935
			// For mobile user-agent we prefer mobile theme over selected one with a final fallback to theme named as template
936
			$themes_to_check = array();
937
			if (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile')
938
			{
939
				$themes_to_check[] = $this->template_dir.'/mobile/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css';
940
				$themes_to_check[] = $this->template_dir.'/mobile/fw_mobile.css';
941
			}
942
			$themes_to_check[] = $this->template_dir.'/css/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css';
943
			$themes_to_check[] = $this->template_dir.'/css/'.$this->template.'.css';
944
			foreach($themes_to_check as $theme_css)
945
			{
946
				if (file_exists(EGW_SERVER_ROOT.$theme_css)) break;
947
			}
948
			$debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
949
			if (!$debug_minify && file_exists(EGW_SERVER_ROOT.($theme_min_css = str_replace('.css', '.min.css', $theme_css))))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $theme_css seems to be defined by a foreach iteration on line 944. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
950
			{
951
				//error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get()));
952
				self::includeCSS($theme_min_css);
953
954
				// Global category styles
955
				Categories::css(Categories::GLOBAL_APPNAME);
956
			}
957
			else
958
			{
959
				// Load these first
960
				// Cascade should go:
961
				//  Libs < etemplate2 < framework/theme < app < print
962
				// Enhanced selectboxes (et1)
963
				self::includeCSS('/api/js/jquery/chosen/chosen.css');
964
965
				// eTemplate2 uses jQueryUI, so load it first so et2 can override if needed
966
				self::includeCSS("/vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css");
967
968
				// eTemplate2 - load in top so sidebox has styles too
969
				self::includeCSS('/api/templates/default/etemplate2.css');
970
971
				// Category styles
972
				Categories::css(Categories::GLOBAL_APPNAME);
973
974
				self::includeCSS($theme_css);
975
976
				// sending print css last, so it can overwrite anything
977
				$print_css = $this->template_dir.'/print.css';
978
				if(!file_exists(EGW_SERVER_ROOT.$print_css))
979
				{
980
					$print_css = '/api/templates/default/print.css';
981
				}
982
				self::includeCSS($print_css);
983
			}
984
			// search for app specific css file, so it can customize the theme
985
			self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
986
				self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app');
987
		}
988
		return array(
989
			'app_css'   => $app_css,
990
			'css_file'  => Framework\CssIncludes::tags(),
991
		);
992
	}
993
994
	/**
995
	 * Used by the template headers for including javascript in the header
996
	 *
997
	 * The method is included here to make it easier to change the js support
998
	 * in eGW.  One change then all templates will support it (as long as they
999
	 * include a call to this method).
1000
	 *
1001
	 * @param array $extra =array() extra data to pass to egw.js as data-parameter
1002
	 * @return string the javascript to be included
1003
	 */
1004
	public static function _get_js(array $extra=array())
1005
	{
1006
		$java_script = '';
1007
1008
		/* this flag is for all javascript code that has to be put before other jscode.
1009
		Think of conf vars etc...  ([email protected]) */
1010
		if (isset($GLOBALS['egw_info']['flags']['java_script_thirst']))
1011
		{
1012
			$java_script .= $GLOBALS['egw_info']['flags']['java_script_thirst'] . "\n";
1013
		}
1014
		// add configuration, link-registry, images, user-data and -perferences for non-popup windows
1015
		// specifying etag in url to force reload, as we send expires header
1016
		if ($GLOBALS['egw_info']['flags']['js_link_registry'])
1017
		{
1018
			self::includeJS('/api/config.php', array(
1019
				'etag' => md5(json_encode(Config::clientConfigs()).Link::json_registry()),
1020
			));
1021
			self::includeJS('/api/images.php', array(
1022
				'template' => $GLOBALS['egw_info']['server']['template_set'],
1023
				'etag' => md5(json_encode(Image::map($GLOBALS['egw_info']['server']['template_set'])))
1024
			));
1025
			self::includeJS('/api/user.php', array(
1026
				'user' => $GLOBALS['egw_info']['user']['account_lid'],
1027
				'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'],
1028
				// add etag on url, so we can set an expires header
1029
				'etag' => md5(json_encode($GLOBALS['egw_info']['user']['preferences']['common']).
1030
					$GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id'])),
1031
			));
1032
		}
1033
1034
		$extra['url'] = $GLOBALS['egw_info']['server']['webserver_url'];
1035
		$extra['include'] = array_map(function($str){return substr($str,1);}, self::get_script_links(true), array(1));
1036
		$extra['app'] = $GLOBALS['egw_info']['flags']['currentapp'];
1037
1038
		// Load LABjs ONCE here
1039
		$java_script .= '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1040
				'/api/js/labjs/LAB.src.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/labjs/LAB.src.js')."\"></script>\n".
1041
			'<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1042
				'/api/js/jsapi/egw.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/jsapi/egw.js').'" id="egw_script_id"';
1043
1044
		// add values of extra parameter and class var as data attributes to script tag of egw.js
1045
		foreach($extra+self::$extra as $name => $value)
1046
		{
1047
			if (is_array($value)) $value = json_encode($value);
1048
			// we need to double encode (Html::htmlspecialchars( , TRUE)), as otherwise we get invalid json, eg. for quotes
1049
			$java_script .= ' data-'.$name."=\"". Html::htmlspecialchars($value, true)."\"";
1050
		}
1051
		$java_script .= "></script>\n";
1052
1053
		if(@isset($_GET['menuaction']))
1054
		{
1055
			list(, $class) = explode('.',$_GET['menuaction']);
1056
			if(is_array($GLOBALS[$class]->public_functions) &&
1057
				$GLOBALS[$class]->public_functions['java_script'])
1058
			{
1059
				$java_script .= $GLOBALS[$class]->java_script();
1060
			}
1061
		}
1062
		if (isset($GLOBALS['egw_info']['flags']['java_script']))
1063
		{
1064
			// Strip out any script tags, this needs to be executed as anonymous function
1065
			$GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']);
1066
			if(trim($GLOBALS['egw_info']['flags']['java_script']) != '')
1067
			{
1068
				$java_script .= '<script type="text/javascript">window.egw_LAB.wait(function() {'.$GLOBALS['egw_info']['flags']['java_script'] . "});</script>\n";
1069
			}
1070
		}
1071
1072
		return $java_script;
1073
	}
1074
1075
	/**
1076
	 * List available themes
1077
	 *
1078
	 * Themes are css file in the template directory
1079
	 *
1080
	 * @param string $themes_dir ='css'
1081
	 */
1082
	function list_themes()
1083
	{
1084
		$list = array();
1085
		if (file_exists($file=EGW_SERVER_ROOT.$this->template_dir.'/setup/setup.inc.php') &&
1086
			(include $file) && isset($GLOBALS['egw_info']['template'][$this->template]['themes']))
1087
		{
1088
			$list = $GLOBALS['egw_info']['template'][$this->template]['themes'];
1089
		}
1090
		if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir.'/css')))
1091
		{
1092
			while (($file = readdir($dh)))
1093
			{
1094
				if (preg_match('/'."\.css$".'/i', $file))
1095
				{
1096
					list($name) = explode('.',$file);
1097
					if (!isset($list[$name])) $list[$name] = ucfirst ($name);
1098
				}
1099
			}
1100
			closedir($dh);
1101
		}
1102
		return $list;
1103
	}
1104
1105
	/**
1106
	 * List available templates
1107
	 *
1108
	 * @param boolean $full_data =false true: value is array with values for keys 'name', 'title', ...
1109
	 * @returns array alphabetically sorted list of templates
1110
	 */
1111
	static function list_templates($full_data=false)
1112
	{
1113
		$list = array('pixelegg'=>null);
1114
		// templates packaged like apps in own directories (containing as setup/setup.inc.php file!)
1115
		$dr = dir(EGW_SERVER_ROOT);
1116
		while (($entry=$dr->read()))
1117
		{
1118
			if ($entry != '..' && !isset($GLOBALS['egw_info']['apps'][$entry]) && is_dir(EGW_SERVER_ROOT.'/'.$entry) &&
1119
				file_exists($f = EGW_SERVER_ROOT . '/' . $entry .'/setup/setup.inc.php'))
1120
			{
1121
				include($f);
1122
				if (isset($GLOBALS['egw_info']['template'][$entry]))
1123
				{
1124
					$list[$entry] = $full_data ? $GLOBALS['egw_info']['template'][$entry] :
1125
						$GLOBALS['egw_info']['template'][$entry]['title'];
1126
				}
1127
			}
1128
		}
1129
		$dr->close();
1130
1131
		return array_filter($list);
1132
	}
1133
1134
	/**
1135
	* Compile entries for topmenu:
1136
	* - regular items: links
1137
	* - info items
1138
	*
1139
	* @param array $vars
1140
	* @param array $apps
1141
	*/
1142
	function topmenu(array $vars,array $apps)
1143
	{
1144
		$this->_add_topmenu_info_item($this->_user_avatar_menu(), 'user_avatar');
1145
		$this->_add_topmenu_info_item($this->_logout_menu(), 'logout');
1146
		if($GLOBALS['egw_info']['user']['apps']['home'] && isset($apps['home']))
1147
		{
1148
			$this->_add_topmenu_item($apps['home']);
1149
		}
1150
1151
		if($GLOBALS['egw_info']['user']['apps']['preferences'])
1152
		{
1153
			$this->add_preferences_topmenu('prefs');
1154
			$this->add_preferences_topmenu('acl');
1155
			$this->add_preferences_topmenu('cats');
1156
		}
1157
1158
		// allways display password in topmenu, if user has rights to change it
1159
		if ($GLOBALS['egw_info']['user']['apps']['preferences'] &&
1160
			!$GLOBALS['egw']->acl->check('nopasswordchange', 1, 'preferences'))
1161
		{
1162
			$this->_add_topmenu_item(array(
1163
				'id'    => 'password',
1164
				'name'  => 'preferences',
1165
				'title' => lang('Password'),
1166
				'url'   => "javascript:egw.open_link('".
1167
					self::link('/index.php?menuaction=preferences.preferences_password.change')."','_blank','400x270')",
1168
			));
1169
		}
1170
		/* disable help until content is reworked
1171
		if($GLOBALS['egw_info']['user']['apps']['manual'] && isset($apps['manual']))
1172
		{
1173
			$this->_add_topmenu_item(array_merge($apps['manual'],array('title' => lang('Help'))));
1174
		}*/
1175
1176
		Hooks::process('topmenu_info',array(),true);
1177
		// Add extra items added by hooks
1178
		foreach(self::$top_menu_extra as $extra_item) {
1179
			$this->_add_topmenu_item($extra_item);
1180
		}
1181
1182
		$this->_add_topmenu_item($apps['logout']);
1183
1184
		if (($update = Framework\Updates::notification()))
1185
		{
1186
			$this->_add_topmenu_info_item($update, 'update');
1187
		}
1188
		if($GLOBALS['egw_info']['user']['apps']['notifications'])
1189
		{
1190
			$this->_add_topmenu_info_item(self::_get_notification_bell(), 'notifications');
1191
		}
1192
		$this->_add_topmenu_info_item($this->_user_avatar_menu(), 'user_avatar');
1193
		$this->_add_topmenu_item($this->_current_users());
1194
		$this->_add_topmenu_info_item($vars['quick_add'], 'quick_add');
1195
		$this->_add_topmenu_info_item($this->_print_menu(), 'print_title');
1196
1197
	}
1198
1199
	/**
1200
	 * Add Preferences link to topmenu using settings-hook to know if an app supports Preferences
1201
	 */
1202
	protected function add_preferences_topmenu($type='prefs')
1203
	{
1204
		static $memberships=null;
1205
		if (!isset($memberships)) $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true);
1206
		static $types = array(
1207
			'prefs' => array(
1208
				'title' => 'Preferences',
1209
				'hook'  => 'settings',
1210
			),
1211
			'acl' => array(
1212
				'title' => 'Access',
1213
				'hook'  => 'acl_rights',
1214
			),
1215
			'cats' => array(
1216
				'title' => 'Categories',
1217
				'hook' => 'categories',
1218
				'run_hook' => true,	// acturally run hook, not just look it's implemented
1219
			),
1220
		);
1221
		if (!$GLOBALS['egw_info']['user']['apps']['preferences'] || $GLOBALS['egw_info']['server']['deny_'.$type] &&
1222
			array_intersect($memberships, (array)$GLOBALS['egw_info']['server']['deny_'.$type]) &&
1223
			!$GLOBALS['egw_info']['user']['apps']['admin'])
1224
		{
1225
			return;	// user has no access to Preferences app
1226
		}
1227
		if (isset($types[$type]['run_hook']))
1228
		{
1229
			$apps = Hooks::process($types[$type]['hook']);
1230
			// as all apps answer, we need to remove none-true responses
1231
			foreach($apps as $app => $val)
1232
			{
1233
				if (!$val) unset($apps[$app]);
1234
			}
1235
		}
1236
		else
1237
		{
1238
			$apps = Hooks::implemented($types[$type]['hook']);
1239
		}
1240
		$this->_add_topmenu_item(array(
1241
			'id' => $type,
1242
			'name' => 'preferences',
1243
			'title' => lang($types[$type]['title']),
1244
			'url' => "javascript:egw.show_preferences(\"$type\",".json_encode($apps).')',
1245
		));
1246
	}
1247
1248
	/**
1249
	* Add menu items to the topmenu template class to be displayed
1250
	*
1251
	* @param array $app application data
1252
	* @param mixed $alt_label string with alternative menu item label default value = null
1253
	* @param string $urlextra string with alternate additional code inside <a>-tag
1254
	* @access protected
1255
	* @return void
1256
	*/
1257
	abstract function _add_topmenu_item(array $app_data,$alt_label=null);
1258
1259
	/**
1260
	* Add info items to the topmenu template class to be displayed
1261
	*
1262
	* @param string $content Html of item
1263
	* @param string $id =null
1264
	* @access protected
1265
	* @return void
1266
	*/
1267
	abstract function _add_topmenu_info_item($content, $id=null);
1268
1269
	static $top_menu_extra = array();
1270
1271
	/**
1272
	* Called by hooks to add an entry in the topmenu location.
1273
	* Extra entries will be added just before Logout.
1274
	*
1275
	* @param string $id unique element id
1276
	* @param string $url Address for the entry to link to
1277
	* @param string $title Text displayed for the entry
1278
	* @param string $target Optional, so the entry can open in a new page or popup
1279
	* @access public
1280
	* @return void
1281
	*/
1282
	public static function add_topmenu_item($id,$url,$title,$target = '')
1283
	{
1284
		$entry['name'] = $id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$entry was never initialized. Although not strictly required by PHP, it is generally a good practice to add $entry = array(); before regardless.
Loading history...
1285
		$entry['url'] = $url;
1286
		$entry['title'] = $title;
1287
		$entry['target'] = $target;
1288
1289
		self::$top_menu_extra[$id] = $entry;
1290
	}
1291
1292
	/**
1293
	* called by hooks to add an icon in the topmenu info location
1294
	*
1295
	* @param string $id unique element id
1296
	* @param string $icon_src src of the icon image. Make sure this nog height then 18pixels
1297
	* @param string $iconlink where the icon links to
1298
	* @param booleon $blink set true to make the icon blink
0 ignored issues
show
Bug introduced by
The type EGroupware\Api\booleon was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1299
	* @param mixed $tooltip string containing the tooltip html, or null of no tooltip
1300
	* @access public
1301
	* @return void
1302
	*/
1303
	abstract function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null);
1304
1305
	/**
1306
	 * Call and return content of 'after_navbar' hook
1307
	 *
1308
	 * @return string
1309
	 */
1310
	protected function _get_after_navbar()
1311
	{
1312
		ob_start();
1313
		Hooks::process('after_navbar',null,true);
1314
		$content = ob_get_contents();
1315
		ob_end_clean();
1316
1317
		return $content;
1318
	}
1319
1320
	/**
1321
	 * Return javascript (eg. for onClick) to open manual with given url
1322
	 *
1323
	 * @param string $url
1324
	 */
1325
	abstract function open_manual_js($url);
1326
1327
	/**
1328
	 * Methods to add javascript to framework
1329
	 */
1330
1331
	/**
1332
	 * The include manager manages including js files and their dependencies
1333
	 */
1334
	protected static $js_include_mgr;
1335
1336
	/**
1337
	* Checks to make sure a valid package and file name is provided
1338
	*
1339
	* Example call syntax:
1340
	* a) Api\Framework::includeJS('jscalendar','calendar')
1341
	*    --> /phpgwapi/js/jscalendar/calendar.js
1342
	* b) Api\Framework::includeJS('/phpgwapi/inc/calendar-setup.js',array('lang'=>'de'))
1343
	*    --> /phpgwapi/inc/calendar-setup.js?lang=de
1344
	*
1345
	* @param string $package package or complete path (relative to EGW_SERVER_ROOT) to be included
1346
	* @param string|array $file =null file to be included - no ".js" on the end or array with get params
1347
	* @param string $app ='phpgwapi' application directory to search - default = phpgwapi
1348
	* @param boolean $append =true should the file be added
1349
	*/
1350
	static function includeJS($package, $file=null, $app='phpgwapi', $append=true)
1351
	{
1352
		self::$js_include_mgr->include_js_file($package, $file, $app, $append);
1353
	}
1354
1355
	/**
1356
	 * Set or return all javascript files set via validate_file, optionally clear all files
1357
	 *
1358
	 * @param array $files =null array with pathes relative to EGW_SERVER_ROOT, eg. /api/js/jquery/jquery.js
1359
	 * @param boolean $clear_files =false true clear files after returning them
1360
	 * @return array with pathes relative to EGW_SERVER_ROOT
1361
	 */
1362
	static function js_files(array $files=null, $clear_files=false)
1363
	{
1364
		if (isset($files) && is_array($files))
1365
		{
1366
			self::$js_include_mgr->include_files($files);
1367
		}
1368
		return self::$js_include_mgr->get_included_files($clear_files);
1369
	}
1370
1371
	/**
1372
	 * Used for generating the list of external js files to be included in the head of a page
1373
	 *
1374
	 * NOTE: This method should only be called by the template class.
1375
	 * The validation is done when the file is added so we don't have to worry now
1376
	 *
1377
	 * @param boolean $return_pathes =false false: return Html script tags, true: return array of file pathes relative to webserver_url
1378
	 * @param boolean $clear_files =false true clear files after returning them
1379
	 * @return string|array see $return_pathes parameter
1380
	 */
1381
	static public function get_script_links($return_pathes=false, $clear_files=false)
1382
	{
1383
		$to_include = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files($clear_files));
1384
1385
		if ($return_pathes)
1386
		{
1387
			return $to_include;
1388
		}
1389
		$start = '<script type="text/javascript" src="'. $GLOBALS['egw_info']['server']['webserver_url'];
1390
		$end = '">'."</script>\n";
1391
		return "\n".$start.implode($end.$start, $to_include).$end;
1392
	}
1393
1394
	/**
1395
	 *
1396
	 * @var boolean
1397
	 */
1398
	protected static $load_default_css = true;
1399
1400
	/**
1401
	 * Include a css file, either speicified by it's path (relative to EGW_SERVER_ROOT) or appname and css file name
1402
	 *
1403
	 * @param string $app path (relative to EGW_SERVER_ROOT) or appname (if !is_null($name))
1404
	 * @param string $name =null name of css file in $app/templates/{default|$this->template}/$name.css
1405
	 * @param boolean $append =true true append file, false prepend (add as first) file used eg. for template itself
1406
	 * @param boolean $no_default_css =false true do NOT load any default css, only what app explicitly includes
1407
	 * @return boolean false: css file not found, true: file found
1408
	 */
1409
	public static function includeCSS($app, $name=null, $append=true, $no_default_css=false)
1410
	{
1411
		if ($no_default_css)
1412
		{
1413
			self::$load_default_css = false;
1414
		}
1415
		//error_log(__METHOD__."('$app', '$name', append=$append, no_default=$no_default_css) ".function_backtrace());
1416
		return Framework\CssIncludes::add($app, $name, $append, $no_default_css);
1417
	}
1418
1419
	/**
1420
	 * Add registered CSS and javascript to ajax response
1421
	 */
1422
	public static function include_css_js_response()
1423
	{
1424
		$response = Json\Response::get();
1425
		$app = $GLOBALS['egw_info']['flags']['currentapp'];
1426
1427
		// try to add app specific css file
1428
		self::includeCSS($app, 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
1429
			self::includeCSS($app,'app');
1430
1431
		// add all css files from Framework::includeCSS()
1432
		$query = null;
1433
		//error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get()));
1434
		foreach(Framework\CssIncludes::get() as $path)
0 ignored issues
show
Bug introduced by
The expression EGroupware\Api\Framework\CssIncludes::get() of type string is not traversable.
Loading history...
1435
		{
1436
			unset($query);
1437
			list($path,$query) = explode('?',$path,2);
1438
			$path .= '?'. ($query ? $query : filemtime(EGW_SERVER_ROOT.$path));
1439
			$response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path);
1440
		}
1441
1442
		// try to add app specific js file
1443
		self::includeJS('.', 'app', $app);
1444
1445
		// add all js files from Framework::includeJS()
1446
		$files = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files());
1447
		foreach($files as $path)
1448
		{
1449
			$response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path);
1450
		}
1451
	}
1452
1453
	/**
1454
	 * Set a preference via ajax
1455
	 *
1456
	 * @param string $app
1457
	 * @param string $name
1458
	 * @param string $value
1459
	 */
1460
	public static function ajax_set_preference($app, $name, $value)
1461
	{
1462
		$GLOBALS['egw']->preferences->read_repository();
1463
		if ((string)$value === '')
1464
		{
1465
			$GLOBALS['egw']->preferences->delete($app, $name);
1466
		}
1467
		else
1468
		{
1469
			$GLOBALS['egw']->preferences->add($app, $name, $value);
1470
		}
1471
		$GLOBALS['egw']->preferences->save_repository(True);
1472
	}
1473
1474
	/**
1475
	 * Get Preferences of a certain application via ajax
1476
	 *
1477
	 * @param string $app
1478
	 */
1479
	public static function ajax_get_preference($app)
1480
	{
1481
		// dont block session, while we read preferences, they are not supposed to change something in the session
1482
		$GLOBALS['egw']->session->commit_session();
1483
1484
		if (preg_match('/^[a-z0-9_]+$/i', $app))
1485
		{
1486
			// send etag header, if we are directly called (not via jsonq!)
1487
			if (strpos($_GET['menuaction'], __FUNCTION__) !== false)
1488
			{
1489
				$etag = '"'.$app.'-'.md5(json_encode($GLOBALS['egw_info']['user']['preferences'][$app])).'"';
1490
				if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
1491
				{
1492
					header("HTTP/1.1 304 Not Modified");
1493
					exit();
1494
				}
1495
				header('ETag: '.$etag);
1496
			}
1497
			$response = Json\Response::get();
1498
			$response->call('egw.set_preferences', (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app);
1499
		}
1500
	}
1501
1502
	/**
1503
	 * Create or delete a favorite for multiple users
1504
	 *
1505
	 * Need to be in egw_framework to be called with .template postfix from json.php!
1506
	 *
1507
	 * @param string $app Current application, needed to save preference
1508
	 * @param string $name Name of the favorite
1509
	 * @param string $action "add" or "delete"
1510
	 * @param boolean|int|string $group ID of the group to create the favorite for, or 'all' for all users
1511
	 * @param array $filters =array() key => value pairs for the filter
1512
	 * @return boolean Success
1513
	 */
1514
	public static function ajax_set_favorite($app, $name, $action, $group, $filters = array())
1515
	{
1516
		return Framework\Favorites::set_favorite($app, $name, $action, $group, $filters);
1517
	}
1518
1519
	/**
1520
	 * Get a cachable list of users for the client
1521
	 *
1522
	 * The account source takes care of access and filtering according to preference
1523
	 */
1524
	public static function ajax_user_list()
1525
	{
1526
		$list = array('accounts' => array(),'groups' => array(), 'owngroups' => array());
1527
		if($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'primary_group')
1528
		{
1529
			$list['accounts']['filter']['group'] = $GLOBALS['egw_info']['user']['account_primary_group'];
1530
		}
1531
		$contact_obj = new Contacts();
1532
		foreach($list as $type => &$accounts)
1533
		{
1534
			$options = array('account_type' => $type) + $accounts;
1535
			$key_pair = Accounts::link_query('',$options);
1536
			$accounts = array();
1537
			foreach($key_pair as $account_id => $name)
1538
			{
1539
				$contact = $contact_obj->read('account:'.$account_id, true);
1540
				$accounts[] = array('value' => $account_id, 'label' => $name, 'icon' => self::link('/api/avatar.php', array(
1541
						'contact_id' => $contact['id'],
1542
						'etag' => $contact['etag']
1543
					)));
1544
			}
1545
		}
1546
1547
		Json\Response::get()->data($list);
1548
		return $list;
1549
	}
1550
1551
	/**
1552
	 * Get certain account-data of given account-id(s)
1553
	 *
1554
	 * @param string|array $_account_ids
1555
	 * @param string $_field ='account_email'
1556
	 * @param boolean $_resolve_groups =false true: return attribute for all members, false return attribute for group itself
1557
	 * @return array account_id => data pairs
1558
	 */
1559
	public static function ajax_account_data($_account_ids, $_field, $_resolve_groups=false)
1560
	{
1561
		$list = array();
1562
		foreach((array)$_account_ids as $account_id)
1563
		{
1564
			foreach($account_id < 0 && $_resolve_groups ?
0 ignored issues
show
Comprehensibility Bug introduced by
$account_id is overwriting a variable from outer foreach loop.
Loading history...
1565
				$GLOBALS['egw']->accounts->members($account_id, true) : array($account_id) as $account_id)
1566
			{
1567
				// Make sure name is formatted according to preference
1568
				if($_field == 'account_fullname')
1569
				{
1570
					$list[$account_id] = Accounts::format_username(
1571
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_lid'),
1572
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_firstname'),
1573
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_lastname'),
1574
						$account_id
1575
					);
1576
				}
1577
				else
1578
				{
1579
					$list[$account_id] = $GLOBALS['egw']->accounts->id2name($account_id, $_field);
1580
				}
1581
			}
1582
		}
1583
1584
		Json\Response::get()->data($list);
1585
		return $list;
1586
	}
1587
}
1588
// Init all static variables
1589
Framework::init_static();
1590