Framework::js_files()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 2
dl 0
loc 7
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 self
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
	 * @param string $link
193
	 */
194
	static function getUrl($link)
195
	{
196
		return Header\Http::fullUrl($link);
197
	}
198
199
	/**
200
	 * Handles redirects under iis and apache, it does NOT return (calls exit)
201
	 *
202
	 * This function handles redirects under iis and apache it assumes that $phpgw->link() has already been called
203
	 *
204
	 * @param string $url url to redirect to
205
	 * @param string $link_app =null appname to redirect for, default currentapp
206
	 */
207
	static function redirect($url, $link_app=null)
208
	{
209
		// Determines whether the current output buffer should be flushed
210
		$do_flush = true;
211
212
		if (Json\Response::isJSONResponse() || Json\Request::isJSONRequest())
213
		{
214
			Json\Response::get()->redirect($url, false, $link_app);
215
216
			// check if we have a message, in which case send it along too
217
			$extra = self::get_extra();
218
			if ($extra['message'])
219
			{
220
				Json\Response::get()->apply('egw.message', $extra['message']);
221
			}
222
223
			// If we are in a json request, we should not flush the current output!
224
			$do_flush = false;
225
		}
226
		else
227
		{
228
			$file = $line = null;
229
			if (headers_sent($file,$line))
230
			{
231
				throw new Exception\AssertionFailed(__METHOD__."('".htmlspecialchars($url)."') can NOT redirect, output already started at $file line $line!");
232
			}
233
			if ($GLOBALS['egw']->framework instanceof Framework\Ajax && !empty($link_app))
234
			{
235
				self::set_extra('egw', 'redirect', array($url, $link_app));
236
				$GLOBALS['egw']->framework->render('');
237
			}
238
			else
239
			{
240
				Header("Location: $url");
241
				print("\n\n");
242
			}
243
		}
244
245
		if ($do_flush)
246
		{
247
			@ob_flush(); flush();
0 ignored issues
show
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...
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

247
			/** @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...
248
		}
249
250
		// commit session (if existing), to fix timing problems sometimes preventing session creation ("Your session can not be verified")
251
		if (isset($GLOBALS['egw']->session)) $GLOBALS['egw']->session->commit_session();
252
253
		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...
254
	}
255
256
	/**
257
	 * Redirects direct to a generated link
258
	 *
259
	 * @param string $url	The url the link is for
260
	 * @param string|array	$extravars	Extra params to be passed to the url
261
	 * @param string $link_app =null if appname or true, some templates generate a special link-handler url
262
	 * @return string	The full url after processing
263
	 */
264
	static function redirect_link($url, $extravars='', $link_app=null)
265
	{
266
		self::redirect(self::link($url, $extravars), $link_app);
267
	}
268
269
	/**
270
	 * Renders an applicaton page with the complete eGW framework (header, navigation and menu)
271
	 *
272
	 * This is the (new) prefered way to render a page in eGW!
273
	 *
274
	 * @param string $content Html of the main application area
275
	 * @param string $app_header =null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header']
276
	 * @param string $navbar =null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu
277
	 *
278
	 */
279
	function render($content,$app_header=null,$navbar=null)
280
	{
281
		if (!is_null($app_header)) $GLOBALS['egw_info']['flags']['app_header'] = $app_header;
282
		if (!is_null($navbar)) $GLOBALS['egw_info']['flags']['nonavbar'] = !$navbar;
283
284
		echo $this->header();
285
286
		if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || !$GLOBALS['egw_info']['flags']['nonavbar'])
287
		{
288
			echo $this->navbar();
289
		}
290
		echo $content;
291
292
		echo $this->footer();
293
	}
294
295
	/**
296
	 * Returns the html-header incl. the opening body tag
297
	 *
298
	 * @return string with Html
299
	 */
300
	abstract function header(array $extra=array());
301
302
	/**
303
	 * Returns the Html from the body-tag til the main application area (incl. opening div tag)
304
	 *
305
	 * If header has NOT been called, also return header content!
306
	 * No need to manually call header, this allows to postpone header so navbar / sidebox can include JS or CSS.
307
	 *
308
	 * @return string with Html
309
	 */
310
	abstract function navbar();
311
312
	/**
313
	 * Return true if we are rendering the top-level EGroupware window
314
	 *
315
	 * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself
316
	 *
317
	 * @return boolean $consider_navbar_not_yet_called_as_true=true
318
	 * @return boolean
319
	 */
320
	abstract function isTop($consider_navbar_not_yet_called_as_true=true);
321
322
	/**
323
	 * Returns the content of one sidebox
324
	 *
325
	 * @param string $appname
326
	 * @param string $menu_title
327
	 * @param array $file
328
	 * @param string $type =null 'admin', 'preferences', 'favorites', ...
329
	 */
330
	abstract function sidebox($appname,$menu_title,$file,$type=null);
331
332
	/**
333
	 * Returns the Html from the closing div of the main application area to the closing html-tag
334
	 *
335
	 * @return string
336
	 */
337
	abstract function footer();
338
339
	/**
340
	 * Displays the login screen
341
	 *
342
	 * @param string $extra_vars for login url
343
	 * @param string $change_passwd =null string with message to render input fields for password change
344
	 */
345
	function login_screen($extra_vars, $change_passwd=null)
346
	{
347
		(new Framework\Login($this))->screen($extra_vars, $change_passwd);
348
	}
349
350
	/**
351
	 * displays a login denied message
352
	 */
353
	function denylogin_screen()
354
	{
355
		(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

355
		(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...
356
	}
357
358
	/**
359
	 * Calculate page-generation- and session-restore times
360
	 *
361
	 * @return array values for keys 'page_generation_time' and 'session_restore_time', if display is an
362
	 */
363
	public static function get_page_generation_time()
364
	{
365
		$times = array(
366
			'page_generation_time' => sprintf('%4.2lf', microtime(true) - $GLOBALS['egw_info']['flags']['page_start_time']),
367
		);
368
		if ($GLOBALS['egw_info']['flags']['session_restore_time'])
369
		{
370
			$times['session_restore_time'] = sprintf('%4.2lf', $GLOBALS['egw_info']['flags']['session_restore_time']);
371
		}
372
		return $times;
373
	}
374
375
	/**
376
	 * Get footer as array to eg. set as vars for a template (from idots' head.inc.php)
377
	 *
378
	 * @return array
379
	 */
380
	public function _get_footer()
381
	{
382
		$var = Array(
383
			'img_root'       => $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images',
384
			'version'        => $GLOBALS['egw_info']['server']['versions']['api']
385
		);
386
		$var['page_generation_time'] = '';
387
		if($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time'])
388
		{
389
			$times = self::get_page_generation_time();
390
391
			$var['page_generation_time'] = '<div class="pageGenTime" id="divGenTime_'.$GLOBALS['egw_info']['flags']['currentapp'].'"><span>'.
392
				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

392
				/** @scrutinizer ignore-call */ 
393
    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...
393
394
			if (isset($times['session_restore_time']))
395
			{
396
				$var['page_generation_time'] .= ' '.lang('(session restored in %1 seconds)',
397
					$times['session_restore_time']);
398
			}
399
			$var['page_generation_time'] .= '</span></div>';
400
		}
401
		if (empty($GLOBALS['egw_info']['server']['versions']['maintenance_release']))
402
		{
403
			$GLOBALS['egw_info']['server']['versions']['maintenance_release'] = self::api_version();
404
		}
405
		$var['powered_by'] = '<a href="http://www.egroupware.org/" class="powered_by" target="_blank">'.
406
			lang('Powered by').' EGroupware '.
407
			$GLOBALS['egw_info']['server']['versions']['maintenance_release'].'</a>';
408
409
		return $var;
410
	}
411
412
	/**
413
	 * Body tags for onLoad, onUnload and onResize
414
	 *
415
	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
416
	 * @var array
417
	 */
418
	protected static $body_tags = array();
419
420
	/**
421
	 * Adds on(Un)Load= attributes to the body tag of a page
422
	 *
423
	 * Can only be set via egw_framework::set_on* methods.
424
	 *
425
	 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
426
	 * @returns string the attributes to be used
427
	 */
428
	static public function _get_body_attribs()
429
	{
430
		$js = '';
431
		foreach(self::$body_tags as $what => $data)
432
		{
433
			if (!empty($data))
434
			{
435
				if($what == 'onLoad')
436
				{
437
					$js .= 'onLoad="egw_LAB.wait(function() {'. htmlspecialchars($data).'})"';
438
					continue;
439
				}
440
				$js .= ' '.$what.'="' . htmlspecialchars($data) . '"';
441
			}
442
		}
443
		return $js;
444
	}
445
446
	/**
447
	 * Get header as array to eg. set as vars for a template (from idots' head.inc.php)
448
	 *
449
	 * @param array $extra =array() extra attributes passed as data-attribute to egw.js
450
	 * @return array
451
	 */
452
	protected function _get_header(array $extra=array())
453
	{
454
		// display password expires in N days message once per session
455
		$message = null;
456
		if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' &&
457
			Auth::check_password_change($message) !== true)
458
		{
459
			self::message($message, 'info');
460
		}
461
462
		// get used language code (with a little xss check, if someone tries to sneak something in)
463
		if (preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$GLOBALS['egw_info']['user']['preferences']['common']['lang']))
464
		{
465
			$lang_code = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
466
		}
467
		// IE specific fixes
468
		if (Header\UserAgent::type() == 'msie')
469
		{
470
			// tell IE to use it's own mode, not old compatibility modes (set eg. via group policy for all intranet sites)
471
			// has to be before any other header tags, but meta and title!!!
472
			$pngfix = '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'."\n";
473
		}
474
475
		$app = $GLOBALS['egw_info']['flags']['currentapp'];
476
		$app_title = isset($GLOBALS['egw_info']['apps'][$app]) ? $GLOBALS['egw_info']['apps'][$app]['title'] : lang($app);
477
		$app_header = $GLOBALS['egw_info']['flags']['app_header'] ? $GLOBALS['egw_info']['flags']['app_header'] : $app_title;
478
		$site_title = strip_tags($GLOBALS['egw_info']['server']['site_title'].' ['.($app_header ? $app_header : $app_title).']');
479
480
		// send appheader to clientside
481
		$extra['app-header'] = $app_header;
482
483
		if($GLOBALS['egw_info']['flags']['currentapp'] != 'wiki') $robots ='<meta name="robots" content="none" />';
484
485
		$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...
486
487
		if ($GLOBALS['egw_info']['flags']['include_wz_tooltip'] &&
488
			file_exists(EGW_SERVER_ROOT.($wz_tooltip = '/phpgwapi/js/wz_tooltip/wz_tooltip.js')))
489
		{
490
			$include_wz_tooltip = '<script src="'.$GLOBALS['egw_info']['server']['webserver_url'].
491
				$wz_tooltip.'?'.filemtime(EGW_SERVER_ROOT.$wz_tooltip).'" type="text/javascript"></script>';
492
		}
493
		return $this->_get_css()+array(
494
			'img_icon'			=> $var['favicon_file'],
495
			'img_shortcut'		=> $var['favicon_file'],
496
			'pngfix'        	=> $pngfix,
497
			'lang_code'			=> $lang_code,
498
			'charset'       	=> Translation::charset(),
499
			'website_title' 	=> $site_title,
500
			'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

500
			'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...
501
			'java_script'   	=> self::_get_js($extra),
502
			'meta_robots'		=> $robots,
503
			'dir_code'			=> lang('language_direction_rtl') != 'rtl' ? '' : ' dir="rtl"',
504
			'include_wz_tooltip'=> $include_wz_tooltip,
505
			'webserver_url'     => $GLOBALS['egw_info']['server']['webserver_url'],
506
		);
507
	}
508
509
	/**
510
	 * Get navbar as array to eg. set as vars for a template (from idots' navbar.inc.php)
511
	 *
512
	 * @param array $apps navbar apps from _get_navbar_apps
513
	 * @return array
514
	 */
515
	protected function _get_navbar($apps)
516
	{
517
		$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...
518
519
		if(isset($GLOBALS['egw_info']['flags']['app_header']))
520
		{
521
			$var['current_app_title'] = $GLOBALS['egw_info']['flags']['app_header'];
522
		}
523
		else
524
		{
525
			$var['current_app_title']=$apps[$GLOBALS['egw_info']['flags']['currentapp']]['title'];
526
		}
527
		$var['currentapp'] = $GLOBALS['egw_info']['flags']['currentapp'];
528
529
		// quick add selectbox
530
		$var['quick_add'] = $this->_get_quick_add();
531
532
		$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...
533
534
		if($GLOBALS['egw_info']['user']['account_lastpwd_change'] == 0)
535
		{
536
			$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...
537
				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

537
				/** @scrutinizer ignore-call */ 
538
    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...
538
		}
539
		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']))
540
		{
541
			$api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']);
542
		}
543
544
		$var['logo_header'] = $var['logo_file'] = self::get_login_logo_or_bg_url('login_logo_file', 'logo');
545
546
		if ($GLOBALS['egw_info']['server']['login_logo_header'])
547
		{
548
			$var['logo_header'] = self::get_login_logo_or_bg_url('login_logo_header', 'logo');
549
		}
550
551
		$var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.egroupware.org';
552
553
		if (substr($var['logo_url'],0,4) != 'http')
554
		{
555
			$var['logo_url'] = 'http://'.$var['logo_url'];
556
		}
557
		$var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.egroupware.org';
558
559
		return $var;
560
	}
561
562
	/**
563
	 * Get login logo or background image base on requested config type
564
	 *
565
	 * @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...
566
	 * @param type $find_type type of image to search on as alternative option. e.g.: "logo"
567
	 *
568
	 * @return string returns full url of the image
569
	 */
570
	static function get_login_logo_or_bg_url ($type, $find_type)
571
	{
572
		$url = is_array($GLOBALS['egw_info']['server'][$type]) ?
573
			$GLOBALS['egw_info']['server'][$type][0] :
574
			$GLOBALS['egw_info']['server'][$type];
575
576
		if (substr($url, 0, 4) == 'http' ||
577
			$url[0] == '/')
578
		{
579
			return $url;
580
		}
581
		else
582
		{
583
			return Image::find('api',$url ? $url : $find_type, '', null);
584
		}
585
	}
586
587
	/**
588
	 * Returns Html with user and time
589
	 *
590
	 * @return void
591
	 */
592
	protected static function _user_time_info()
593
	{
594
		$now = new DateTime();
595
		$user_info = '<span>'.lang($now->format('l')) . ' ' . $now->format(true).'</span>';
596
597
		$user_tzs = DateTime::getUserTimezones();
598
		if (count($user_tzs) > 1)
599
		{
600
			$tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz'];
601
			$user_info .= Html::form(Html::select('tz',$tz,$user_tzs,true),array(),
602
				'/index.php','','tz_selection',' style="display: inline;"','GET');
603
		}
604
		return $user_info;
605
	}
606
607
	/**
608
	 * Returns user avatar menu
609
	 *
610
	 * @return string
611
	 */
612
	protected static function _user_avatar_menu()
613
	{
614
		$stat = array_pop(Hooks::process('framework_avatar_stat'));
0 ignored issues
show
Bug introduced by
EGroupware\Api\Hooks::pr...framework_avatar_stat') cannot be passed to array_pop() as the parameter $array expects a reference. ( Ignorable by Annotation )

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

614
		$stat = array_pop(/** @scrutinizer ignore-type */ Hooks::process('framework_avatar_stat'));
Loading history...
615
616
		return '<span title="'.Accounts::format_username().'" class="avatar"><img src="'.Egw::link('/api/avatar.php', array(
617
								'account_id' => $GLOBALS['egw_info']['user']['account_id'],
618
							)).'"/>'.(!empty($stat) ?
619
				'<span class="fw_avatar_stat '.$stat['class'].'" title="'.$stat['title'].'">'.$stat['body'].'</span>' : '').'</span>';
620
	}
621
622
	/**
623
	 * Returns logout menu
624
	 *
625
	 * @return string
626
	 */
627
	protected static function _logout_menu()
628
	{
629
		return '<a href="'.Egw::link('/logout.php').'" title="'.lang("Logout").'" ></a>';
630
	}
631
632
	/**
633
	 * Returns print menu
634
	 *
635
	 * @return string
636
	 */
637
	protected static function _print_menu()
638
	{
639
		return '<span title="'.lang("Print current view").'"</span>';
640
	}
641
642
643
	/**
644
	 * Prepare the current users
645
	 *
646
	 * @return array
647
	 */
648
	protected static function _current_users()
649
	{
650
	   if( $GLOBALS['egw_info']['user']['apps']['admin'] && $GLOBALS['egw_info']['user']['preferences']['common']['show_currentusers'])
651
	   {
652
		   return [
653
			   'name' => 'current_user',
654
			   'title' => lang('Current users').':'.$GLOBALS['egw']->session->session_count(),
655
			   'url' => self::link('/index.php','menuaction=admin.admin_accesslog.sessions&ajax=true')
656
		   ];
657
	   }
658
	}
659
660
	/**
661
	 * Prepare the quick add selectbox
662
	 *
663
	 * @return string
664
	 */
665
	protected static function _get_quick_add()
666
	{
667
		return '<span id="quick_add" title="'.lang('Quick add').'"></span>';
668
	}
669
670
	/**
671
	 * Prepare notification signal (blinking bell)
672
	 *
673
	 * @return string
674
	 */
675
	protected static function _get_notification_bell()
676
	{
677
		return Html::image('notifications', 'notificationbell', lang('notifications'),
678
			'id="notificationbell" style="display: none"');
679
	}
680
681
	/**
682
	 * Get context to use with file_get_context or fopen to use our proxy settings from setup
683
	 *
684
	 * @param string $username =null username for regular basic Auth
685
	 * @param string $password =null password --------- " ----------
686
	 * @param array $opts =array() further params for http(s) context, eg. array('timeout' => 123)
687
	 * @return resource|null context to use with file_get_context/fopen or null if no proxy configured
688
	 */
689
	public static function proxy_context($username=null, $password=null, array $opts = array())
690
	{
691
		$opts += array(
692
			'method' => 'GET',
693
		);
694
		if (!empty($GLOBALS['egw_info']['server']['httpproxy_server']))
695
		{
696
			$opts += array (
697
				'proxy'  => 'tcp://'.$GLOBALS['egw_info']['server']['httpproxy_server'].':'.
698
					($GLOBALS['egw_info']['server']['httpproxy_port'] ? $GLOBALS['egw_info']['server']['httpproxy_port'] : 8080),
699
				'request_fulluri' => true,
700
			);
701
			// proxy authentication
702
			if (!empty($GLOBALS['egw_info']['server']['httpproxy_server_username']))
703
			{
704
				$opts['header'][] = 'Proxy-Authorization: Basic '.base64_encode($GLOBALS['egw_info']['server']['httpproxy_server_username'].':'.
705
					$GLOBALS['egw_info']['server']['httpproxy_server_password']);
706
			}
707
		}
708
		// optional authentication
709
		if (isset($username))
710
		{
711
			$opts['header'][] = 'Authorization: Basic '.base64_encode($username.':'.$password);
712
		}
713
		return stream_context_create(array(
714
			'http' => $opts,
715
			'https' => $opts,
716
		));
717
	}
718
719
	/**
720
	 * Get API version from changelog or database, whichever is bigger
721
	 *
722
	 * @param string &$changelog on return path to changelog
723
	 * @return string
724
	 */
725
	public static function api_version(&$changelog=null)
726
	{
727
		return Framework\Updates::api_version($changelog);
728
	}
729
730
	/**
731
	 * Get the link to an application's index page
732
	 *
733
	 * @param string $app
734
	 * @return string
735
	 */
736
	public static function index($app)
737
	{
738
		$data =& $GLOBALS['egw_info']['user']['apps'][$app];
739
		if (!isset($data))
740
		{
741
			throw new Exception\WrongParameter("'$app' not a valid app for this user!");
742
		}
743
		$index = '/'.$app.'/index.php';
744
		if (isset($data['index']))
745
		{
746
			if ($data['index'][0] == '/')
747
			{
748
				$index = $data['index'];
749
			}
750
			else
751
			{
752
				$index = '/index.php?menuaction='.$data['index'];
753
			}
754
		}
755
		return self::link($index,$GLOBALS['egw_info']['flags']['params'][$app]);
756
	}
757
758
	/**
759
	 * Used internally to store unserialized value of $GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']
760
	 */
761
	private static $user_apporder = array();
762
763
	/**
764
	 * Internal usort callback function used to sort an array according to the
765
	 * user sort order
766
	 */
767
	private static function _sort_apparray($a, $b)
768
	{
769
		//Unserialize the user_apporder array
770
		$arr = self::$user_apporder;
771
772
		$ind_a = isset($arr[$a['name']]) ? $arr[$a['name']] : null;
773
		$ind_b = isset($arr[$b['name']]) ? $arr[$b['name']] : null;
774
775
		if ($ind_a == $ind_b)
776
			return 0;
777
778
		if ($ind_a == null)
779
			return -1;
780
781
		if ($ind_b == null)
782
			return 1;
783
784
		return $ind_a > $ind_b ? 1 : -1;
785
	}
786
787
	/**
788
	 * Prepare an array with apps used to render the navbar
789
	 *
790
	 * This is similar to the former common::navbar() method - though it returns the vars and does not place them in global scope.
791
	 *
792
	 * @return array
793
	 */
794
	protected static function _get_navbar_apps()
795
	{
796
		$first = key($GLOBALS['egw_info']['user']['apps']);
797
		if(is_array($GLOBALS['egw_info']['user']['apps']['admin']) && $first != 'admin')
798
		{
799
			$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...
800
			foreach($GLOBALS['egw_info']['user']['apps'] as $index => $value)
801
			{
802
				if($index != 'admin')
803
				{
804
					$newarray[$index] = $value;
805
				}
806
			}
807
			$GLOBALS['egw_info']['user']['apps'] = $newarray;
808
			reset($GLOBALS['egw_info']['user']['apps']);
809
		}
810
		unset($index);
811
		unset($value);
812
		unset($newarray);
813
814
		$apps = array();
815
		foreach($GLOBALS['egw_info']['user']['apps'] as $app => $data)
816
		{
817
			if (is_long($app))
818
			{
819
				continue;
820
			}
821
822
			if ($app == 'preferences' || $GLOBALS['egw_info']['apps'][$app]['status'] != 2 && $GLOBALS['egw_info']['apps'][$app]['status'] != 3)
823
			{
824
				$apps[$app]['title'] = $GLOBALS['egw_info']['apps'][$app]['title'];
825
				$apps[$app]['url']   = self::index($app);
826
				$apps[$app]['name']  = $app;
827
828
				// create popup target
829
				if ($data['status'] == 4)
830
				{
831
					$apps[$app]['target'] = ' target="'.$app.'" onClick="'."if (this != '') { window.open(this+'".
832
						(strpos($apps[$app]['url'],'?') !== false ? '&' : '?').
833
						"referer='+encodeURIComponent(location),this.target,'width=800,height=600,scrollbars=yes,resizable=yes'); return false; } else { return true; }".'"';
834
				}
835
				elseif(isset($GLOBALS['egw_info']['flags']['navbar_target']) && $GLOBALS['egw_info']['flags']['navbar_target'])
836
				{
837
					$apps[$app]['target'] = 'target="' . $GLOBALS['egw_info']['flags']['navbar_target'] . '"';
838
				}
839
				else
840
				{
841
					$apps[$app]['target'] = '';
842
				}
843
844
				// take status flag into account as we might use it on client-side.
845
				// for instance: applications with status 5 will run in background
846
				$apps[$app]['status'] = $data['status'];
847
848
				$icon = isset($data['icon']) ?  $data['icon'] : 'navbar';
849
				$icon_app = isset($data['icon_app']) ? $data['icon_app'] : $app;
850
				$apps[$app]['icon']  = $apps[$app]['icon_hover']  = Image::find($icon_app,Array($icon,'nonav'),'');
851
			}
852
		}
853
854
		//Sort the applications accordingly to their user sort setting
855
		if ($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder'])
856
		{
857
			//Sort the application array using the user_apporder array as sort index
858
			self::$user_apporder =
859
				unserialize($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']);
860
			uasort($apps, __CLASS__.'::_sort_apparray');
861
		}
862
863
		if ($GLOBALS['egw_info']['flags']['currentapp'] == 'preferences' || $GLOBALS['egw_info']['flags']['currentapp'] == 'about')
864
		{
865
			$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...
866
		}
867
		else
868
		{
869
			$app = $GLOBALS['egw_info']['flags']['currentapp'];
870
			$app_title = $GLOBALS['egw_info']['apps'][$app]['title'];
871
		}
872
873
		if ($GLOBALS['egw_info']['user']['apps']['preferences'])	// Preferences last
874
		{
875
			$prefs = $apps['preferences'];
876
			unset($apps['preferences']);
877
			$apps['preferences'] = $prefs;
878
		}
879
880
		// We handle this here because its special
881
		$apps['about']['title'] = 'EGroupware';
882
		$apps['about']['url']   = self::link('/about.php');
883
		$apps['about']['icon']  = $apps['about']['icon_hover'] = Image::find('api',Array('about','nonav'));
884
		$apps['about']['name']  = 'about';
885
886
		$apps['logout']['title'] = lang('Logout');
887
		$apps['logout']['name']  = 'logout';
888
		$apps['logout']['url']   = self::link('/logout.php');
889
		$apps['logout']['icon']  = $apps['logout']['icon_hover'] = Image::find('api',Array('logout','nonav'));
890
891
		return $apps;
892
	}
893
894
	/**
895
	 * Used by template headers for including CSS in the header
896
	 *
897
	 * 'app_css'   - css styles from a) the menuaction's css-method and b) the $GLOBALS['egw_info']['flags']['css']
898
	 * 'file_css'  - link tag of the app.css file of the current app
899
	 * 'theme_css' - url of the theme css file
900
	 * 'print_css' - url of the print css file
901
	 *
902
	 * @author Dave Hall (*based* on verdilak? css inclusion code)
903
	 * @return array with keys 'app_css' from the css method of the menuaction-class and 'file_css' (app.css file of the application)
904
	 */
905
	public function _get_css()
906
	{
907
		$app_css = '';
908
		if (isset($GLOBALS['egw_info']['flags']['css']))
909
		{
910
			$app_css = $GLOBALS['egw_info']['flags']['css'];
911
		}
912
913
		if (self::$load_default_css)
914
		{
915
			// For mobile user-agent we prefer mobile theme over selected one with a final fallback to theme named as template
916
			$themes_to_check = array();
917
			if (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile')
918
			{
919
				$themes_to_check[] = $this->template_dir.'/mobile/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css';
920
				$themes_to_check[] = $this->template_dir.'/mobile/fw_mobile.css';
921
			}
922
			$themes_to_check[] = $this->template_dir.'/css/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css';
923
			$themes_to_check[] = $this->template_dir.'/css/'.$this->template.'.css';
924
			foreach($themes_to_check as $theme_css)
925
			{
926
				if (file_exists(EGW_SERVER_ROOT.$theme_css)) break;
927
			}
928
			$debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
929
			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 924. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
930
			{
931
				//error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get()));
932
				self::includeCSS($theme_min_css);
933
934
				// Global category styles
935
				if (basename($_SERVER['PHP_SELF']) !== 'login.php')
936
				{
937
					Categories::css(Categories::GLOBAL_APPNAME);
938
				}
939
			}
940
			else
941
			{
942
				// Load these first
943
				// Cascade should go:
944
				//  Libs < etemplate2 < framework/theme < app < print
945
				// Enhanced selectboxes (et1)
946
				self::includeCSS('/api/js/jquery/chosen/chosen.css');
947
948
				// eTemplate2 uses jQueryUI, so load it first so et2 can override if needed
949
				self::includeCSS("/vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css");
950
951
				// eTemplate2 - load in top so sidebox has styles too
952
				self::includeCSS('/api/templates/default/etemplate2.css');
953
954
				// Category styles
955
				if (basename($_SERVER['PHP_SELF']) !== 'login.php')
956
				{
957
					Categories::css(Categories::GLOBAL_APPNAME);
958
				}
959
960
				self::includeCSS($theme_css);
961
962
				// sending print css last, so it can overwrite anything
963
				$print_css = $this->template_dir.'/print.css';
964
				if(!file_exists(EGW_SERVER_ROOT.$print_css))
965
				{
966
					$print_css = '/api/templates/default/print.css';
967
				}
968
				self::includeCSS($print_css);
969
			}
970
			// search for app specific css file, so it can customize the theme
971
			self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
972
				self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app');
973
		}
974
		return array(
975
			'app_css'   => $app_css,
976
			'css_file'  => Framework\CssIncludes::tags(),
977
		);
978
	}
979
980
	/**
981
	 * Used by the template headers for including javascript in the header
982
	 *
983
	 * The method is included here to make it easier to change the js support
984
	 * in eGW.  One change then all templates will support it (as long as they
985
	 * include a call to this method).
986
	 *
987
	 * @param array $extra =array() extra data to pass to egw.js as data-parameter
988
	 * @return string the javascript to be included
989
	 */
990
	public static function _get_js(array $extra=array())
991
	{
992
		$java_script = '';
993
994
		/* this flag is for all javascript code that has to be put before other jscode.
995
		Think of conf vars etc...  ([email protected]) */
996
		if (isset($GLOBALS['egw_info']['flags']['java_script_thirst']))
997
		{
998
			$java_script .= $GLOBALS['egw_info']['flags']['java_script_thirst'] . "\n";
999
		}
1000
		// add configuration, link-registry, images, user-data and -perferences for non-popup windows
1001
		// specifying etag in url to force reload, as we send expires header
1002
		if ($GLOBALS['egw_info']['flags']['js_link_registry'])
1003
		{
1004
			self::includeJS('/api/config.php', array(
1005
				'etag' => md5(json_encode(Config::clientConfigs()).Link::json_registry()),
1006
			));
1007
			self::includeJS('/api/images.php', array(
1008
				'template' => $GLOBALS['egw_info']['server']['template_set'],
1009
				'etag' => md5(json_encode(Image::map($GLOBALS['egw_info']['server']['template_set'])))
1010
			));
1011
			self::includeJS('/api/user.php', array(
1012
				'user' => $GLOBALS['egw_info']['user']['account_lid'],
1013
				'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'],
1014
				// add etag on url, so we can set an expires header
1015
				'etag' => md5(json_encode($GLOBALS['egw_info']['user']['preferences']['common']).
1016
					$GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id'])),
1017
			));
1018
		}
1019
1020
		$extra['url'] = $GLOBALS['egw_info']['server']['webserver_url'];
1021
		$extra['include'] = array_map(function($str){return substr($str,1);}, self::get_script_links(true), array(1));
1022
		$extra['app'] = $GLOBALS['egw_info']['flags']['currentapp'];
1023
1024
		// Load LABjs ONCE here
1025
		$java_script .= '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1026
				'/api/js/labjs/LAB.src.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/labjs/LAB.src.js')."\"></script>\n".
1027
			'<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1028
				'/api/js/jsapi/egw.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/jsapi/egw.js').'" id="egw_script_id"';
1029
1030
		// add values of extra parameter and class var as data attributes to script tag of egw.js
1031
		foreach($extra+self::$extra as $name => $value)
1032
		{
1033
			if (is_array($value)) $value = json_encode($value);
1034
			// we need to double encode (Html::htmlspecialchars( , TRUE)), as otherwise we get invalid json, eg. for quotes
1035
			$java_script .= ' data-'.$name."=\"". Html::htmlspecialchars($value, true)."\"";
1036
		}
1037
		$java_script .= "></script>\n";
1038
1039
		if(@isset($_GET['menuaction']))
1040
		{
1041
			list(, $class) = explode('.',$_GET['menuaction']);
1042
			if(is_array($GLOBALS[$class]->public_functions) &&
1043
				$GLOBALS[$class]->public_functions['java_script'])
1044
			{
1045
				$java_script .= $GLOBALS[$class]->java_script();
1046
			}
1047
		}
1048
		if (isset($GLOBALS['egw_info']['flags']['java_script']))
1049
		{
1050
			// Strip out any script tags, this needs to be executed as anonymous function
1051
			$GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']);
1052
			if(trim($GLOBALS['egw_info']['flags']['java_script']) != '')
1053
			{
1054
				$java_script .= '<script type="text/javascript">window.egw_LAB.wait(function() {'.$GLOBALS['egw_info']['flags']['java_script'] . "});</script>\n";
1055
			}
1056
		}
1057
1058
		return $java_script;
1059
	}
1060
1061
	/**
1062
	 * List available themes
1063
	 *
1064
	 * Themes are css file in the template directory
1065
	 *
1066
	 * @param string $themes_dir ='css'
1067
	 */
1068
	function list_themes()
1069
	{
1070
		$list = array();
1071
		if (file_exists($file=EGW_SERVER_ROOT.$this->template_dir.'/setup/setup.inc.php') &&
1072
			(include $file) && isset($GLOBALS['egw_info']['template'][$this->template]['themes']))
1073
		{
1074
			$list = $GLOBALS['egw_info']['template'][$this->template]['themes'];
1075
		}
1076
		if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir.'/css')))
1077
		{
1078
			while (($file = readdir($dh)))
1079
			{
1080
				if (preg_match('/'."\.css$".'/i', $file))
1081
				{
1082
					list($name) = explode('.',$file);
1083
					if (!isset($list[$name])) $list[$name] = ucfirst ($name);
1084
				}
1085
			}
1086
			closedir($dh);
1087
		}
1088
		return $list;
1089
	}
