Completed
Push — 16.1 ( b5bda6...d590ad )
by Nathan
30:38 queued 15:41
created

admin_ui::group_actions()   F

Complexity

Conditions 15
Paths 518

Size

Total Lines 78
Code Lines 54

Duplication

Lines 36
Ratio 46.15 %

Importance

Changes 0
Metric Value
cc 15
eloc 54
nc 518
nop 0
dl 36
loc 78
rs 3.1262
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: Admin app UI
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <[email protected]>
7
 * @package admin
8
 * @copyright (c) 2013-16 by Ralf Becker <[email protected]>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
use EGroupware\Api\Egw;
16
use EGroupware\Api\Etemplate;
17
18
/**
19
 * UI for admin
20
 */
21
class admin_ui
22
{
23
	/**
24
	 * Methods callable via menuaction
25
	 * @var array
26
	 */
27
	public $public_functions = array(
28
		'index' => true,
29
	);
30
31
	/**
32
	 * Reference to global accounts object
33
	 *
34
	 * @var Api\Accounts
35
	 */
36
	private static $accounts;
37
38
	/**
39
	 * New index page
40
	 *
41
	 * @param array $content
42
	 */
43
	public function index(array $content=null)
44
	{
45
		if (admin_statistics::check(false))
46
		{
47
			$_GET['load'] = 'admin.admin_statistics.submit';
48
		}
49
		$tpl = new Etemplate('admin.index');
50
51
		if (!is_array($content)) $content = array();
52
		$content['nm'] = array(
53
			'get_rows' => 'admin_ui::get_users',
54
			'no_cat' => true,
55
			'no_filter2' => true,
56
			'filter_no_lang' => true,
57
			'lettersearch' => true,
58
			'order' => 'account_lid',
59
			'sort' => 'ASC',
60
			'row_id' => 'account_id',
61
			'default_cols' => '!account_id,created',
62
			'actions' => self::user_actions(),
63
			'placeholder_actions' => array('add')
64
		);
65
		$content['groups'] = array(
66
			'get_rows'   => 'admin_ui::get_groups',
67
			'no_cat'     => true,
68
			'no_filter'  => true,
69
			'no_filter2' => true,
70
			'row_id'     => 'account_id',
71
			'actions'    => self::group_actions(),
72
			'placeholder_actions' => array('add')
73
		);
74
75
		//$content['msg'] = 'Hi Ralf ;-)';
76
		$sel_options['tree'] = $this->tree_data();
77
		$sel_options['filter'] = array('' => lang('All groups'));
78
		foreach(self::$accounts->search(array(
79
			'type' => 'groups',
80
			'start' => false,
81
			'order' => 'account_lid',
82
			'sort' => 'ASC',
83
		)) as $data)
84
		{
85
			$sel_options['filter'][$data['account_id']] = empty($data['account_description']) ? $data['account_lid'] : array(
86
				'label' => $data['account_lid'],
87
				'title' => $data['account_description'],
88
			);
89
		}
90
		$sel_options['account_primary_group'] = $sel_options['filter'];
91
		unset($sel_options['account_primary_group']['']);
92
93
		$tpl->setElementAttribute('tree', 'actions', self::tree_actions());
94
95
		// switching between iframe and nm/accounts-list depending on load parameter
96
		// important for first time load eg. from an other application calling it's site configuration
97
		$tpl->setElementAttribute('nm', 'disabled', !empty($_GET['load']));
98
		$tpl->setElementAttribute('iframe', 'disabled', empty($_GET['load']));
99
		if (!empty($_GET['load']))
100
		{
101
			$vars = $_GET;
102
			$vars['menuaction'] = $vars['load'];
103
			unset($vars['load']);
104
			$content['iframe'] = Egw::link('/index.php', $vars);
105
		}
106
		else
107
		{
108
			$content['iframe'] = 'about:blank';	// we show accounts-list be default now
109
		}
110
111
		$tpl->exec('admin.admin_ui.index', $content, $sel_options);
112
	}
113
114
	/**
115
	 * Actions on tree / groups
116
	 *
117
	 * @return array
118
	 */
119
	public static function tree_actions()
120
	{
121
		$actions = static::group_actions();
122
123
		foreach($actions as $action_id =>  &$action)
124
		{
125
			if (!isset($action['enableId']) && !in_array($action_id, array('add')))
126
			{
127
				$action['enableId'] = '^/groups/-\\d+';
128
			}
129
		}
130
131
		return $actions;
132
	}
133
134
	/**
135
	 * Actions on users
136
	 *
137
	 * @return array
138
	 */
139
	public static function user_actions()
140
	{
141
		static $actions = null;
142
143
		if (!isset($actions))
144
		{
145
			$actions = array(
146
				'edit' => array(
147
					'caption' => 'Open',
148
					'default' => true,
149
					'allowOnMultiple' => false,
150
					'onExecute' => 'javaScript:app.admin.account',
151
					'group' => $group=0,
152
				),
153
				'add' => array(
154
					'caption' => 'Add user',
155
					'onExecute' => 'javaScript:app.admin.account',
156
					'group' => $group,
157
				),
158
			);
159
			// generate urls for add/edit accounts via addressbook
160
			$edit = Link::get_registry('addressbook', 'edit');
161
			$edit['account_id'] = '$id';
162 View Code Duplication
			foreach($edit as $name => $val)
0 ignored issues
show
Bug introduced by
The expression $edit of type boolean|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
163
			{
164
				$actions['edit']['url'] .= ($actions['edit']['url'] ? '&' : '').$name.'='.$val;
165
			}
166
			unset($edit['account_id']);
167
			$edit['owner'] = 0;
168 View Code Duplication
			foreach($edit as $name => $val)
0 ignored issues
show
Bug introduced by
The expression $edit of type boolean|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
169
			{
170
				$actions['add']['url'] .= ($actions['edit']['url'] ? '&' : '').$name.'='.$val;
171
			}
172
			++$group;
173
			// supporting both old way using $GLOBALS['menuData'] and new just returning data in hook
174
			$apps = array_unique(array_merge(array('admin'), Api\Hooks::implemented('edit_user')));
175 View Code Duplication
			foreach($apps as $app)
176
			{
177
				$GLOBALS['menuData'] = $data = array();
178
				$data = Api\Hooks::single('edit_user', $app);
179
				if (!is_array($data)) $data = $GLOBALS['menuData'];
180
				foreach($data as $item)
181
				{
182
					// allow hook to return "real" actions, but still support legacy: description, url, extradata, options
183
					if (empty($item['caption']))
184
					{
185
						$item['caption'] = $item['description'];
186
						unset($item['description']);
187
					}
188
					if (isset($item['url']) && isset($item['extradata']))
189
					{
190
						$item['url'] = $item['extradata'].'&account_id=$id';
191
						$item['id'] = substr($item['extradata'], 11);
192
						unset($item['extradata']);
193
						$matches = null;
194
						if ($item['options'] && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
195
						{
196
							$item['popup'] = $matches[2].'x'.$matches[3];
197
							if (isset($matches[5])) $item['tooltip'] = $matches[5];
198
							unset($item['options']);
199
						}
200
					}
201
					if (empty($item['icon'])) $item['icon'] = $app.'/navbar';
202
					if (empty($item['group'])) $item['group'] = $group;
203
					if (empty($item['onExecute'])) $item['onExecute'] = $item['popup'] ?
204
						'javaScript:nm_action' : 'javaScript:app.admin.iframe_location';
205
					if (!isset($item['allowOnMultiple'])) $item['allowOnMultiple'] = false;
206
207
					$actions[$item['id']] = $item;
208
				}
209
			}
210
			$actions['delete'] = array(
211
				'caption' => 'Delete',
212
				'group' => ++$group,
213
				'popup' => '400x200',
214
				'url' => 'menuaction=admin.admin_account.delete&account_id=$id',
215
				'allowOnMultiple' => false,
216
			);
217
		}
218
		//error_log(__METHOD__."() actions=".array2string($actions));
219
		return $actions;
220
	}
221
222
	/**
223
	 * Actions on groups
224
	 *
225
	 * @return array
226
	 */
227
	public static function group_actions()
228
	{
229
		$user_actions = self::user_actions();
230
		$actions = array(
231
			'view' => array(
232
				'onExecute' => 'javaScript:app.admin.group',
233
				'caption' => 'Show members',
234
				'default' => true,
235
				'group' => $group=1,
236
				'allowOnMultiple' => false
237
			),
238
			'add' => array(
239
				'group' => $group,
240
			)+$user_actions['add'],
241
			'acl' => array(
242
				'onExecute' => 'javaScript:app.admin.group',
243
				'caption' => 'Access control',
244
				'url' => 'menuaction=admin.admin_acl.index&account_id=$id',
245
				'popup' => '900x450',
246
				'icon' => 'lock',
247
				'group' => 2,
248
				'allowOnMultiple' => false
249
			),
250
		);
251
		if (!$GLOBALS['egw']->acl->check('account_access',64,'admin'))	// no rights to set ACL-rights
252
		{
253
			$actions['deny'] = array(
254
				'caption'   => 'Deny access',
255
				'url'       => 'menuaction=admin.admin_denyaccess.list_apps&account_id=$id',
256
				'onExecute' => 'javaScript:app.admin.group',
257
				'icon'      => 'cancel',
258
				'group'     => 2,
259
				'allowOnMultiple' => false
260
			);
261
		}
262
263
		$group = 5;	// allow to place actions in different groups by hook, this is the default
264
265
		// supporting both old way using $GLOBALS['menuData'] and new just returning data in hook
266
		$apps = array_unique(array_merge(array('admin'), Api\Hooks::implemented('edit_group')));
267 View Code Duplication
		foreach($apps as $app)
268
		{
269
			$GLOBALS['menuData'] = $data = array();
270
			$data = Api\Hooks::single('edit_group', $app);
271
			if (!is_array($data)) $data = $GLOBALS['menuData'];
272
273
			foreach($data as $item)
274
			{
275
				// allow hook to return "real" actions, but still support legacy: description, url, extradata, options
276
				if (empty($item['caption']))
277
				{
278
					$item['caption'] = $item['description'];
279
					unset($item['description']);
280
				}
281
				if (isset($item['url']) && isset($item['extradata']))
282
				{
283
					$item['url'] = $item['extradata'].'&account_id=$id';
284
					$item['id'] = substr($item['extradata'], 11);
285
					unset($item['extradata']);
286
					$matches = null;
287
					if ($item['options'] && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
288
					{
289
						$item['popup'] = $matches[2].'x'.$matches[3];
290
						$item['onExecute'] = 'javaScript:nm_action';
291
						if (isset($matches[5])) $item['tooltip'] = $matches[5];
292
						unset($item['options']);
293
					}
294
				}
295
				if (empty($item['icon'])) $item['icon'] = $app.'/navbar';
296
				if (empty($item['group'])) $item['group'] = $group;
297
				if (empty($item['onExecute'])) $item['onExecute'] = 'javaScript:app.admin.group';
298
				if (!isset($item['allowOnMultiple'])) $item['allowOnMultiple'] = false;
299
300
				$actions[$item['id']] = $item;
301
			}
302
		}
303
		return $actions;
304
	}
305
306
	/**
307
	 * Callback for nextmatch to fetch users
308
	 *
309
	 * @param array $query
310
	 * @param array &$rows=null
311
	 * @return int total number of rows available
312
	 */
313
	public static function get_users(array $query, array &$rows=null)
314
	{
315
		$params = array(
316
			'type' => (int)$query['filter'] ? (int)$query['filter'] : 'accounts',
317
			'start' => $query['start'],
318
			'offset' => $query['num_rows'],
319
			'order' => $query['order'],
320
			'sort' => $query['sort'],
321
			'active' => false,
322
		);
323
		if ($query['searchletter'])
324
		{
325
			$params['query'] = $query['searchletter'];
326
			$params['query_type'] = 'start';
327
		}
328
		elseif($query['search'])
329
		{
330
			$params['query'] = $query['search'];
331
			$params['query_type'] = 'all';
332
		}
333
334
		$rows = array_values(self::$accounts->search($params));
335
		//error_log(__METHOD__."() accounts->search(".array2string($params).") total=".self::$accounts->total);
336
337
		foreach($rows as &$row)
338
		{
339
			$row['status'] = self::$accounts->is_expired($row) ?
340
				lang('Expired').' '.Api\DateTime::to($row['account_expires'], true) :
341
					(!self::$accounts->is_active($row) ? lang('Disabled') :
342
						($row['account_expires'] != -1 ? lang('Expires').' '.Api\DateTime::to($row['account_expires'], true) :
343
							lang('Enabled')));
344
345
			if (!self::$accounts->is_active($row)) $row['status_class'] = 'adminAccountInactive';
346
		}
347
348
		return self::$accounts->total;
349
	}
350
351
	/**
352
	 * Callback for the nextmatch to get groups
353
	 */
354
	public static function get_groups(&$query, &$rows)
355
	{
356
		$groups = $GLOBALS['egw']->accounts->search(array(
357
				'type'  => 'groups',
358
				'order' => $query['order'],
359
				'sort'  => $query['sort'],
360
				'start' => (int)$query['start'],
361
				'num_rows' => (int)$query['num_rows']
362
			));
363
364
		$apps = array();
365
		foreach($GLOBALS['egw_info']['apps'] as $app => $data)
366
		{
367 View Code Duplication
			if (!$data['enabled'] || !$data['status'] || $data['status'] == 3)
368
			{
369
				continue;	// do NOT show disabled apps, or our API (status = 3)
370
			}
371
372
			$apps[] = $app;
373
		}
374
375
		$rows = array();
376
		foreach($groups as &$group)
377
		{
378
			$run_rights = $GLOBALS['egw']->acl->get_user_applications($group['account_id'], false, false);
379
			foreach($apps as $app)
380
			{
381
				if((boolean)$run_rights[$app])
382
				{
383
					$group['apps'][] = $app;
384
				}
385
			}
386
387
			$group['members'] = $GLOBALS['egw']->accounts->members($group['account_id'],true);
388
			$rows[] = $group;
389
		}
390
391
		return $GLOBALS['egw']->accounts->total;
392
	}
393
394
	/**
395
	 * Autoload tree from $_GET['id'] on
396
	 */
397
	public static function ajax_tree()
398
	{
399
		Etemplate\Widget\Tree::send_quote_json(self::tree_data(!empty($_GET['id']) ? $_GET['id'] : '/'));
400
	}
401
402
	/**
403
	 * Get data for navigation tree
404
	 *
405
	 * Example:
406
	 * array(
407
	 *	'id' => 0, 'item' => array(
408
	 *		array('id' => '/INBOX', 'text' => 'INBOX', 'tooltip' => 'Your inbox', 'open' => 1, 'im1' => 'kfm_home.png', 'im2' => 'kfm_home.png', 'child' => '1', 'item' => array(
409
	 *			array('id' => '/INBOX/sub', 'text' => 'sub', 'im0' => 'folderClosed.gif'),
410
	 *			array('id' => '/INBOX/sub2', 'text' => 'sub2', 'im0' => 'folderClosed.gif'),
411
	 *		)),
412
	 *		array('id' => '/user', 'text' => 'user', 'child' => '1', 'item' => array(
413
	 *			array('id' => '/user/birgit', 'text' => 'birgit', 'im0' => 'folderClosed.gif'),
414
	 *		)),
415
	 * ));
416
	 *
417
	 * @param string $root ='/'
418
	 * @return array
419
	 */
420
	public static function tree_data($root = '/')
421
	{
422
		$tree = array('id' => $root === '/' ? 0 : $root, 'item' => array(), 'child' => 1);
423
424
		if ($root == '/')
425
		{
426
			$hook_data = self::call_hook();
427
			foreach($hook_data as $app => $app_data)
428
			{
429
				if(!is_array($app_data))
430
				{
431
					// Application has no data
432
					continue;
433
				}
434
				foreach($app_data as $text => $data)
435
				{
436
					if (!is_array($data))
437
					{
438
						$data = array(
439
							'link' => $data,
440
						);
441
					}
442
					if (empty($data['text'])) $data['text'] = $text;
443
					if (empty($data['id']))
444
					{
445
						$data['id'] = $root.($app == 'admin' ? 'admin' : 'apps/'.$app).'/';
446
						$matches = null;
447
						if (preg_match_all('/(menuaction|load)=([^&]+)/', $data['link'], $matches))
448
						{
449
							$data['id'] .= $matches[2][(int)array_search('load', $matches[1])];
450
						}
451
					}
452
					if (!empty($data['icon']))
453
					{
454
						$icon = Etemplate\Widget\Tree::imagePath($data['icon']);
455
						if ($data['child'] || $data['item'])
456
						{
457
							$data['im1'] = $data['im2'] = $icon;
458
						}
459
						else
460
						{
461
							$data['im0'] = $icon;
462
						}
463
					}
464
					unset($data['icon']);
465
					$parent =& $tree['item'];
466
					$parts = explode('/', $data['id']);
467
					if ($data['id'][0] == '/') array_shift($parts);	// remove root
468
					array_pop($parts);
469
					$path = '';
470
					foreach($parts as $part)
471
					{
472
						$path .= ($path == '/' ? '' : '/').$part;
473
						if (!isset($parent[$path]))
474
						{
475
							$icon = Etemplate\Widget\Tree::imagePath($part == 'apps' ? Api\Image::find('api', 'home') :
476
								(($i=Api\Image::find($part, 'navbar')) ? $i : Api\Image::find('api', 'nonav')));
477
							$parent[$path] = array(
478
								'id' => $path,
479
								'text' => $part == 'apps' ? lang('Applications') : lang($part),
480
								//'im0' => 'folderOpen.gif',
481
								'im1' => $icon,
482
								'im2' => $icon,
483
								'item' => array(),
484
								'child' => 1,
485
							);
486
							if ($path == '/admin') $parent[$path]['open'] = true;
487
						}
488
						$parent =& $parent[$path]['item'];
489
					}
490
					$data['text'] = lang($data['text']);
491
					if (!empty($data['tooltip'])) $data['tooltip'] = lang($data['tooltip']);
492
					// make sure keys are unique, as we overwrite tree entries otherwise
493
					if (isset($parent[$data['id']])) $data['id'] .= md5($data['link']);
494
					$parent[$data['id']] = self::fix_userdata($data);
495
				}
496
			}
497
		}
498
		elseif ($root == '/groups')
499
		{
500
			foreach($GLOBALS['egw']->accounts->search(array(
501
				'type' => 'groups',
502
				'order' => 'account_lid',
503
				'sort' => 'ASC',
504
			)) as $group)
505
			{
506
				$tree['item'][] = self::fix_userdata(array(
507
					'text' => $group['account_lid'],
508
					'tooltip' => $group['account_description'],
509
					'id' => $root.'/'.$group['account_id'],
510
				));
511
			}
512
		}
513
		self::strip_item_keys($tree['item']);
514
		//_debug_array($tree); exit;
515
		return $tree;
516
	}
517
518
	/**
519
	 * Fix userdata as understood by tree
520
	 *
521
	 * @param array $data
522
	 * @return array
523
	 */
524
	private static function fix_userdata(array $data)
525
	{
526
		// store link as userdata, maybe we should store everything not directly understood by tree this way ...
527
		foreach(array_diff_key($data, array_flip(array(
528
			'id','text','tooltip','im0','im1','im2','item','child','select','open','call',
529
		))) as $name => $content)
530
		{
531
			$data['userdata'][] = array(
532
				'name' => $name,
533
				'content' => $content,
534
			);
535
			unset($data[$name]);
536
		}
537
		return $data;
538
	}
539
540
	/**
541
	 * Attribute 'item' has to be an array
542
	 *
543
	 * @param array $items
544
	 */
545
	private static function strip_item_keys(array &$items)
546
	{
547
		$items = array_values($items);
548
		foreach($items as &$item)
549
		{
550
			if (is_array($item) && isset($item['item']))
551
			{
552
				self::strip_item_keys($item['item']);
553
			}
554
		}
555
	}
556
557
	public static $hook_data = array();
558
	/**
559
	 * Return data from regular admin hook calling display_section() instead of returning it
560
	 *
561
	 * @return array appname => array of label => link/data pairs
562
	 */
563
	protected static function call_hook()
564
	{
565
		self::$hook_data = array();
566
		function display_section($appname,$file,$file2=False)
567
		{
568
			admin_ui::$hook_data[$appname] = $file2 ? $file2 : $file;
569
			//error_log(__METHOD__."(".array2string(func_get_args()).")");
570
		}
571
		self::$hook_data = array_merge(Api\Hooks::process('admin', array('admin')), self::$hook_data);
572
573
		// sort apps alphabetic by their title / Api\Translation of app-name
574
		uksort(self::$hook_data, function($a, $b)
575
		{
576
			return strcasecmp(lang($a), lang($b));
577
		});
578
		// make sure admin is first
579
		self::$hook_data = array_merge(array('admin' => self::$hook_data['admin']), self::$hook_data);
580
581
		return self::$hook_data;
582
	}
583
584
	/**
585
	 * Init static variables
586
	 */
587
	public static function init_static()
588
	{
589
		self::$accounts = $GLOBALS['egw']->accounts;
590
	}
591
}
592
admin_ui::init_static();
593