Completed
Push — 16.1 ( 095a95...78bd83 )
by Hadi
82:12 queued 63:46
created

Ajax::get_sidebox()   F

Complexity

Conditions 21
Paths 260

Size

Total Lines 86
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 47
nc 260
nop 1
dl 0
loc 86
rs 3.6419
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * EGroupware - Framework for Ajax based templates: jdots & Pixelegg
4
 *
5
 * @link http://www.stylite.de
6
 * @package api
7
 * @subpackage framework
8
 * @author Andreas Stöckel <[email protected]>
9
 * @author Ralf Becker <[email protected]>
10
 * @author Nathan Gray <[email protected]>
11
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
12
 * @version $Id$
13
 */
14
15
namespace EGroupware\Api\Framework;
16
17
use EGroupware\Api;
18
use EGroupware\Api\Egw;
19
20
/**
21
* Stylite jdots template
22
*/
23
abstract class Ajax extends Api\Framework
24
{
25
	/**
26
	 * Appname used to include javascript code
27
	 */
28
	const JS_INCLUDE_APP = '';
29
	/**
30
	 * Appname used for everything else
31
	 */
32
	const APP = '';
33
34
	/**
35
	 * Minimum width of sidebar eg. from German 2-letter daynames in Calendar
36
	 * or Calendar's navigation buttons need to be seen
37
	 *
38
	 * Need to be changed in js/fw_[template].js.
39
	 */
40
	const MIN_SIDEBAR_WIDTH = 245;
41
	/**
42
	 * Default width need to be tested with English 3-letter day-names and Pixelegg template in Calendar
43
	 *
44
	 * Need to be changed in js/egw_fw.js around line 1536 too!
45
	 */
46
	const DEFAULT_SIDEBAR_WIDTH = 255;
47
	/**
48
	 * Whether javascript:egw_link_handler calls (including given app) should be returned by the "link" function
49
	 * or just the link
50
	 *
51
	 * @var string
52
	 */
53
	private static $link_app;
54
55
	/**
56
	 * Constructor
57
	 *
58
	 * @param string $template = '' name of the template
59
	 */
60
	function __construct($template=self::APP)
61
	{
62
		parent::__construct($template);		// call the constructor of the extended class
63
64
		$this->template_dir = '/'.$template;		// we are packaged as an application
65
	}
66
67
	/**
68
	 * Check if current user agent is supported
69
	 *
70
	 * Currently we do NOT support:
71
	 * - iPhone, iPad, Android, SymbianOS due to iframe scrolling problems of Webkit
72
	 * - IE < 7
73
	 *
74
	 * @return boolean
75
	 */
76
	public static function is_supported_user_agent()
77
	{
78
		if (Api\Header\UserAgent::type() == 'msie' && Api\Header\UserAgent::version() < 7)
79
		{
80
			return false;
81
		}
82
		return true;
83
	}
84
85
	/**
86
	 * Reads an returns the width of the sidebox or false if the width is not set
87
	 */
88
	private static function get_sidebar_width($app)
89
	{
90
		$width = self::DEFAULT_SIDEBAR_WIDTH;
91
92
		//Check whether the width had been stored explicitly for the jdots template, use that value
93
		if ($GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth'])
94
		{
95
			$width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth'];
96
//				error_log(__METHOD__.__LINE__."($app):$width --> reading jdotssideboxwidth");
97
		}
98
		//Otherwise use the legacy "idotssideboxwidth" value
99 View Code Duplication
		else if ($GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth'])
100
		{
101
			$width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth'];
102
//				error_log(__METHOD__.__LINE__."($app):$width --> reading idotssideboxwidth");
103
		}
104
105
		//Width may not be smaller than MIN_SIDEBAR_WIDTH
106
		if ($width < self::MIN_SIDEBAR_WIDTH)
107
			$width = self::MIN_SIDEBAR_WIDTH;
108
109
		return $width;
110
	}
111
112
	/**
113
	 * Returns the global width of the sidebox. If the app_specific_sidebar_width had been switched
114
	 * on, the default width will be returned
115
	 */
116
	private static function get_global_sidebar_width()
117
	{
118
		return self::DEFAULT_SIDEBAR_WIDTH;
119
	}
120
121
122
123
	/**
124
	 * Extract applicaton name from given url (incl. GET parameters)
125
	 *
126
	 * @param string $url
127
	 * @return string appname or NULL if it could not be detected (eg. constructing javascript urls)
128
	 */
129
	public static function app_from_url($url)
130
	{
131
		$matches = null;
132
		if (preg_match('/menuaction=([a-z0-9_-]+)\./i',$url,$matches))
133
		{
134
			return $matches[1];
135
		}
136
		if ($GLOBALS['egw_info']['server']['webserver_url'] &&
137
			($webserver_path = parse_url($GLOBALS['egw_info']['server']['webserver_url'],PHP_URL_PATH)))
138
		{
139
			list(,$url) = explode($webserver_path, parse_url($url,PHP_URL_PATH),2);
140
		}
141
		if (preg_match('/\/([^\/]+)\/([^\/]+\.php)?(\?|\/|$)/',$url,$matches))
142
		{
143
			return $matches[1];
144
		}
145
		//error_log(__METHOD__."('$url') could NOT detect application!");
146
		return null;
147
	}
148
149
	/**
150
	 * Link url generator
151
	 *
152
	 * @param string $url The url the link is for
153
	 * @param string|array	$extravars	Extra params to be passed to the url
154
	 * @param string $link_app = null if appname or true, some templates generate a special link-handler url
155
	 * @return string	The full url after processing
156
	 */
157
	static function link($url = '', $extravars = '', $link_app=null)
158
	{
159
		if (is_null($link_app)) $link_app = self::$link_app;
160
		$link = parent::link($url, $extravars);
161
162
		// $link_app === true --> detect application, otherwise use given application
163
		if ($link_app && (is_string($link_app) || ($link_app = self::app_from_url($link))))
164
		{
165
			// Link gets handled in JS, so quotes need slashes as well as url-encoded
166
			// encoded ampersands in get parameters (%26) need to be encoded twise,
167
			// so they are still encoded when assigned to window.location
168
			$link_with_slashes = str_replace(array('%27','%26'), array('\%27','%2526'), $link);
169
170
			//$link = "javascript:window.egw_link_handler?egw_link_handler('$link','$link_app'):parent.egw_link_handler('$link','$link_app');";
171
			$link = "javascript:egw_link_handler('$link_with_slashes','$link_app')";
172
		}
173
		return $link;
174
	}
175
176
	/**
177
	 * Query additional CSP frame-src from current app
178
	 *
179
	 * We have to query all apps, as we dont reload frameset!
180
	 *
181
	 * @return array
182
	 */
183
	protected function _get_csp_frame_src()
184
	{
185
		$srcs = array();
186
		foreach(Api\Hooks::process('csp-frame-src') as $src)
187
		{
188
			if ($src) $srcs = array_merge($srcs, $src);
189
		}
190
		return $srcs;
191
	}
192
193
	/**
194
	 * Returns the html-header incl. the opening body tag
195
	 *
196
	 * @param array $extra = array() extra attributes passed as data-attribute to egw.js
197
	 * @return string with Api\Html
198
	 */
199
	function header(array $extra=array())
200
	{
201
		// make sure header is output only once
202
		if (self::$header_done) return '';
203
		self::$header_done = true;
204
205
		$this->send_headers();
206
207
		// catch error echo'ed before the header, ob_start'ed in the header.inc.php
208
		$content = ob_get_contents();
209
		ob_end_clean();
210
		//error_log(__METHOD__.'('.array2string($extra).') called from:'.function_backtrace());
211
212
		// the instanciation of the template has to be here and not in the constructor,
213
		// as the old template class has problems if restored from the session (php-restore)
214
		// todo: check if this is still true
215
		$this->tpl = new Template(EGW_SERVER_ROOT.$this->template_dir);
216
		if (Api\Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
217
		{
218
			$this->tpl->set_file(array('_head' => 'head_mobile.tpl'));
219
		}
220
		else
221
		{
222
			$this->tpl->set_file(array('_head' => 'head.tpl'));
223
		}
224
		$this->tpl->set_block('_head','head');
225
		$this->tpl->set_block('_head','framework');
226
227
		// should we draw the framework, or just a header
228
		$do_framework = isset($_GET['cd']) && $_GET['cd'] === 'yes';
229
230
		// load clientside link registry to framework only
231
		if (!isset($GLOBALS['egw_info']['flags']['js_link_registry']))
232
		{
233
			$GLOBALS['egw_info']['flags']['js_link_registry'] = $do_framework;
234
		}
235
		// Loader
236
		$this->tpl->set_var('loader_text', lang('please wait...'));
237
238
		if ($do_framework)
239
		{
240
			//echo __METHOD__.__LINE__.' do framework ...'.'<br>';
241
			// framework javascript classes only need for framework
242
			if (Api\Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
243
			{
244
				self::includeJS('.', 'fw_mobile', static::JS_INCLUDE_APP);
245
			}
246
			else
247
			{
248
				self::includeJS('.', 'fw_'.static::APP, static::JS_INCLUDE_APP);
249
			}
250
			Api\Cache::unsetSession(__CLASS__,'sidebox_md5');	// sideboxes need to be send again
251
252
			$extra['navbar-apps'] = $this->get_navbar_apps($_SERVER['REQUEST_URI']);
253
		}
254
		// for an url WITHOUT cd=yes --> load framework if not yet loaded:
255
		// - if top has framework object, we are all right
256
		// - if not we need to check if we have an opener (are a popup window)
257
		// - as popups can open further popups, we need to decend all the way down until we find a framework
258
		// - only if we cant find a framework in all openers, we redirect to create a new framework
259
		if(!$do_framework)
260
		{
261
			// fetch sidebox from application and set it in extra data, if we are no popup
262
			if (!$GLOBALS['egw_info']['flags']['nonavbar'])
263
			{
264
				$this->do_sidebox();
265
			}
266
			// for remote manual never check/create framework
267
			if (!in_array($GLOBALS['egw_info']['flags']['currentapp'], array('manual', 'login', 'logout', 'sitemgr')))
268
			{
269
				if (empty($GLOBALS['egw_info']['flags']['java_script'])) $GLOBALS['egw_info']['flags']['java_script']='';
270
				$extra['check-framework'] = $_GET['cd'] !== 'no';
271
			}
272
		}
273
		$this->tpl->set_var($this->_get_header($extra));
274
		$content = $this->tpl->fp('out','head').$content;
275
276
		if (!$do_framework)
277
		{
278
			return $content;
279
		}
280
281
		// topmenu
282
		$vars = $this->_get_navbar($apps = $this->_get_navbar_apps());
283
		$this->tpl->set_var($this->topmenu($vars,$apps));
284
285
		// hook after_navbar (eg. notifications)
286
		$this->tpl->set_var('hook_after_navbar',$this->_get_after_navbar());
287
288
		//Global sidebar width
289
		$this->tpl->set_var('sidebox_width', self::get_global_sidebar_width());
290
		$this->tpl->set_var('sidebox_min_width', self::MIN_SIDEBAR_WIDTH);
291
292
		if (!(Api\Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile'))
293
		{
294
			// logout button
295
			$this->tpl->set_var('title_logout', lang("Logout"));
296
			$this->tpl->set_var('link_logout', Egw::link('/logout.php'));
297
			//Print button title
298
			$this->tpl->set_var('title_print', lang("Print current view"));
299
		}
300
301
		// add framework div's
302
		$this->tpl->set_var($this->_get_footer());
303
		$content .= $this->tpl->fp('out','framework');
304
		$content .= self::footer(false);
305
306
		echo $content;
307
		exit();
308
	}
309
310
	private $topmenu_items;
311
	private $topmenu_info_items;
312
313
	/**
314
	 * Compile entries for topmenu:
315
	 * - regular items: links
316
	 * - info items
317
	 *
318
	 * @param array $vars
319
	 * @param array $apps
320
	 * @return array
321
	 */
322
	function topmenu(array $vars,array $apps)
323
	{
324
		$this->topmenu_items = $this->topmenu_info_items = array();
325
326
		parent::topmenu($vars,$apps);
327
		$vars['topmenu_items'] = "<ul>\n<li>".implode("</li>\n<li>",$this->topmenu_items)."</li>\n</ul>";
328
		$vars['topmenu_info_items'] = '';
329
		foreach($this->topmenu_info_items as $id => $item)
330
		{
331
			$vars['topmenu_info_items'] .= '<div class="topmenu_info_item"'.
332
				(is_numeric($id) ? '' : ' id="topmenu_info_'.$id.'"').'>'.$item."</div>\n";
333
		}
334
		$this->topmenu_items = $this->topmenu_info_items = null;
335
336
		return $vars;
337
	}
338
339
	/**
340
	* called by hooks to add an icon in the topmenu info location
341
	*
342
	* @param string $id unique element id
343
	* @param string $icon_src src of the icon image. Make sure this nog height then 18pixels
344
	* @param string $iconlink where the icon links to
345
	* @param booleon $blink set true to make the icon blink
346
	* @param mixed $tooltip string containing the tooltip Api\Html, or null of no tooltip
347
	* @todo implement in a reasonable way for jdots
348
	* @return void
349
	*/
350
	function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null)
351
	{
352
		unset($id,$icon_src,$iconlink,$blink,$tooltip);	// not used
353
		// not yet implemented, only used in admin/inc/hook_topmenu_info.inc.php to notify about pending updates
354
	}
355
356
	/**
357
	* Add menu items to the topmenu template class to be displayed
358
	*
359
	* @param array $app application data
360
	* @param mixed $alt_label string with alternative menu item label default value = null
361
	* @param string $urlextra string with alternate additional code inside <a>-tag
362
	* @access protected
363
	* @return void
364
	*/
365
	function _add_topmenu_item(array $app_data,$alt_label=null)
366
	{
367
		switch($app_data['name'])
368
		{
369
			case 'logout':
370 View Code Duplication
				if (Api\Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
371
				{
372
373
				}
374
				else
375
				{
376
					return;	// no need for logout in topmenu on jdots
377
				}
378
				break;
379
380
			case 'manual':
381
				$app_data['url'] = "javascript:callManual();";
382
				break;
383
384
			default:
385
				if (strpos($app_data['url'],'logout.php') === false && substr($app_data['url'], 0, 11) != 'javascript:')
386
				{
387
					$app_data['url'] = "javascript:egw_link_handler('".$app_data['url']."','".
388
						(isset($GLOBALS['egw_info']['user']['apps'][$app_data['name']]) ?
389
							$app_data['name'] : 'about')."')";
390
				}
391
		}
392
		$id = $app_data['id'] ? $app_data['id'] : ($app_data['name'] ? $app_data['name'] : $app_data['title']);
393
		$title =  htmlspecialchars($alt_label ? $alt_label : $app_data['title']);
394
		$this->topmenu_items[] = '<a id="topmenu_' . $id . '" href="'.htmlspecialchars($app_data['url']).'" title="'.$app_data['title'].'">'.$title.'</a>';
395
	}
396
397
	/**
398
	 * Add info items to the topmenu template class to be displayed
399
	 *
400
	 * @param string $content Api\Html of item
401
	 * @param string $id = null
402
	 * @access protected
403
	 * @return void
404
	 */
405
	function _add_topmenu_info_item($content, $id=null)
406
	{
407
		if(strpos($content,'menuaction=admin.admin_accesslog.sessions') !== false)
408
		{
409
			$content = preg_replace('/href="([^"]+)"/',"href=\"javascript:egw_link_handler('\\1','admin')\"",$content);
410
		}
411
		if ($id)
412
		{
413
			$this->topmenu_info_items[$id] = $content;
414
		}
415
		else
416
		{
417
			$this->topmenu_info_items[] = $content;
418
		}
419
	}
420
421
	/**
422
	 * Change timezone
423
	 *
424
	 * @param string $tz
425
	 */
426
	static function ajax_tz_selection($tz)
427
	{
428
		Api\DateTime::setUserPrefs($tz);	// throws exception, if tz is invalid
429
430
		$GLOBALS['egw']->preferences->read_repository();
431
		$GLOBALS['egw']->preferences->add('common','tz',$tz);
432
		$GLOBALS['egw']->preferences->save_repository();
433
	}
434
435
	/**
436
	 * Flag if do_sidebox() was called
437
	 *
438
	 * @var boolean
439
	 */
440
	protected $sidebox_done = false;
441
442
	/**
443
	 * Returns the Api\Html from the body-tag til the main application area (incl. opening div tag)
444
	 *
445
	 * jDots does NOT use a navbar, but it tells us that application might want a sidebox!
446
	 *
447
	 * @return string
448
	 */
449
	function navbar()
450
	{
451
		$header = '';
452
		if (!self::$header_done)
453
		{
454
			$header = $this->header();
455
		}
456
		$GLOBALS['egw_info']['flags']['nonavbar'] = false;
457
458
		if (!$this->sidebox_done && self::$header_done)
459
		{
460
			$this->do_sidebox();
461
			return $header.'<span id="late-sidebox" data-setSidebox="'.htmlspecialchars(json_encode(self::$extra['setSidebox'])).'"/>';
462
		}
463
464
		return $header;
465
	}
466
467
	/**
468
	 * Set sidebox content in self::$data['setSidebox']
469
	 *
470
	 * We store in the session the md5 of each sidebox menu already send to client.
471
	 * If the framework get reloaded, that list gets cleared in header();
472
	 * Most apps never change sidebox, so we not even need to generate it more then once.
473
	 */
474
	function do_sidebox()
475
	{
476
		$this->sidebox_done = true;
477
478
		$app = $GLOBALS['egw_info']['flags']['currentapp'];
479
480
		// only send admin sidebox, for admin index url (when clicked on admin),
481
		// not for other admin pages, called eg. from sidebox menu of other apps
482
		// --> that way we always stay in the app, and NOT open admin sidebox for an app tab!!!
483
		if ($app == 'admin' && substr($_SERVER['PHP_SELF'],-16) != '/admin/index.php' &&
484
			$_GET['menuaction'] != 'admin.admin_ui.index')
485
		{
486
			//error_log(__METHOD__."() app=$app, menuaction=$_GET[menuaction], PHP_SELF=$_SERVER[PHP_SELF] --> sidebox request ignored");
487
			return;
488
		}
489
		$md5_session =& Api\Cache::getSession(__CLASS__,'sidebox_md5');
490
491
		//Set the sidebox content
492
		$sidebox = $this->get_sidebox($app);
493
		$md5 = md5(json_encode($sidebox));
494
495
		if ($md5_session[$app] !== $md5)
496
		{
497
			//error_log(__METHOD__."() header changed md5_session[$app]!=='$md5' --> setting it on self::\$extra[setSidebox]");
498
			$md5_session[$app] = $md5;	// update md5 in session
499
			self::$extra['setSidebox'] = array($app, $sidebox, $md5);
500
		}
501
		//else error_log(__METHOD__."() md5_session[$app]==='$md5' --> nothing to do");
502
	}
503
504
	/**
505
	 * Return true if we are rendering the top-level EGroupware window
506
	 *
507
	 * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself
508
	 *
509
	 * @return boolean $consider_navbar_not_yet_called_as_true=true ignored by jdots, we only care for cd=yes GET param
510
	 * @return boolean
511
	 */
512
	public function isTop($consider_navbar_not_yet_called_as_true=true)
513
	{
514
		unset($consider_navbar_not_yet_called_as_true);	// not used
515
		return isset($_GET['cd']) && $_GET['cd'] === 'yes';
516
	}
517
518
	/**
519
	 * Array containing sidebox menus by applications and menu-name
520
	 *
521
	 * @var array
522
	 */
523
	protected $sideboxes;
524
525
	/**
526
	 * Should calls the first call to self::sidebox create an opened menu
527
	 *
528
	 * @var boolean
529
	 */
530
	protected $sidebox_menu_opened = true;
531
532
	/**
533
	 * Callback for sideboxes hooks, collects the data in a private var
534
	 *
535
	 * @param string $appname
536
	 * @param string $menu_title
537
	 * @param array $file
538
	 * @param string $type = null 'admin', 'preferences', 'favorites', ...
539
	 */
540
	public function sidebox($appname,$menu_title,$file,$type=null)
541
	{
542
		if (!isset($file['menuOpened'])) $file['menuOpened'] = (boolean)$this->sidebox_menu_opened;
543
		//error_log(__METHOD__."('$appname', '$menu_title', file[menuOpened]=$file[menuOpened], ...) this->sidebox_menu_opened=$this->sidebox_menu_opened");
544
		$this->sidebox_menu_opened = false;
545
546
		// fix app admin menus to use admin.admin_ui.index loader
547
		if (($type == 'admin' || $menu_title == lang('Admin')) && $appname != 'admin')
548
		{
549
			$file = preg_replace("/^(javascript:egw_link_handler\(')(.*)menuaction=([^&]+)(.*)(','[^']+'\))$/",
550
				'$1$2menuaction=admin.admin_ui.index&load=$3$4&ajax=true\',\'admin\')', $file_was=$file);
551
		}
552
553
		$this->sideboxes[$appname][$menu_title] = $file;
554
	}
555
556
	/**
557
	 * Return sidebox data for an application
558
	 *
559
	 * @param $appname
560
	 * @return array of array(
561
	 * 		'menu_name' => (string),	// menu name, currently md5(title)
562
	 * 		'title'     => (string),	// translated title to display
563
	 * 		'opened'    => (boolean),	// menu opend or closed
564
	 *  	'entries'   => array(
565
	 *			array(
566
	 *				'lang_item' => translated menu item or Api\Html, i item_link === false
567
	 * 				'icon_or_star' => url of bullet images, or false for none
568
	 *  			'item_link' => url or false (lang_item contains complete html)
569
	 *  			'target' => target attribute fragment, ' target="..."'
570
	 *			),
571
	 *			// more entries
572
	 *		),
573
	 * 	),
574
	 *	array (
575
	 *		// next menu
576
	 *	)
577
	 */
578
	public function get_sidebox($appname)
579
	{
580
		if (!isset($this->sideboxes[$appname]))
581
		{
582
			self::$link_app = $appname;
583
			// allow other apps to hook into sidebox menu of an app, hook-name: sidebox_$appname
584
			$this->sidebox_menu_opened = true;
585
			Api\Hooks::process('sidebox_'.$appname,array($appname),true);	// true = call independent of app-permissions
586
587
			// calling the old hook
588
			$this->sidebox_menu_opened = true;
589
			Api\Hooks::single('sidebox_menu',$appname);
590
			self::$link_app = null;
591
592
			// allow other apps to hook into sidebox menu of every app: sidebox_all
593
			Api\Hooks::process('sidebox_all',array($GLOBALS['egw_info']['flags']['currentapp']),true);
594
		}
595
		//If there still is no sidebox content, return null here
596
		if (!isset($this->sideboxes[$appname]))
597
		{
598
			return null;
599
		}
600
601
		$data = array();
602
		$sendToBottom = array();
603
		foreach($this->sideboxes[$appname] as $menu_name => &$file)
604
		{
605
			$current_menu = array(
606
				'menu_name' => md5($menu_name),	// can contain Api\Html tags and javascript!
607
				'title' => $menu_name,
608
				'entries' => array(),
609
				'opened' => (boolean)$file['menuOpened'],
610
			);
611
			foreach($file as $item_text => $item_link)
612
			{
613
				if ($item_text === 'menuOpened' || $item_text === 'sendToBottom' ||// flag, not menu entry
614
					$item_text === '_NewLine_' || $item_link === '_NewLine_')
615
				{
616
					continue;
617
				}
618
				if (strtolower($item_text) == 'grant access' && $GLOBALS['egw_info']['server']['deny_user_grants_access'])
619
				{
620
					continue;
621
				}
622
623
				$var = array();
624
				$var['icon_or_star'] = $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images/bullet.png';
625
				$var['target'] = '';
626
				if(is_array($item_link))
627
				{
628
					if(isset($item_link['icon']))
629
					{
630
						$app = isset($item_link['app']) ? $item_link['app'] : $appname;
631
						$var['icon_or_star'] = $item_link['icon'] ? Api\Image::find($app,$item_link['icon']) : False;
632
					}
633
					$var['lang_item'] = isset($item_link['no_lang']) && $item_link['no_lang'] ? $item_link['text'] : lang($item_link['text']);
634
					$var['item_link'] = $item_link['link'];
635
					if ($item_link['target'])
636
					{
637
						// we only support real targets not Api\Html markup with target in it
638
						if (strpos($item_link['target'], 'target=') === false &&
639
							strpos($item_link['target'], '"') === false)
640
						{
641
							$var['target'] = $item_link['target'];
642
						}
643
					}
644
				}
645
				else
646
				{
647
					$var['lang_item'] = lang($item_text);
648
					$var['item_link'] = $item_link;
649
				}
650
				$current_menu['entries'][] = $var;
651
			}
652
653
			if ($file['sendToBottom'])
654
			{
655
				$sendToBottom[] = $current_menu;
656
			}
657
			else
658
			{
659
				$data[] = $current_menu;
660
			}
661
		}
662
		return array_merge($data, $sendToBottom);
663
	}
664
665
	/**
666
	 * Ajax callback which is called whenever a previously opened tab is closed or
667
	 * opened.
668
	 *
669
	 * @param $tablist is an array which contains each tab as an associative array
670
	 *   with the keys 'appName' and 'active'
671
	 */
672
	public static function ajax_tab_changed_state($tablist)
673
	{
674
		$tabs = array();
675
		foreach($tablist as $data)
676
		{
677
			$tabs[] = $data['appName'];
678
			if ($data['active']) $active = $data['appName'];
679
		}
680
		// send app a notification, that it's tab got closed
681
		// used eg. in phpFreeChat to leave the chat
682
		if (($old_tabs = Api\Cache::getSession(__CLASS__, 'open_tabs')))
683
		{
684
			foreach(array_diff(explode(',',$old_tabs),$tabs) as $app)
685
			{
686
				//error_log("Tab '$app' closed, old_tabs=$old_tabs");
687
				Api\Hooks::single(array(
688
					'location' => 'tab_closed',
689
					'app' => $app,
690
				), $app);
691
			}
692
		}
693
		$open = implode(',',$tabs);
694
695
		if ($open != $GLOBALS['egw_info']['user']['preferences']['common']['open_tabs'] ||
696
			$active != $GLOBALS['egw_info']['user']['preferences']['common']['active_tab'])
697
		{
698
			//error_log(__METHOD__.'('.array2string($tablist).") storing common prefs: open_tabs='$tabs', active_tab='$active'");
699
			Api\Cache::setSession(__CLASS__, 'open_tabs', $open);
700
			$GLOBALS['egw']->preferences->read_repository();
701
			$GLOBALS['egw']->preferences->add('common', 'open_tabs', $open);
702
			$GLOBALS['egw']->preferences->add('common', 'active_tab', $active);
703
			$GLOBALS['egw']->preferences->save_repository(true);
704
		}
705
	}
706
707
	/**
708
	 * Return sidebox data for an application
709
	 *
710
	 * Format see get_sidebox()
711
	 *
712
	 * @param $appname
713
	 */
714
	public function ajax_sidebox($appname, $md5)
715
	{
716
		// dont block session, while we read sidebox, they are not supposed to change something in the session
717
		$GLOBALS['egw']->session->commit_session();
718
719
		$response = Api\Json\Response::get();
720
		$sidebox = $this->get_sidebox($appname);
721
		$encoded = json_encode($sidebox);
722
		$new_md5 = md5($encoded);
723
724
		$response_array = array();
725
		$response_array['md5'] = $new_md5;
726
727
		if ($new_md5 != $md5)
728
		{
729
			//TODO: Add some proper solution to be able to attach the already
730
			//JSON data to the response in order to gain some performace improvements.
731
			$response_array['data'] = $sidebox;
732
		}
733
734
		$response->data($response_array);
735
	}
736
737
	/**
738
	 * Stores the width of the sidebox menu depending on the sidebox menu settings
739
	 * @param $appname the name of the application
740
	 * @param $width the width set
741
	 */
742
	public static function ajax_sideboxwidth($appname, $width)
743
	{
744
		//error_log(__METHOD__."($appname, $width)");
745
		//Check whether the supplied parameters are valid
746
		if (is_int($width) && $GLOBALS['egw_info']['user']['apps'][$appname])
747
		{
748
			self::set_sidebar_width($appname, $width);
0 ignored issues
show
Bug introduced by
The method set_sidebar_width() does not seem to exist on object<EGroupware\Api\Framework\Ajax>.

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...
749
		}
750
	}
751
752
	/**
753
	 * Stores the user defined sorting of the applications inside the preferences
754
	 *
755
	 * @param array $apps
756
	 */
757
	public static function ajax_appsort(array $apps)
758
	{
759
		$order = array();
760
		$i = 0;
761
762
		//Parse the "$apps" array for valid content (security)
763
		foreach($apps as $app)
764
		{
765
			//Check whether the app really exists and add it to the $app_arr var
766
			if ($GLOBALS['egw_info']['user']['apps'][$app])
767
			{
768
				$order[$app] = $i;
769
				$i++;
770
			}
771
		}
772
773
		//Store the order array inside the common user Api\Preferences
774
		$GLOBALS['egw']->preferences->read_repository();
775
		$GLOBALS['egw']->preferences->add('common', 'user_apporder', serialize($order));
776
		$GLOBALS['egw']->preferences->save_repository(true);
777
	}
778
779
	/**
780
	 * Prepare an array with apps used to render the navbar
781
	 *
782
	 * @return array of array(
783
	 *  'name'  => app / directory name
784
	 * 	'title' => translated application title
785
	 *  'url'   => url to call for index
786
	 *  'icon'  => icon name
787
	 *  'icon_app' => application of icon
788
	 *  'icon_hover' => hover-icon, if used by template
789
	 *  'target'=> ' target="..."' attribute fragment to open url in target, popup or ''
790
	 * )
791
	 */
792
	public function navbar_apps()
793
	{
794
		$apps = parent::_get_navbar_apps(Api\Image::svg_usable());	// use svg if usable in browser
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get_navbar_apps() instead of navbar_apps()). Are you sure this is correct? If so, you might want to change this to $this->_get_navbar_apps().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
795
796
		//Add its sidebox width to each app
797
		foreach ($apps as $app => &$data)
798
		{
799
			$data['sideboxwidth'] = self::get_sidebar_width($app);
800
			// overwrite icon with svg, if supported by browser
801
			unset($data['icon_hover']);	// not used in jdots
802
		}
803
804
		unset($apps['logout']);	// never display it
805
		if (isset($apps['about'])) $apps['about']['noNavbar'] = true;
806
		if (isset($apps['preferences'])) $apps['preferences']['noNavbar'] = true;
807
		if (isset($apps['manual'])) $apps['manual']['noNavbar'] = true;
808
		if (isset($apps['home'])) $apps['home']['noNavbar'] = true;
809
810
		// no need for website icon, if we have sitemgr
811
		if (isset($apps['sitemgr']) && isset($apps['sitemgr-link']))
812
		{
813
			unset($apps['sitemgr-link']);
814
		}
815
816
		return $apps;
817
	}
818
819
	/**
820
	 * Prepare an array with apps used to render the navbar
821
	 *
822
	 * @param string $url contains the current url on the client side. It is used to
823
	 *  determine whether the default app/home should be opened on the client
824
	 *  or whether a specific application-url has been given.
825
	 *
826
	 * @return array of array(
827
	 *  'name'  => app / directory name
828
	 * 	'title' => translated application title
829
	 *  'url'   => url to call for index
830
	 *  'icon'  => icon name
831
	 *  'icon_app' => application of icon
832
	 *  'icon_hover' => hover-icon, if used by template
833
	 *  'target'=> ' target="..."' attribute fragment to open url in target, popup or ''
834
	 *  'opened' => unset or false if the tab should not be opened, otherwise the numeric position in the tab list
835
	 *  'active' => true if this tab should be the active one when it is restored, otherwise unset or false
836
	 *  'openOnce' => unset or the url which will be opened when the tab is restored
837
	 * )
838
	 */
839
	protected function get_navbar_apps($url)
840
	{
841
		$apps = $this->navbar_apps();
842
843
		// open tab for default app, if no other tab is set
844 View Code Duplication
		if (!($default_app = $GLOBALS['egw_info']['user']['preferences']['common']['default_app']))
845
		{
846
			$default_app = 'home';
847
		}
848
		if (isset($apps[$default_app]))
849
		{
850
			$apps[$default_app]['isDefault'] = true;
851
		}
852
853
		// check if user called a specific url --> open it as active tab
854
		$last_direct_url =& Api\Cache::getSession(__CLASS__, 'last_direct_url');
855
		if ($url !== $last_direct_url)
856
		{
857
			$active_tab = $url_tab = self::app_from_url($url);
858
			$last_direct_url = $url;
859
		}
860
861
		//self::app_from_url might return an application the user has no rights
862
		//for or may return an application that simply does not exist. So check first
863
		//whether the $active_tab really exists in the $apps array.
864
		if ($active_tab && array_key_exists($active_tab, $apps))
865
		{
866
			// Do not remove cd=yes if it's an ajax=true app
867
			if (strpos( $apps[$active_tab]['url'],'ajax=true') !== False)
868
			{
869
				$url = preg_replace('/[&?]cd=yes/','',$url);
870
			}
871
			$apps[$active_tab]['openOnce'] = $url;
872
			$store_prefs = true;
873
		}
874 View Code Duplication
		else
875
		{
876
			$active_tab = $GLOBALS['egw_info']['user']['preferences']['common']['active_tab'];
877
			if (!$active_tab) $active_tab = $default_app;
878
		}
879
		// if we have the open tabs in the session, use it instead the maybe forced common prefs open_tabs
880
		if (!($open_tabs = Api\Cache::getSession(__CLASS__, 'open_tabs')))
881
		{
882
			$open_tabs = $GLOBALS['egw_info']['user']['preferences']['common']['open_tabs'];
883
		}
884
		$open_tabs = $open_tabs ? explode(',',$open_tabs) : array();
885
		if ($active_tab && !in_array($active_tab,$open_tabs))
886
		{
887
			$open_tabs[] = $active_tab;
888
			$store_prefs = true;
889
		}
890
		if ($store_prefs)
891
		{
892
			$GLOBALS['egw']->preferences->read_repository();
893
			$GLOBALS['egw']->preferences->add('common', 'open_tabs', implode(',',$open_tabs));
894
			$GLOBALS['egw']->preferences->add('common', 'active_tab', $active_tab);
895
			$GLOBALS['egw']->preferences->save_repository(true);
896
		}
897
898
		//error_log(__METHOD__."('$url') url_tab='$url_tab', active_tab=$active_tab, open_tabs=".array2string($open_tabs));
899
		// Restore Tabs
900
		foreach($open_tabs as $n => $app)
901
		{
902
			if (isset($apps[$app]))		// user might no longer have app rights
903
			{
904
				$apps[$app]['opened'] = $n;
905
				if ($app == $active_tab)
906
				{
907
					$apps[$app]['active'] = true;
908
				}
909
			}
910
		}
911
		return array_values($apps);
912
	}
913
914
	/**
915
	 * Have we output the footer
916
	 *
917
	 * @var boolean
918
	 */
919
	static private $footer_done;
920
921
	/**
922
	 * Returns the Api\Html from the closing div of the main application area to the closing html-tag
923
	 *
924
	 * @param boolean $no_framework = true
925
	 * @return string
926
	 */
927
	function footer($no_framework=true)
928
	{
929
		//error_log(__METHOD__."($no_framework) footer_done=".array2string(self::$footer_done).' '.function_backtrace());
930
		if (self::$footer_done) return;	// prevent (multiple) footers
931
		self::$footer_done = true;
932
933
		if (!isset($GLOBALS['egw_info']['flags']['nofooter']) || !$GLOBALS['egw_info']['flags']['nofooter'])
934
		{
935
			if ($no_framework && $GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time'])
936
			{
937
				$vars = $this->_get_footer();
938
				$footer = "\n".$vars['page_generation_time']."\n";
939
			}
940
		}
941
		return $footer.
942
			$GLOBALS['egw_info']['flags']['need_footer']."\n".	// eg. javascript, which need to be at the end of the page
943
			"</body>\n</html>\n";
944
	}
945
946
	/**
947
	 * Return javascript (eg. for onClick) to open manual with given url
948
	 *
949
	 * @param string $url
950
	 * @return string
951
	 */
952
	function open_manual_js($url)
953
	{
954
		return "callManual('$url')";
955
	}
956
957
	/**
958
	 * JSON reponse object
959
	 *
960
	 * If set output is requested for an ajax response --> no header, navbar or footer
961
	 *
962
	 * @var Api\Json\Response
963
	 */
964
	public $response;
965
966
	/**
967
	 * Run a link via ajax, returning content via egw_json_response->data()
968
	 *
969
	 * This behavies like /index.php, but returns the content via json.
970
	 *
971
	 * @param string $link
972
	 */
973
	public static function ajax_exec($link)
974
	{
975
		$parts = parse_url($link);
976
		$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $parts['path'];
977
		if ($parts['query'])
978
		{
979
			$_SERVER['REQUEST_URI'] = '?'.$parts['query'];
980
			parse_str($parts['query'],$_GET);
981
			$_REQUEST = $_GET;	// some apps use $_REQUEST to check $_GET or $_POST
982
		}
983
984
		if (!isset($_GET['menuaction']))
985
		{
986
			throw new Api\Exception\WrongParameter(__METHOD__."('$link') no menuaction set!");
987
		}
988
		// set session action
989
		$GLOBALS['egw']->session->set_action('Ajax: '.$_GET['menuaction']);
990
991
		list($app,$class,$method) = explode('.',$_GET['menuaction']);
992
993
		if (!isset($GLOBALS['egw_info']['user']['apps'][$app]))
994
		{
995
			throw new Api\Exception\NoPermission\App($app);
996
		}
997
		$GLOBALS['egw_info']['flags']['currentapp'] = $app;
998
999
		$GLOBALS['egw']->framework->response = Api\Json\Response::get();
1000
1001
		$GLOBALS[$class] = $obj = CreateObject($app.'.'.$class);
0 ignored issues
show
Deprecated Code introduced by
The function CreateObject() has been deprecated with message: use autoloadable class-names and new

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

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

Loading history...
1002
1003
		if(!is_array($obj->public_functions) || !$obj->public_functions[$method])
1004
		{
1005
			throw new Api\Exception\NoPermission("Bad menuaction {$_GET['menuaction']}, not listed in public_functions!");
1006
		}
1007
		// dont send header and footer
1008
		self::$header_done = self::$footer_done = true;
1009
1010
		// need to call do_sidebox, as header() with $header_done does NOT!
1011
		$GLOBALS['egw']->framework->do_sidebox();
1012
1013
		// send Api\Preferences, so we dont need to request them in a second ajax request
1014
		$GLOBALS['egw']->framework->response->call('egw.set_preferences',
1015
			(array)$GLOBALS['egw_info']['user']['preferences'][$app], $app);
1016
1017
		// call application menuaction
1018
		ob_start();
1019
		$obj->$method();
1020
		$output .= ob_get_contents();
1021
		ob_end_clean();
1022
1023
		// add registered css and javascript to the response
1024
		self::include_css_js_response();
1025
1026
		// add output if present
1027
		if ($output)
1028
		{
1029
			$GLOBALS['egw']->framework->response->data($output);
1030
		}
1031
	}
1032
1033
	/**
1034
	 * Apps available for mobile, if admin did not configured something else
1035
	 * (needs to kept in sync with list in phpgwapi/js/framework/fw_mobile.js!)
1036
	 *
1037
	 * Constant is read by admin_hooks::config to set default for fw_mobile_app_list.
1038
	 */
1039
	const DEFAULT_MOBILE_APPS = 'calendar,infolog,timesheet,resources,addressbook,projectmanager,tracker,mail,filemanager';
1040
}
1041