1090
1091
	/**
1092
	 * List available templates
1093
	 *
1094
	 * @param boolean $full_data =false true: value is array with values for keys 'name', 'title', ...
1095
	 * @returns array alphabetically sorted list of templates
1096
	 */
1097
	static function list_templates($full_data=false)
1098
	{
1099
		$list = array('pixelegg'=>null);
1100
		// templates packaged like apps in own directories (containing as setup/setup.inc.php file!)
1101
		$dr = dir(EGW_SERVER_ROOT);
1102
		while (($entry=$dr->read()))
1103
		{
1104
			if ($entry != '..' && !isset($GLOBALS['egw_info']['apps'][$entry]) && is_dir(EGW_SERVER_ROOT.'/'.$entry) &&
1105
				file_exists($f = EGW_SERVER_ROOT . '/' . $entry .'/setup/setup.inc.php'))
1106
			{
1107
				include($f);
1108
				if (isset($GLOBALS['egw_info']['template'][$entry]))
1109
				{
1110
					$list[$entry] = $full_data ? $GLOBALS['egw_info']['template'][$entry] :
1111
						$GLOBALS['egw_info']['template'][$entry]['title'];
1112
				}
1113
			}
1114
		}
1115
		$dr->close();
1116
1117
		return array_filter($list);
1118
	}
