2013-05-12 15:44:46 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* EGroupware: Admin app UI
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @author Ralf Becker <rb@stylite.de>
|
|
|
|
* @package admin
|
2016-04-04 10:48:04 +02:00
|
|
|
* @copyright (c) 2013-16 by Ralf Becker <rb@stylite.de>
|
2013-05-12 15:44:46 +02:00
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @version $Id$
|
|
|
|
*/
|
|
|
|
|
2016-04-04 10:48:04 +02:00
|
|
|
use EGroupware\Api;
|
2016-04-27 21:12:20 +02:00
|
|
|
use EGroupware\Api\Egw;
|
|
|
|
use EGroupware\Api\Etemplate;
|
2019-11-14 21:52:00 +01:00
|
|
|
use EGroupware\Api\Etemplate\Widget\Tree;
|
2020-03-03 00:00:58 +01:00
|
|
|
use EGroupware\Api\Link;
|
2016-04-04 10:48:04 +02:00
|
|
|
|
2013-05-12 15:44:46 +02:00
|
|
|
/**
|
|
|
|
* UI for admin
|
|
|
|
*/
|
|
|
|
class admin_ui
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Methods callable via menuaction
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
public $public_functions = array(
|
2013-07-31 16:01:12 +02:00
|
|
|
'index' => true,
|
2013-05-12 15:44:46 +02:00
|
|
|
);
|
|
|
|
|
2013-07-03 18:27:18 +02:00
|
|
|
/**
|
|
|
|
* Reference to global accounts object
|
|
|
|
*
|
2016-04-27 21:12:20 +02:00
|
|
|
* @var Api\Accounts
|
2013-07-03 18:27:18 +02:00
|
|
|
*/
|
|
|
|
private static $accounts;
|
|
|
|
|
2013-05-12 15:44:46 +02:00
|
|
|
/**
|
|
|
|
* New index page
|
|
|
|
*
|
|
|
|
* @param array $content
|
|
|
|
*/
|
2013-11-15 20:12:36 +01:00
|
|
|
public function index(array $content=null)
|
2013-05-12 15:44:46 +02:00
|
|
|
{
|
2023-02-27 12:57:57 +01:00
|
|
|
/* disable usage statistic for now, as no more backend
|
2014-07-17 16:02:11 +02:00
|
|
|
if (admin_statistics::check(false))
|
|
|
|
{
|
|
|
|
$_GET['load'] = 'admin.admin_statistics.submit';
|
2019-03-27 18:57:20 +01:00
|
|
|
$_GET['ajax'] = 'false';
|
|
|
|
$_GET['required'] = 'true';
|
2023-02-27 12:57:57 +01:00
|
|
|
}*/
|
2016-04-27 21:12:20 +02:00
|
|
|
$tpl = new Etemplate('admin.index');
|
2013-05-12 15:44:46 +02:00
|
|
|
|
2014-10-22 19:10:12 +02:00
|
|
|
if (!is_array($content)) $content = array();
|
2013-08-01 13:44:44 +02:00
|
|
|
$content['nm'] = array(
|
2013-07-03 18:27:18 +02:00
|
|
|
'get_rows' => 'admin_ui::get_users',
|
|
|
|
'no_cat' => true,
|
|
|
|
'filter_no_lang' => true,
|
|
|
|
'lettersearch' => true,
|
|
|
|
'order' => 'account_lid',
|
|
|
|
'sort' => 'ASC',
|
|
|
|
'row_id' => 'account_id',
|
2018-06-01 14:46:52 +02:00
|
|
|
'default_cols' => '!account_id,created,account_lastlogin,account_lastloginfrom,account_lastpwd_change',
|
2013-07-03 18:27:18 +02:00
|
|
|
'actions' => self::user_actions(),
|
2016-04-13 18:22:19 +02:00
|
|
|
'placeholder_actions' => array('add')
|
2013-07-03 18:27:18 +02:00
|
|
|
);
|
2017-09-19 19:11:21 +02:00
|
|
|
$content['groups'] = array(
|
2022-11-07 22:51:42 +01:00
|
|
|
'get_rows' => 'admin_ui::get_groups',
|
|
|
|
'no_cat' => true,
|
|
|
|
'no_filter' => true,
|
|
|
|
'no_filter2' => true,
|
|
|
|
'num_rows' => 0,
|
|
|
|
'row_id' => 'account_id',
|
|
|
|
'actions' => self::group_actions(),
|
2017-09-19 19:11:21 +02:00
|
|
|
'placeholder_actions' => array('add')
|
|
|
|
);
|
|
|
|
|
2013-05-12 15:44:46 +02:00
|
|
|
$sel_options['tree'] = $this->tree_data();
|
2016-03-09 11:14:48 +01:00
|
|
|
$sel_options['filter'] = array('' => lang('All groups'));
|
2013-07-03 18:27:18 +02:00
|
|
|
foreach(self::$accounts->search(array(
|
|
|
|
'type' => 'groups',
|
|
|
|
'start' => false,
|
|
|
|
'order' => 'account_lid',
|
|
|
|
'sort' => 'ASC',
|
2013-11-15 20:12:36 +01:00
|
|
|
)) as $data)
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
|
|
|
$sel_options['filter'][$data['account_id']] = empty($data['account_description']) ? $data['account_lid'] : array(
|
|
|
|
'label' => $data['account_lid'],
|
|
|
|
'title' => $data['account_description'],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-15 21:20:21 +02:00
|
|
|
$sel_options['filter2'] = array(
|
2020-05-19 19:00:21 +02:00
|
|
|
'' => 'All',
|
2020-05-15 21:20:21 +02:00
|
|
|
'enabled' => 'Enabled',
|
|
|
|
'disabled' => 'Disabled',
|
|
|
|
'expired' => 'Expired',
|
|
|
|
'expires' => 'Expires',
|
|
|
|
'not_enabled' => 'Not enabled'
|
|
|
|
);
|
|
|
|
|
2014-03-27 20:00:52 +01:00
|
|
|
$tpl->setElementAttribute('tree', 'actions', self::tree_actions());
|
2013-07-31 16:01:12 +02:00
|
|
|
|
2014-04-03 09:23:27 +02:00
|
|
|
// switching between iframe and nm/accounts-list depending on load parameter
|
|
|
|
// important for first time load eg. from an other application calling it's site configuration
|
|
|
|
$tpl->setElementAttribute('iframe', 'disabled', empty($_GET['load']));
|
2018-07-31 17:30:57 +02:00
|
|
|
$content['iframe'] = 'about:blank'; // we show accounts-list be default now
|
2013-10-15 18:53:43 +02:00
|
|
|
if (!empty($_GET['load']))
|
|
|
|
{
|
|
|
|
$vars = $_GET;
|
|
|
|
$vars['menuaction'] = $vars['load'];
|
|
|
|
unset($vars['load']);
|
2018-07-31 17:30:57 +02:00
|
|
|
$content[$vars['ajax'] ? 'ajax_target':'iframe'] = Egw::link('/index.php', $vars);
|
2013-10-15 18:53:43 +02:00
|
|
|
}
|
|
|
|
|
2013-05-12 15:44:46 +02:00
|
|
|
$tpl->exec('admin.admin_ui.index', $content, $sel_options);
|
|
|
|
}
|
|
|
|
|
2013-07-03 18:27:18 +02:00
|
|
|
/**
|
2014-03-27 20:00:52 +01:00
|
|
|
* Actions on tree / groups
|
2013-07-03 18:27:18 +02:00
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2014-03-27 20:00:52 +01:00
|
|
|
public static function tree_actions()
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
2017-09-19 19:11:21 +02:00
|
|
|
$actions = static::group_actions();
|
2014-03-27 20:00:52 +01:00
|
|
|
|
2017-09-19 19:11:21 +02:00
|
|
|
foreach($actions as $action_id => &$action)
|
2014-07-22 15:48:57 +02:00
|
|
|
{
|
2017-09-19 19:11:21 +02:00
|
|
|
if (!isset($action['enableId']) && !in_array($action_id, array('add')))
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
2017-09-19 19:11:21 +02:00
|
|
|
$action['enableId'] = '^/groups/-\\d+';
|
2013-07-03 18:27:18 +02:00
|
|
|
}
|
|
|
|
}
|
2017-09-19 19:11:21 +02:00
|
|
|
|
2014-03-27 20:00:52 +01:00
|
|
|
return $actions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Actions on users
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function user_actions()
|
|
|
|
{
|
|
|
|
static $actions = null;
|
|
|
|
|
|
|
|
if (!isset($actions))
|
|
|
|
{
|
|
|
|
$actions = array(
|
|
|
|
'edit' => array(
|
|
|
|
'caption' => 'Open',
|
|
|
|
'default' => true,
|
|
|
|
'allowOnMultiple' => false,
|
|
|
|
'onExecute' => 'javaScript:app.admin.account',
|
|
|
|
'group' => $group=0,
|
|
|
|
),
|
|
|
|
'add' => array(
|
|
|
|
'caption' => 'Add user',
|
|
|
|
'onExecute' => 'javaScript:app.admin.account',
|
|
|
|
'group' => $group,
|
|
|
|
),
|
2020-03-03 00:00:58 +01:00
|
|
|
'copy' => array(
|
|
|
|
'caption' => 'Copy',
|
|
|
|
'url' => 'menuaction=addressbook.addressbook_ui.edit&makecp=1&contact_id=$id',
|
|
|
|
'onExecute' => 'javaScript:app.admin.account',
|
|
|
|
'allowOnMultiple' => false,
|
|
|
|
'icon' => 'copy',
|
|
|
|
),
|
2014-03-27 20:00:52 +01:00
|
|
|
);
|
|
|
|
// generate urls for add/edit accounts via addressbook
|
2016-04-27 21:12:20 +02:00
|
|
|
$edit = Link::get_registry('addressbook', 'edit');
|
2014-03-27 20:00:52 +01:00
|
|
|
$edit['account_id'] = '$id';
|
|
|
|
foreach($edit as $name => $val)
|
|
|
|
{
|
|
|
|
$actions['edit']['url'] .= ($actions['edit']['url'] ? '&' : '').$name.'='.$val;
|
|
|
|
}
|
|
|
|
unset($edit['account_id']);
|
|
|
|
$edit['owner'] = 0;
|
|
|
|
foreach($edit as $name => $val)
|
|
|
|
{
|
|
|
|
$actions['add']['url'] .= ($actions['edit']['url'] ? '&' : '').$name.'='.$val;
|
|
|
|
}
|
|
|
|
++$group;
|
|
|
|
// supporting both old way using $GLOBALS['menuData'] and new just returning data in hook
|
2016-04-27 21:12:20 +02:00
|
|
|
$apps = array_unique(array_merge(array('admin'), Api\Hooks::implemented('edit_user')));
|
2014-03-27 20:00:52 +01:00
|
|
|
foreach($apps as $app)
|
|
|
|
{
|
|
|
|
$GLOBALS['menuData'] = $data = array();
|
2016-04-27 21:12:20 +02:00
|
|
|
$data = Api\Hooks::single('edit_user', $app);
|
2014-03-27 20:00:52 +01:00
|
|
|
if (!is_array($data)) $data = $GLOBALS['menuData'];
|
|
|
|
foreach($data as $item)
|
|
|
|
{
|
|
|
|
// allow hook to return "real" actions, but still support legacy: description, url, extradata, options
|
|
|
|
if (empty($item['caption']))
|
|
|
|
{
|
|
|
|
$item['caption'] = $item['description'];
|
|
|
|
unset($item['description']);
|
|
|
|
}
|
|
|
|
if (isset($item['url']) && isset($item['extradata']))
|
|
|
|
{
|
|
|
|
$item['url'] = $item['extradata'].'&account_id=$id';
|
|
|
|
$item['id'] = substr($item['extradata'], 11);
|
|
|
|
unset($item['extradata']);
|
|
|
|
$matches = null;
|
2021-10-05 14:11:11 +02:00
|
|
|
if (!empty($item['options']) && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
2014-03-27 20:00:52 +01:00
|
|
|
{
|
|
|
|
$item['popup'] = $matches[2].'x'.$matches[3];
|
|
|
|
if (isset($matches[5])) $item['tooltip'] = $matches[5];
|
|
|
|
unset($item['options']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($item['icon'])) $item['icon'] = $app.'/navbar';
|
|
|
|
if (empty($item['group'])) $item['group'] = $group;
|
2021-10-05 14:11:11 +02:00
|
|
|
if (empty($item['onExecute'])) $item['onExecute'] = !empty($item['popup']) ?
|
2014-04-01 17:49:08 +02:00
|
|
|
'javaScript:nm_action' : 'javaScript:app.admin.iframe_location';
|
2014-03-27 20:00:52 +01:00
|
|
|
if (!isset($item['allowOnMultiple'])) $item['allowOnMultiple'] = false;
|
|
|
|
|
|
|
|
$actions[$item['id']] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$actions['delete'] = array(
|
|
|
|
'caption' => 'Delete',
|
|
|
|
'group' => ++$group,
|
2019-07-22 23:45:25 +02:00
|
|
|
'popup' => '615x600',
|
2014-03-27 20:00:52 +01:00
|
|
|
'url' => 'menuaction=admin.admin_account.delete&account_id=$id',
|
2021-02-10 00:53:51 +01:00
|
|
|
'allowOnMultiple' => true,
|
2014-03-27 20:00:52 +01:00
|
|
|
);
|
|
|
|
}
|
2013-07-03 18:27:18 +02:00
|
|
|
//error_log(__METHOD__."() actions=".array2string($actions));
|
|
|
|
return $actions;
|
|
|
|
}
|
|
|
|
|
2017-09-19 19:11:21 +02:00
|
|
|
/**
|
|
|
|
* Actions on groups
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function group_actions()
|
|
|
|
{
|
|
|
|
$user_actions = self::user_actions();
|
|
|
|
$actions = array(
|
|
|
|
'view' => array(
|
|
|
|
'onExecute' => 'javaScript:app.admin.group',
|
|
|
|
'caption' => 'Show members',
|
|
|
|
'default' => true,
|
|
|
|
'group' => $group=1,
|
|
|
|
'allowOnMultiple' => false
|
|
|
|
),
|
|
|
|
'add' => array(
|
|
|
|
'group' => $group,
|
|
|
|
)+$user_actions['add'],
|
|
|
|
'acl' => array(
|
|
|
|
'onExecute' => 'javaScript:app.admin.group',
|
|
|
|
'caption' => 'Access control',
|
|
|
|
'url' => 'menuaction=admin.admin_acl.index&account_id=$id',
|
|
|
|
'popup' => '900x450',
|
|
|
|
'icon' => 'lock',
|
|
|
|
'group' => 2,
|
|
|
|
'allowOnMultiple' => false
|
|
|
|
),
|
2023-11-02 20:08:02 +01:00
|
|
|
'run_rights' => array(
|
|
|
|
'onExecute' => 'javaScript:app.admin.group_run_rights',
|
|
|
|
'caption' => 'Run rights for applications',
|
|
|
|
'icon' => 'lock',
|
|
|
|
'group' => 2,
|
|
|
|
'allowOnMultiple' => true
|
|
|
|
)
|
2017-09-19 19:11:21 +02:00
|
|
|
);
|
|
|
|
if (!$GLOBALS['egw']->acl->check('account_access',64,'admin')) // no rights to set ACL-rights
|
|
|
|
{
|
|
|
|
$actions['deny'] = array(
|
|
|
|
'caption' => 'Deny access',
|
|
|
|
'url' => 'menuaction=admin.admin_denyaccess.list_apps&account_id=$id',
|
|
|
|
'onExecute' => 'javaScript:app.admin.group',
|
|
|
|
'icon' => 'cancel',
|
|
|
|
'group' => 2,
|
|
|
|
'allowOnMultiple' => false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$group = 5; // allow to place actions in different groups by hook, this is the default
|
|
|
|
|
2022-08-14 13:55:31 +02:00
|
|
|
$apps = Api\Hooks::implemented('edit_group');
|
|
|
|
// register hooks, if no admin one, can be removed after 22.1
|
|
|
|
if (!isset($apps['admin']))
|
|
|
|
{
|
|
|
|
Api\Hooks::read(true);
|
|
|
|
$apps = Api\Hooks::implemented('edit_group');
|
|
|
|
}
|
|
|
|
// skip EPL and groups app, in case their group-admin is still installed
|
|
|
|
$apps = array_unique(array_diff($apps, ['groups', 'stylite']));
|
2017-09-19 19:11:21 +02:00
|
|
|
foreach($apps as $app)
|
|
|
|
{
|
2022-08-14 13:55:31 +02:00
|
|
|
// supporting both old way using $GLOBALS['menuData'] and new just returning data in hook
|
|
|
|
$GLOBALS['menuData'] = [];
|
2017-09-19 19:11:21 +02:00
|
|
|
$data = Api\Hooks::single('edit_group', $app);
|
|
|
|
if (!is_array($data)) $data = $GLOBALS['menuData'];
|
|
|
|
|
|
|
|
foreach($data as $item)
|
|
|
|
{
|
|
|
|
// allow hook to return "real" actions, but still support legacy: description, url, extradata, options
|
|
|
|
if (empty($item['caption']))
|
|
|
|
{
|
|
|
|
$item['caption'] = $item['description'];
|
|
|
|
unset($item['description']);
|
|
|
|
}
|
|
|
|
if (isset($item['url']) && isset($item['extradata']))
|
|
|
|
{
|
|
|
|
$item['url'] = $item['extradata'].'&account_id=$id';
|
|
|
|
$item['id'] = substr($item['extradata'], 11);
|
|
|
|
unset($item['extradata']);
|
|
|
|
$matches = null;
|
2021-10-05 14:11:11 +02:00
|
|
|
if (!empty($item['options']) && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
2017-09-19 19:11:21 +02:00
|
|
|
{
|
|
|
|
$item['popup'] = $matches[2].'x'.$matches[3];
|
|
|
|
$item['onExecute'] = 'javaScript:nm_action';
|
|
|
|
if (isset($matches[5])) $item['tooltip'] = $matches[5];
|
|
|
|
unset($item['options']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($item['icon'])) $item['icon'] = $app.'/navbar';
|
|
|
|
if (empty($item['group'])) $item['group'] = $group;
|
|
|
|
if (empty($item['onExecute'])) $item['onExecute'] = 'javaScript:app.admin.group';
|
|
|
|
if (!isset($item['allowOnMultiple'])) $item['allowOnMultiple'] = false;
|
|
|
|
|
|
|
|
$actions[$item['id']] = $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $actions;
|
|
|
|
}
|
|
|
|
|
2013-07-03 18:27:18 +02:00
|
|
|
/**
|
|
|
|
* Callback for nextmatch to fetch users
|
|
|
|
*
|
|
|
|
* @param array $query
|
|
|
|
* @param array &$rows=null
|
|
|
|
* @return int total number of rows available
|
|
|
|
*/
|
|
|
|
public static function get_users(array $query, array &$rows=null)
|
|
|
|
{
|
|
|
|
$params = array(
|
2021-10-05 14:11:11 +02:00
|
|
|
'type' => (int)($query['filter'] ?? 0) ?: 'accounts',
|
2013-07-03 18:27:18 +02:00
|
|
|
'start' => $query['start'],
|
|
|
|
'offset' => $query['num_rows'],
|
|
|
|
'order' => $query['order'],
|
|
|
|
'sort' => $query['sort'],
|
2019-05-03 11:42:49 +02:00
|
|
|
'active' => !empty($query['active']) ? $query['active'] : false,
|
2013-07-03 18:27:18 +02:00
|
|
|
);
|
2020-05-15 21:20:21 +02:00
|
|
|
// Make sure active filter give status what it needs
|
2021-10-05 14:11:11 +02:00
|
|
|
switch($query['filter2'] ?? '')
|
2020-05-15 21:20:21 +02:00
|
|
|
{
|
|
|
|
case 'disabled':
|
|
|
|
case 'expired':
|
|
|
|
case 'not_enabled':
|
2020-09-10 11:11:54 +02:00
|
|
|
case 'expires':
|
|
|
|
// this filters are not implemented by backend --> need to do unlimited query, then apply status filter and finally limit the query
|
|
|
|
$need_status_filter = $query['filter2'];
|
|
|
|
$params['start'] = false;
|
|
|
|
unset($params['offset']);
|
|
|
|
$params['active'] = $query['filter2'] === 'expires';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '': // all
|
2020-05-15 21:20:21 +02:00
|
|
|
$params['active'] = false;
|
|
|
|
break;
|
2020-09-10 11:11:54 +02:00
|
|
|
|
2020-05-15 21:20:21 +02:00
|
|
|
case 'enabled':
|
|
|
|
$params['active'] = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-10-05 14:11:11 +02:00
|
|
|
if (!empty($query['searchletter']))
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
|
|
|
$params['query'] = $query['searchletter'];
|
|
|
|
$params['query_type'] = 'start';
|
|
|
|
}
|
2021-10-05 14:11:11 +02:00
|
|
|
elseif(!empty($query['search']))
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
|
|
|
$params['query'] = $query['search'];
|
|
|
|
$params['query_type'] = 'all';
|
|
|
|
}
|
2023-02-28 15:15:03 +01:00
|
|
|
if (!empty($query['account_id']))
|
|
|
|
{
|
|
|
|
$params['account_id'] = (array)$query['account_id'];
|
|
|
|
}
|
2013-07-03 18:27:18 +02:00
|
|
|
|
2014-04-10 13:29:20 +02:00
|
|
|
$rows = array_values(self::$accounts->search($params));
|
2013-07-31 16:01:12 +02:00
|
|
|
//error_log(__METHOD__."() accounts->search(".array2string($params).") total=".self::$accounts->total);
|
2020-09-10 11:11:54 +02:00
|
|
|
$total = self::$accounts->total;
|
2013-07-03 18:27:18 +02:00
|
|
|
|
2021-05-16 20:53:38 +02:00
|
|
|
// release session (after query got cached!) to allow parallel requests to run
|
|
|
|
$GLOBALS['egw']->session->commit_session();
|
|
|
|
|
2020-05-15 21:20:21 +02:00
|
|
|
foreach($rows as $key => &$row)
|
2013-07-03 18:27:18 +02:00
|
|
|
{
|
2020-05-15 21:20:21 +02:00
|
|
|
// Filter by status
|
2021-10-05 14:11:11 +02:00
|
|
|
if (!empty($need_status_filter) && !static::filter_status($need_status_filter, $row))
|
2020-05-15 21:20:21 +02:00
|
|
|
{
|
|
|
|
unset($rows[$key]);
|
2020-09-10 11:11:54 +02:00
|
|
|
$total--;
|
2020-05-15 21:20:21 +02:00
|
|
|
continue;
|
|
|
|
}
|
2013-07-03 18:27:18 +02:00
|
|
|
$row['status'] = self::$accounts->is_expired($row) ?
|
2016-04-04 10:48:04 +02:00
|
|
|
lang('Expired').' '.Api\DateTime::to($row['account_expires'], true) :
|
2013-07-03 18:27:18 +02:00
|
|
|
(!self::$accounts->is_active($row) ? lang('Disabled') :
|
2016-04-04 10:48:04 +02:00
|
|
|
($row['account_expires'] != -1 ? lang('Expires').' '.Api\DateTime::to($row['account_expires'], true) :
|
2013-07-03 18:27:18 +02:00
|
|
|
lang('Enabled')));
|
|
|
|
|
|
|
|
if (!self::$accounts->is_active($row)) $row['status_class'] = 'adminAccountInactive';
|
|
|
|
}
|
2021-10-05 14:11:11 +02:00
|
|
|
// finally, limit query, if status filter was used
|
|
|
|
if (!empty($need_status_filter))
|
2020-09-10 11:11:54 +02:00
|
|
|
{
|
|
|
|
$rows = array_values(array_slice($rows, (int)$query['start'], $query['num_rows'] ?: count($rows)));
|
|
|
|
}
|
|
|
|
return $total;
|
2013-07-03 18:27:18 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 21:20:21 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Filter the account based on given status.
|
|
|
|
*
|
|
|
|
* Status is one of enabled, disabled, expired, expires, not_enabled
|
|
|
|
* @param $status
|
|
|
|
* @param $account
|
|
|
|
*/
|
|
|
|
protected static function filter_status($status, &$account)
|
|
|
|
{
|
|
|
|
switch($status)
|
|
|
|
{
|
|
|
|
case 'enabled':
|
|
|
|
return $account['account_status'] == 'A';
|
|
|
|
|
|
|
|
case 'disabled':
|
2020-05-19 19:00:21 +02:00
|
|
|
return $account['account_status'] !== 'A' && $account['account_expires'] == '-1';
|
2020-05-15 21:20:21 +02:00
|
|
|
|
|
|
|
case 'expired':
|
2020-05-19 19:00:21 +02:00
|
|
|
return $account['account_expires'] !== '-1' && $account['account_expires'] <= time();
|
2020-05-15 21:20:21 +02:00
|
|
|
|
|
|
|
case 'expires':
|
|
|
|
return $account['account_expires'] != '-1' && $account['account_status'] == 'A';
|
|
|
|
|
|
|
|
case 'not_enabled':
|
|
|
|
return static::filter_status('disabled', $account) || static::filter_status('expired', $account);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-19 19:11:21 +02:00
|
|
|
/**
|
|
|
|
* Callback for the nextmatch to get groups
|
2022-11-05 09:12:24 +01:00
|
|
|
*
|
|
|
|
* Does NOT set members for huge installations, but return "is_huge" === true in $rows.
|
|
|
|
*
|
|
|
|
* @param array &$query
|
|
|
|
* @param array|null &$rows on return rows plus boolean value for key "is_huge" === Accounts::isHuge()
|
|
|
|
* @return int total number of rows
|
2017-09-19 19:11:21 +02:00
|
|
|
*/
|
2022-11-05 09:12:24 +01:00
|
|
|
public static function get_groups(array &$query, array &$rows=null)
|
2017-09-19 19:11:21 +02:00
|
|
|
{
|
|
|
|
$groups = $GLOBALS['egw']->accounts->search(array(
|
|
|
|
'type' => 'groups',
|
2021-10-05 14:11:11 +02:00
|
|
|
'query' => $query['search'] ?? null,
|
|
|
|
'order' => $query['order'] ?? null,
|
|
|
|
'sort' => $query['sort'] ?? null,
|
2017-09-19 19:11:21 +02:00
|
|
|
'start' => (int)$query['start'],
|
2017-11-16 19:27:45 +01:00
|
|
|
'offset' => (int)$query['num_rows']
|
2017-09-19 19:11:21 +02:00
|
|
|
));
|
|
|
|
|
2021-05-16 20:53:38 +02:00
|
|
|
// release session (after query got cached!) to allow parallel requests to run
|
|
|
|
$GLOBALS['egw']->session->commit_session();
|
|
|
|
|
2017-09-19 19:11:21 +02:00
|
|
|
$apps = array();
|
|
|
|
foreach($GLOBALS['egw_info']['apps'] as $app => $data)
|
|
|
|
{
|
|
|
|
if (!$data['enabled'] || !$data['status'] || $data['status'] == 3)
|
|
|
|
{
|
|
|
|
continue; // do NOT show disabled apps, or our API (status = 3)
|
|
|
|
}
|
|
|
|
|
|
|
|
$apps[] = $app;
|
|
|
|
}
|
|
|
|
|
2022-11-05 09:12:24 +01:00
|
|
|
$rows = [];
|
|
|
|
$is_huge = $GLOBALS['egw']->accounts->isHuge();
|
2017-09-19 19:11:21 +02:00
|
|
|
foreach($groups as &$group)
|
|
|
|
{
|
|
|
|
$run_rights = $GLOBALS['egw']->acl->get_user_applications($group['account_id'], false, false);
|
|
|
|
foreach($apps as $app)
|
|
|
|
{
|
2021-10-05 14:11:11 +02:00
|
|
|
if(!empty($run_rights[$app]))
|
2017-09-19 19:11:21 +02:00
|
|
|
{
|
|
|
|
$group['apps'][] = $app;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-05 09:12:24 +01:00
|
|
|
// do NOT set members for huge installations, but return "is_huge" === true in $rows
|
|
|
|
if (!$is_huge)
|
|
|
|
{
|
|
|
|
$group['members'] = $GLOBALS['egw']->accounts->members($group['account_id'],true);
|
|
|
|
}
|
2017-09-19 19:11:21 +02:00
|
|
|
$rows[] = $group;
|
|
|
|
}
|
2022-11-05 09:12:24 +01:00
|
|
|
$rows['is_huge'] = $is_huge;
|
2017-09-19 19:11:21 +02:00
|
|
|
return $GLOBALS['egw']->accounts->total;
|
|
|
|
}
|
|
|
|
|
2013-05-12 15:44:46 +02:00
|
|
|
/**
|
|
|
|
* Autoload tree from $_GET['id'] on
|
|
|
|
*/
|
|
|
|
public static function ajax_tree()
|
|
|
|
{
|
2016-04-27 21:12:20 +02:00
|
|
|
Etemplate\Widget\Tree::send_quote_json(self::tree_data(!empty($_GET['id']) ? $_GET['id'] : '/'));
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get data for navigation tree
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* array(
|
|
|
|
* 'id' => 0, 'item' => array(
|
|
|
|
* array('id' => '/INBOX', 'text' => 'INBOX', 'tooltip' => 'Your inbox', 'open' => 1, 'im1' => 'kfm_home.png', 'im2' => 'kfm_home.png', 'child' => '1', 'item' => array(
|
|
|
|
* array('id' => '/INBOX/sub', 'text' => 'sub', 'im0' => 'folderClosed.gif'),
|
|
|
|
* array('id' => '/INBOX/sub2', 'text' => 'sub2', 'im0' => 'folderClosed.gif'),
|
|
|
|
* )),
|
|
|
|
* array('id' => '/user', 'text' => 'user', 'child' => '1', 'item' => array(
|
|
|
|
* array('id' => '/user/birgit', 'text' => 'birgit', 'im0' => 'folderClosed.gif'),
|
|
|
|
* )),
|
|
|
|
* ));
|
|
|
|
*
|
2014-10-22 19:10:12 +02:00
|
|
|
* @param string $root ='/'
|
2013-05-12 15:44:46 +02:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function tree_data($root = '/')
|
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
$tree = array(Tree::ID => $root === '/' ? 0 : $root, Tree::CHILDREN => array(), 'child' => 1);
|
2013-05-12 15:44:46 +02:00
|
|
|
|
|
|
|
if ($root == '/')
|
|
|
|
{
|
|
|
|
$hook_data = self::call_hook();
|
|
|
|
foreach($hook_data as $app => $app_data)
|
|
|
|
{
|
2015-04-13 21:27:28 +02:00
|
|
|
if(!is_array($app_data))
|
|
|
|
{
|
|
|
|
// Application has no data
|
|
|
|
continue;
|
|
|
|
}
|
2013-05-12 15:44:46 +02:00
|
|
|
foreach($app_data as $text => $data)
|
|
|
|
{
|
|
|
|
if (!is_array($data))
|
|
|
|
{
|
|
|
|
$data = array(
|
|
|
|
'link' => $data,
|
|
|
|
);
|
|
|
|
}
|
2019-11-14 21:52:00 +01:00
|
|
|
if (empty($data[Tree::LABEL])) $data[Tree::LABEL] = $text;
|
|
|
|
if (empty($data[Tree::ID]))
|
2013-05-12 15:44:46 +02:00
|
|
|
{
|
|
|
|
$data['id'] = $root.($app == 'admin' ? 'admin' : 'apps/'.$app).'/';
|
2013-11-15 20:12:36 +01:00
|
|
|
$matches = null;
|
2013-10-15 18:53:43 +02:00
|
|
|
if (preg_match_all('/(menuaction|load)=([^&]+)/', $data['link'], $matches))
|
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
$data[Tree::ID] .= $matches[2][(int)array_search('load', $matches[1])];
|
2013-10-15 18:53:43 +02:00
|
|
|
}
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
|
|
|
if (!empty($data['icon']))
|
|
|
|
{
|
2016-04-27 21:12:20 +02:00
|
|
|
$icon = Etemplate\Widget\Tree::imagePath($data['icon']);
|
2021-10-05 14:11:11 +02:00
|
|
|
if (!empty($data['child']) || !empty($data[Tree::CHILDREN]))
|
2013-05-12 15:44:46 +02:00
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
$data[Tree::IMAGE_FOLDER_OPEN] = $data[Tree::IMAGE_FOLDER_CLOSED] = $icon;
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
$data[Tree::IMAGE_LEAF] = $icon;
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
|
|
|
}
|
2013-07-01 19:07:07 +02:00
|
|
|
unset($data['icon']);
|
2019-11-14 21:52:00 +01:00
|
|
|
$parent =& $tree[Tree::CHILDREN];
|
|
|
|
$parts = explode('/', $data[Tree::ID]);
|
|
|
|
if ($data[Tree::ID][0] == '/') array_shift($parts); // remove root
|
2013-11-15 20:12:36 +01:00
|
|
|
array_pop($parts);
|
2013-05-12 15:44:46 +02:00
|
|
|
$path = '';
|
|
|
|
foreach($parts as $part)
|
|
|
|
{
|
|
|
|
$path .= ($path == '/' ? '' : '/').$part;
|
|
|
|
if (!isset($parent[$path]))
|
|
|
|
{
|
2016-05-06 09:59:43 +02:00
|
|
|
$icon = Etemplate\Widget\Tree::imagePath($part == 'apps' ? Api\Image::find('api', 'home') :
|
|
|
|
(($i=Api\Image::find($part, 'navbar')) ? $i : Api\Image::find('api', 'nonav')));
|
2013-05-12 15:44:46 +02:00
|
|
|
$parent[$path] = array(
|
2019-11-14 21:52:00 +01:00
|
|
|
Tree::ID => $path,
|
|
|
|
Tree::LABEL => $part == 'apps' ? lang('Applications') : lang($part),
|
2013-05-12 15:44:46 +02:00
|
|
|
//'im0' => 'folderOpen.gif',
|
2019-11-14 21:52:00 +01:00
|
|
|
Tree::IMAGE_FOLDER_OPEN => $icon,
|
|
|
|
Tree::IMAGE_FOLDER_CLOSED => $icon,
|
|
|
|
Tree::CHILDREN => array(),
|
2013-05-12 15:44:46 +02:00
|
|
|
'child' => 1,
|
|
|
|
);
|
2013-07-01 19:07:07 +02:00
|
|
|
if ($path == '/admin') $parent[$path]['open'] = true;
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2019-11-14 21:52:00 +01:00
|
|
|
$parent =& $parent[$path][Tree::CHILDREN];
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2019-11-14 21:52:00 +01:00
|
|
|
$data[Tree::LABEL] = lang($data[Tree::LABEL]);
|
2013-07-01 19:07:07 +02:00
|
|
|
if (!empty($data['tooltip'])) $data['tooltip'] = lang($data['tooltip']);
|
2015-07-03 14:27:15 +02:00
|
|
|
// make sure keys are unique, as we overwrite tree entries otherwise
|
2019-11-14 21:52:00 +01:00
|
|
|
if (isset($parent[$data[Tree::ID]])) $data[Tree::ID] .= md5($data['link']);
|
|
|
|
$parent[$data[Tree::ID]] = self::fix_userdata($data);
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elseif ($root == '/groups')
|
|
|
|
{
|
2013-07-02 20:26:53 +02:00
|
|
|
foreach($GLOBALS['egw']->accounts->search(array(
|
|
|
|
'type' => 'groups',
|
2013-07-03 18:27:18 +02:00
|
|
|
'order' => 'account_lid',
|
|
|
|
'sort' => 'ASC',
|
2013-07-02 20:26:53 +02:00
|
|
|
)) as $group)
|
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
$tree[Tree::CHILDREN][] = self::fix_userdata(array(
|
|
|
|
Tree::LABEL => $group['account_lid'],
|
|
|
|
Tree::TOOLTIP => $group['account_description'],
|
|
|
|
Tree::ID => $root.'/'.$group['account_id'],
|
2013-07-02 20:26:53 +02:00
|
|
|
));
|
|
|
|
}
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2019-11-14 21:52:00 +01:00
|
|
|
self::strip_item_keys($tree[Tree::CHILDREN]);
|
2013-05-12 15:44:46 +02:00
|
|
|
//_debug_array($tree); exit;
|
|
|
|
return $tree;
|
|
|
|
}
|
|
|
|
|
2013-07-01 19:07:07 +02:00
|
|
|
/**
|
|
|
|
* Fix userdata as understood by tree
|
|
|
|
*
|
|
|
|
* @param array $data
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private static function fix_userdata(array $data)
|
|
|
|
{
|
2019-11-14 21:52:00 +01:00
|
|
|
if(!$data[Tree::LABEL])
|
|
|
|
{
|
|
|
|
$data[Tree::LABEL] = $data['text'];
|
|
|
|
}
|
2013-07-01 19:07:07 +02:00
|
|
|
// store link as userdata, maybe we should store everything not directly understood by tree this way ...
|
|
|
|
foreach(array_diff_key($data, array_flip(array(
|
2019-11-14 21:52:00 +01:00
|
|
|
Tree::ID,Tree::LABEL,Tree::TOOLTIP,'im0','im1','im2','item','child','select','open','call',
|
2013-07-01 19:07:07 +02:00
|
|
|
))) as $name => $content)
|
|
|
|
{
|
|
|
|
$data['userdata'][] = array(
|
|
|
|
'name' => $name,
|
|
|
|
'content' => $content,
|
|
|
|
);
|
|
|
|
unset($data[$name]);
|
|
|
|
}
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2013-07-03 18:27:18 +02:00
|
|
|
/**
|
|
|
|
* Attribute 'item' has to be an array
|
|
|
|
*
|
|
|
|
* @param array $items
|
|
|
|
*/
|
|
|
|
private static function strip_item_keys(array &$items)
|
2013-05-12 15:44:46 +02:00
|
|
|
{
|
|
|
|
$items = array_values($items);
|
|
|
|
foreach($items as &$item)
|
|
|
|
{
|
|
|
|
if (is_array($item) && isset($item['item']))
|
|
|
|
{
|
|
|
|
self::strip_item_keys($item['item']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static $hook_data = array();
|
|
|
|
/**
|
|
|
|
* Return data from regular admin hook calling display_section() instead of returning it
|
|
|
|
*
|
|
|
|
* @return array appname => array of label => link/data pairs
|
|
|
|
*/
|
|
|
|
protected static function call_hook()
|
|
|
|
{
|
|
|
|
self::$hook_data = array();
|
|
|
|
function display_section($appname,$file,$file2=False)
|
|
|
|
{
|
|
|
|
admin_ui::$hook_data[$appname] = $file2 ? $file2 : $file;
|
2013-07-01 19:07:07 +02:00
|
|
|
//error_log(__METHOD__."(".array2string(func_get_args()).")");
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2016-04-27 21:12:20 +02:00
|
|
|
self::$hook_data = array_merge(Api\Hooks::process('admin', array('admin')), self::$hook_data);
|
2014-11-28 13:43:10 +01:00
|
|
|
|
2016-04-27 21:12:20 +02:00
|
|
|
// sort apps alphabetic by their title / Api\Translation of app-name
|
2014-11-28 13:43:10 +01:00
|
|
|
uksort(self::$hook_data, function($a, $b)
|
|
|
|
{
|
|
|
|
return strcasecmp(lang($a), lang($b));
|
|
|
|
});
|
2014-12-19 08:49:04 +01:00
|
|
|
// make sure admin is first
|
|
|
|
self::$hook_data = array_merge(array('admin' => self::$hook_data['admin']), self::$hook_data);
|
2014-11-28 13:43:10 +01:00
|
|
|
|
|
|
|
return self::$hook_data;
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2013-07-03 18:27:18 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Init static variables
|
|
|
|
*/
|
|
|
|
public static function init_static()
|
|
|
|
{
|
|
|
|
self::$accounts = $GLOBALS['egw']->accounts;
|
|
|
|
}
|
2013-05-12 15:44:46 +02:00
|
|
|
}
|
2022-08-14 13:55:31 +02:00
|
|
|
admin_ui::init_static();
|