1119
1120
	/**
1121
	* Compile entries for topmenu:
1122
	* - regular items: links
1123
	* - info items
1124
	*
1125
	* @param array $vars
1126
	* @param array $apps
1127
	*/
1128
	function topmenu(array $vars,array $apps)
1129
	{
1130
		// array of topmenu info items (orders of the items matter)
1131
		$topmenu_info_items = [
1132
			'user_avatar' => $this->_user_avatar_menu(),
1133
			'logout' => (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile') ? self::_logout_menu() : null,
1134
			'update' => ($update = Framework\Updates::notification()) ? $update : null,
1135
			'notifications' => ($GLOBALS['egw_info']['user']['apps']['notifications']) ? self::_get_notification_bell() : null,
1136
			'quick_add' => $vars['quick_add'],
1137
			'print_title' => $this->_print_menu()
1138
		];
1139
1140
		// array of topmenu items (orders of the items matter)
1141
		$topmenu_items = [
1142
			0 => (is_array(($current_user = $this->_current_users()))) ? $current_user : null,
0 ignored issues
show
introduced by
The condition is_array($current_user = $this->_current_users()) is always true.
Loading history...
1143
		];
1144
1145
		// Home should be at the top before preferences
1146
		if($GLOBALS['egw_info']['user']['apps']['home'] && isset($apps['home']))
1147
		{
1148
			$this->_add_topmenu_item($apps['home']);
1149
		}
1150
1151
		// array of topmenu preferences items (orders of the items matter)
1152
		$topmenu_preferences = ['prefs', 'acl', 'cats', 'security'];
1153
1154
		// set topmenu preferences items
1155
		if($GLOBALS['egw_info']['user']['apps']['preferences'])
1156
		{
1157
			foreach ($topmenu_preferences as $prefs)
1158
			{
1159
				$this->add_preferences_topmenu($prefs);
1160
			}
1161
		}
1162
1163
		// call topmenu info items hooks
1164
		Hooks::process('topmenu_info',array(),true);
1165
1166
		// Add extra items added by hooks
1167
		foreach(self::$top_menu_extra as $extra_item) {
1168
			if ($extra_item['name'] == 'search')
1169
			{
1170
				$topmenu_info_items['search'] = '<a href="'.$extra_item['url'].'" title="'.$extra_item['title'].'"></a>';
1171
			}
1172
			else
1173
			{
1174
				array_push($topmenu_items, $extra_item);
1175
			}
1176
		}
1177
		// push logout as the last item in topmenu items list
1178
		array_push($topmenu_items, $apps['logout']);
1179
1180
		// set topmenu info items
1181
		foreach ($topmenu_info_items as $id => $content)
1182
		{
1183
			if (!$content || (in_array($id, ['search', 'quick_add', 'update']) && (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile')))
1184
			{
1185
				continue;
1186
			}
1187
			$this->_add_topmenu_info_item($content, $id);
1188
		}
1189
		// set topmenu items
1190
		foreach ($topmenu_items as $item)
1191
		{
1192
			if ($item) $this->_add_topmenu_item($item);
1193
		}
1194
	}
1195
1196
	/**
1197
	 * Add Preferences link to topmenu using settings-hook to know if an app supports Preferences
1198
	 */
1199
	protected function add_preferences_topmenu($type='prefs')
1200
	{
1201
		static $memberships=null;
1202
		if (!isset($memberships)) $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true);
1203
		static $types = array(
1204
			'prefs' => array(
1205
				'title' => 'Preferences',
1206
				'hook'  => 'settings',
1207
			),
1208
			'acl' => array(
1209
				'title' => 'Access',
1210
				'hook'  => 'acl_rights',
1211
			),
1212
			'cats' => array(
1213
				'title' => 'Categories',
1214
				'hook' => 'categories',
1215
				'run_hook' => true,	// acturally run hook, not just look it's implemented
1216
			),
1217
			'security' => array(
1218
				'title' => 'Security & Password',
1219
				'hook' => 'preferences_security',
1220
			),
1221
		);
1222
		if (!$GLOBALS['egw_info']['user']['apps']['preferences'] || $GLOBALS['egw_info']['server']['deny_'.$type] &&
1223
			array_intersect($memberships, (array)$GLOBALS['egw_info']['server']['deny_'.$type]) &&
1224
			!$GLOBALS['egw_info']['user']['apps']['admin'])
1225
		{
1226
			return;	// user has no access to Preferences app
1227
		}
1228
		if (isset($types[$type]['run_hook']))
1229
		{
1230
			$apps = Hooks::process($types[$type]['hook']);
1231
			// as all apps answer, we need to remove none-true responses
1232
			foreach($apps as $app => $val)
1233
			{
1234
				if (!$val) unset($apps[$app]);
1235
			}
1236
		}
1237
		else
1238
		{
1239
			$apps = Hooks::implemented($types[$type]['hook']);
1240
		}
1241
		// allways display password in topmenu, if user has rights to change it
1242
		switch ($type)
1243
		{
1244
			case 'security':
1245
				if ($apps || $GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' ||
1246
					!$GLOBALS['egw']->acl->check('nopasswordchange', 1))
1247
				{
1248
					$this->_add_topmenu_item(array(
1249
						'id'    => 'password',
1250
						'name'  => 'preferences',
1251
						'title' => lang($types[$type]['title']),
1252
						'url'   => "javascript:egw.open_link('".
1253
							self::link('/index.php?menuaction=preferences.preferences_password.change')."','_blank','850x580')",
1254
					));
1255
				}
1256
				break;
1257
1258
			default:
1259
				$this->_add_topmenu_item(array(
1260
					'id' => $type,
1261
					'name' => 'preferences',
1262
					'title' => lang($types[$type]['title']),
1263
					'url' => "javascript:egw.show_preferences(\"$type\",".json_encode($apps).')',
1264
				));
1265
		}
1266
	}
1267
1268
	/**
1269
	* Add menu items to the topmenu template class to be displayed
1270
	*
1271
	* @param array $app application data
1272
	* @param mixed $alt_label string with alternative menu item label default value = null
1273
	* @param string $urlextra string with alternate additional code inside <a>-tag
1274
	* @access protected
1275
	* @return void
1276
	*/
1277
	abstract function _add_topmenu_item(array $app_data,$alt_label=null);
1278
1279
	/**
1280
	* Add info items to the topmenu template class to be displayed
1281
	*
1282
	* @param string $content Html of item
1283
	* @param string $id =null
1284
	* @access protected
1285
	* @return void
1286
	*/
1287
	abstract function _add_topmenu_info_item($content, $id=null);
1288
1289
	static $top_menu_extra = array();
1290
1291
	/**
1292
	* Called by hooks to add an entry in the topmenu location.
1293
	* Extra entries will be added just before Logout.
1294
	*
1295
	* @param string $id unique element id
1296
	* @param string $url Address for the entry to link to
1297
	* @param string $title Text displayed for the entry
1298
	* @param string $target Optional, so the entry can open in a new page or popup
1299
	* @access public
1300
	* @return void
1301
	*/
1302
	public static function add_topmenu_item($id,$url,$title,$target = '')
1303
	{
1304
		$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...
1305
		$entry['url'] = $url;
1306
		$entry['title'] = $title;
1307
		$entry['target'] = $target;
1308
1309
		self::$top_menu_extra[$id] = $entry;
1310
	}
1311
1312
	/**
1313
	* called by hooks to add an icon in the topmenu info location
1314
	*
1315
	* @param string $id unique element id
1316
	* @param string $icon_src src of the icon image. Make sure this nog height then 18pixels
1317
	* @param string $iconlink where the icon links to
1318
	* @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...
1319
	* @param mixed $tooltip string containing the tooltip html, or null of no tooltip
1320
	* @access public
1321
	* @return void
1322
	*/
1323
	abstract function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null);
1324
1325
	/**
1326
	 * Call and return content of 'after_navbar' hook
1327
	 *
1328
	 * @return string
1329
	 */
1330
	protected function _get_after_navbar()
1331
	{
1332
		ob_start();
1333
		Hooks::process('after_navbar',null,true);
1334
		$content = ob_get_contents();
1335
		ob_end_clean();
1336
1337
		return $content;
1338
	}
1339
1340
	/**
1341
	 * Return javascript (eg. for onClick) to open manual with given url
1342
	 *
1343
	 * @param string $url
1344
	 */
1345
	abstract function open_manual_js($url);
1346
1347
	/**
1348
	 * Methods to add javascript to framework
1349
	 */
1350
1351
	/**
1352
	 * The include manager manages including js files and their dependencies
1353
	 */
1354
	protected static $js_include_mgr;
1355
1356
	/**
1357
	* Checks to make sure a valid package and file name is provided
1358
	*
1359
	* Example call syntax:
1360
	* a) Api\Framework::includeJS('jscalendar','calendar')
1361
	*    --> /phpgwapi/js/jscalendar/calendar.js
1362
	* b) Api\Framework::includeJS('/phpgwapi/inc/calendar-setup.js',array('lang'=>'de'))
1363
	*    --> /phpgwapi/inc/calendar-setup.js?lang=de
1364
	*
1365
	* @param string $package package or complete path (relative to EGW_SERVER_ROOT) to be included
1366
	* @param string|array $file =null file to be included - no ".js" on the end or array with get params
1367
	* @param string $app ='phpgwapi' application directory to search - default = phpgwapi
1368
	* @param boolean $append =true should the file be added
1369
	*/
1370
	static function includeJS($package, $file=null, $app='phpgwapi', $append=true)
1371
	{
1372
		self::$js_include_mgr->include_js_file($package, $file, $app, $append);
1373
	}
1374
1375
	/**
1376
	 * Set or return all javascript files set via validate_file, optionally clear all files
1377
	 *
1378
	 * @param array $files =null array with pathes relative to EGW_SERVER_ROOT, eg. /api/js/jquery/jquery.js
1379
	 * @param boolean $clear_files =false true clear files after returning them
1380
	 * @return array with pathes relative to EGW_SERVER_ROOT
1381
	 */
1382
	static function js_files(array $files=null, $clear_files=false)
1383
	{
1384
		if (isset($files) && is_array($files))
1385
		{
1386
			self::$js_include_mgr->include_files($files);
1387
		}
1388
		return self::$js_include_mgr->get_included_files($clear_files);
1389
	}
1390
1391
	/**
1392
	 * Used for generating the list of external js files to be included in the head of a page
1393
	 *
1394
	 * NOTE: This method should only be called by the template class.
1395
	 * The validation is done when the file is added so we don't have to worry now
1396
	 *
1397
	 * @param boolean $return_pathes =false false: return Html script tags, true: return array of file pathes relative to webserver_url
1398
	 * @param boolean $clear_files =false true clear files after returning them
1399
	 * @return string|array see $return_pathes parameter
1400
	 */
1401
	static public function get_script_links($return_pathes=false, $clear_files=false)
1402
	{
1403
		$to_include = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files($clear_files));
1404
1405
		if ($return_pathes)
1406
		{
1407
			return $to_include;
1408
		}
1409
		$start = '<script type="text/javascript" src="'. $GLOBALS['egw_info']['server']['webserver_url'];
1410
		$end = '">'."</script>\n";
1411
		return "\n".$start.implode($end.$start, $to_include).$end;
1412
	}
1413
1414
	/**
1415
	 *
1416
	 * @var boolean
1417
	 */
1418
	protected static $load_default_css = true;
1419
1420
	/**
1421
	 * Include a css file, either speicified by it's path (relative to EGW_SERVER_ROOT) or appname and css file name
1422
	 *
1423
	 * @param string $app path (relative to EGW_SERVER_ROOT) or appname (if !is_null($name))
1424
	 * @param string $name =null name of css file in $app/templates/{default|$this->template}/$name.css
1425
	 * @param boolean $append =true true append file, false prepend (add as first) file used eg. for template itself
1426
	 * @param boolean $no_default_css =false true do NOT load any default css, only what app explicitly includes
1427
	 * @return boolean false: css file not found, true: file found
1428
	 */
1429
	public static function includeCSS($app, $name=null, $append=true, $no_default_css=false)
1430
	{
1431
		if ($no_default_css)
1432
		{
1433
			self::$load_default_css = false;
1434
		}
1435
		//error_log(__METHOD__."('$app', '$name', append=$append, no_default=$no_default_css) ".function_backtrace());
1436
		return Framework\CssIncludes::add($app, $name, $append, $no_default_css);
1437
	}
1438
1439
	/**
1440
	 * Add registered CSS and javascript to ajax response
1441
	 */
1442
	public static function include_css_js_response()
1443
	{
1444
		$response = Json\Response::get();
1445
		$app = $GLOBALS['egw_info']['flags']['currentapp'];
1446
1447
		// try to add app specific css file
1448
		self::includeCSS($app, 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
1449
			self::includeCSS($app,'app');
1450
1451
		// add all css files from Framework::includeCSS()
1452
		$query = null;
1453
		//error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get()));
1454
		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...
1455
		{
1456
			unset($query);
1457
			list($path,$query) = explode('?',$path,2);
1458
			$path .= '?'. ($query ? $query : filemtime(EGW_SERVER_ROOT.$path));
1459
			$response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path);
1460
		}
1461
1462
		// try to add app specific js file
1463
		self::includeJS('.', 'app', $app);
1464
1465
		// add all js files from Framework::includeJS()
1466
		$files = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files());
1467
		foreach($files as $path)
1468
		{
1469
			$response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path);
1470
		}
1471
	}
1472
1473
	/**
1474
	 * Set a preference via ajax
1475
	 *
1476
	 * @param string $app
1477
	 * @param string $name
1478
	 * @param string $value
1479
	 */
1480
	public static function ajax_set_preference($app, $name, $value)
1481
	{
1482
		$GLOBALS['egw']->preferences->read_repository();
1483
		if ((string)$value === '')
1484
		{
1485
			$GLOBALS['egw']->preferences->delete($app, $name);
1486
		}
1487
		else
1488
		{
1489
			$GLOBALS['egw']->preferences->add($app, $name, $value);
1490
		}
1491
		$GLOBALS['egw']->preferences->save_repository(True);
1492
	}
1493
1494
	/**
1495
	 * Get Preferences of a certain application via ajax
1496
	 *
1497
	 * @param string $app
1498
	 */
1499
	public static function ajax_get_preference($app)
1500
	{
1501
		// dont block session, while we read preferences, they are not supposed to change something in the session
1502
		$GLOBALS['egw']->session->commit_session();
1503
1504
		if (preg_match('/^[a-z0-9_]+$/i', $app))
1505
		{
1506
			// send etag header, if we are directly called (not via jsonq!)
1507
			if (strpos($_GET['menuaction'], __FUNCTION__) !== false)
1508
			{
1509
				$etag = '"'.$app.'-'.md5(json_encode($GLOBALS['egw_info']['user']['preferences'][$app])).'"';
1510
				if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
1511
				{
1512
					header("HTTP/1.1 304 Not Modified");
1513
					exit();
1514
				}
1515
				header('ETag: '.$etag);
1516
			}
1517
			$response = Json\Response::get();
1518
			$response->call('egw.set_preferences', (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app);
1519
		}
1520
	}
1521
1522
	/**
1523
	 * Create or delete a favorite for multiple users
1524
	 *
1525
	 * Need to be in egw_framework to be called with .template postfix from json.php!
1526
	 *
1527
	 * @param string $app Current application, needed to save preference
1528
	 * @param string $name Name of the favorite
1529
	 * @param string $action "add" or "delete"
1530
	 * @param boolean|int|string $group ID of the group to create the favorite for, or 'all' for all users
1531
	 * @param array $filters =array() key => value pairs for the filter
1532
	 * @return boolean Success
1533
	 */
1534
	public static function ajax_set_favorite($app, $name, $action, $group, $filters = array())
1535
	{
1536
		return Framework\Favorites::set_favorite($app, $name, $action, $group, $filters);
1537
	}
1538
1539
	/**
1540
	 * Get a cachable list of users for the client
1541
	 *
1542
	 * The account source takes care of access and filtering according to preference
1543
	 */
1544
	public static function ajax_user_list()
1545
	{
1546
		$list = array('accounts' => array(),'groups' => array(), 'owngroups' => array());
1547
		if($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'primary_group')
1548
		{
1549
			$list['accounts']['filter']['group'] = $GLOBALS['egw_info']['user']['account_primary_group'];
1550
		}
1551
		$contact_obj = new Contacts();
1552
		foreach($list as $type => &$accounts)
1553
		{
1554
			$options = array('account_type' => $type) + $accounts;
1555
			$key_pair = Accounts::link_query('',$options);
1556
			$accounts = array();
1557
			foreach($key_pair as $account_id => $name)
1558
			{
1559
				$contact = $contact_obj->read('account:'.$account_id, true);
1560
				$accounts[] = array('value' => $account_id, 'label' => $name, 'icon' => self::link('/api/avatar.php', array(
1561
						'contact_id' => $contact['id'],
1562
						'etag' => $contact['etag']
1563
					)));
1564
			}
1565
		}
1566
1567
		Json\Response::get()->data($list);
1568
		return $list;
1569
	}
1570
1571
	/**
1572
	 * Get certain account-data of given account-id(s)
1573
	 *
1574
	 * @param string|array $_account_ids
1575
	 * @param string $_field ='account_email'
1576
	 * @param boolean $_resolve_groups =false true: return attribute for all members, false return attribute for group itself
1577
	 * @return array account_id => data pairs
1578
	 */
1579
	public static function ajax_account_data($_account_ids, $_field, $_resolve_groups=false)
1580
	{
1581
		$list = array();
1582
		foreach((array)$_account_ids as $account_id)
1583
		{
1584
			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...
1585
				$GLOBALS['egw']->accounts->members($account_id, true) : array($account_id) as $account_id)
1586
			{
1587
				// Make sure name is formatted according to preference
1588
				if($_field == 'account_fullname')
1589
				{
1590
					$list[$account_id] = Accounts::format_username(
1591
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_lid'),
1592
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_firstname'),
1593
						$GLOBALS['egw']->accounts->id2name($account_id, 'account_lastname'),
1594
						$account_id
1595
					);
1596
				}
1597
				else
1598
				{
1599
					$list[$account_id] = $GLOBALS['egw']->accounts->id2name($account_id, $_field);
1600
				}
1601
			}
1602
		}
1603
1604
		Json\Response::get()->data($list);
1605
		return $list;
1606
	}
1607
}
1608
// Init all static variables
1609
Framework::init_static();
1610