egroupware_official/admin/src/Groups.php

412 lines
11 KiB
PHP

<?php
/**
* EGroupware: Group administration
*
* @link https://www.egroupware.org
* @package admin
* @author Ralf Becker <rb@egroupware.org>
* @copyright (c) 2014-22 by Ralf Becker <rb@egroupware.org>
*/
namespace EGroupware\Admin;
use admin_cmd_account_app;
use admin_cmd_edit_group;
use EGroupware\Api;
use EGroupware\Api\Framework;
use EGroupware\Api\Egw;
use EGroupware\Api\Acl;
use EGroupware\Api\Etemplate;
/**
* Group administration:
* - hooks into admin to add and edit groups
*/
class Groups
{
/**
* Methods callable via menuaction
*
* @var array
*/
var $public_functions = array(
'edit' => true,
);
/**
* Reference to global accounts object
*
* @var Api\Accounts
*/
protected $accounts;
/**
* Reference to global acl class (instantiated for current user)
*
* @var Acl
*/
protected $acl;
/**
* Apps supporting (group) ACL
*
* @var type
*/
protected $apps_with_acl = array(
'calendar' => True,
'infolog' => True,
'filemanager' => array(
'menuaction' => 'filemanager.filemanager_ui.file',
'path' => '/home/$account_lid',
'tabs' => 'eacl',
'popup' => '495x400',
),
'bookmarks' => True,
'phpbrain' => True,
'projectmanager' => True,
'timesheet' => True
);
/**
* Constructor
*/
public function __construct()
{
$this->acl = $GLOBALS['egw']->acl;
$this->accounts = $GLOBALS['egw']->accounts;
foreach(Api\Hooks::process('group_acl','',true) as $app => $data)
{
if ($data) $this->apps_with_acl[$app] = $data;
}
// we need admin translations
Api\Translation::add_app('admin');
}
/**
* Edit / add a group
*
* @param array $content =null
*/
public function edit(array $content=null)
{
$sel_options = $readonlys = array();
$tpl = new Etemplate('admin.group.edit');
if (!is_array($content))
{
if (isset($_GET['account_id']))
{
// invalidate account, before reading it, to code with changed to DB or LDAP outside EGw
Api\Accounts::cache_invalidate((int)$_GET['account_id']);
if ($this->accounts->exists((int)$_GET['account_id']) != 2 || // 2 = group
!($content = $this->accounts->read((int)$_GET['account_id'])))
{
Framework::window_close(lang('Entry not found!'));
}
if ($GLOBALS['egw']->acl->check('group_access', 8, 'admin')) // no view
{
Framework::window_close(lang('Permission denied!'));
}
$content['account_members'] = array_keys($content['members']);
unset($content['members']);
// we might not see all (system) users, so preserve them
foreach($content['account_members'] as $key => $id)
{
if ($id < 0 || !$this->accounts->id2name($id))
{
$content['unaccessible_members'][] = $id;
unset($content['account_members'][$key]);
}
}
$content['old'] = $content;
}
else
{
if ($GLOBALS['egw']->acl->check('group_access', 4, 'admin')) // no add
{
Framework::window_close(lang('Permission denied!'));
}
$content = array();
}
//call_user_func(base64_decode('c3R5bGl0ZV9saWNlbnNlX2JvOjp2YWxpZGF0ZQ=='));
}
elseif(!empty($content['button']))
{
$button = key($content['button']);
unset($content['button']);
$msg = '';
switch($button)
{
case 'apply':
case 'save':
try {
$refresh_type = !$content['old'] ? 'add' : 'edit';
// check if some account-data changed
if (!$content['old'] || $content['old'] != array_intersect_key($content, $content['old']))
{
if (!empty($content['unaccessible_members']))
{
$content['account_members'] = array_merge($content['account_members'], $content['unaccessible_members']);
}
// Only set real changes
$content['account_id'] = $this->run_command($content, $msg);
if (!empty($content['unaccessible_members']))
{
$content['account_members'] = array_diff($content['account_members'], $content['unaccessible_members']);
}
$content['old'] = array_intersect_key($content, $content['old'] ? $content['old'] :
array_flip(array('account_id','account_lid','account_email','account_members')));
}
$apps = array();
foreach((array)$content['apps'] as $data)
{
if ($data['run']) $apps[] = $data['appname'];
}
//error_log(__METHOD__."() apps=".array2string($apps).", old=".array2string($content['old_run']).", content[apps]=".array2string($content['apps']));
// check if new apps added
if (($added = array_diff($apps, $content['old_run'])))
{
//error_log(__METHOD__."() apps added: ".array2string($added));
$allow = array(
'allow' => true,
'account' => $content['account_id'],
'apps' => $added,
// This is the documentation from policy app
)+(array)$content['admin_cmd'];
$add_cmd = new admin_cmd_account_app($allow);
$msg .= $add_cmd->run();
}
// check if apps being removed
if (($removed = array_diff($content['old_run'], $apps)))
{
//error_log(__METHOD__."() apps removed: ".array2string($removed));
$allow = array(
'allow' => false,
'account' => $content['account_id'],
'apps' => $removed,
// This is the documentation from policy app
)+(array)$content['admin_cmd'];
$rm_cmd = new admin_cmd_account_app($allow);
$msg .= $rm_cmd->run();
}
$content['old_run'] = $apps;
}
catch (Exception $ex) {
$msg .= $ex->getMessage();
unset($button); // do NOT close dialog
}
if (!$msg)
{
$msg = lang('Nothing to save.');
}
else
{
Framework::refresh_opener($msg, 'admin', $content['account_id'], $refresh_type, null, null, null,
isset($ex) ? 'error' : 'success');
}
if ($button != 'save')
{
Framework::message($msg, isset($ex) ? 'error' : 'success');
break;
}
Framework::window_close();
}
}
$run_rights = $content['account_id'] ? $this->acl->get_user_applications($content['account_id'], false, false) : array();
$content['apps'] = $content['old_run'] = array();
$content['default_quota'] = lang('(EPL Only)');
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)
}
$popup = null;
$acl_action = $this->_acl_action($app, $content['account_id'], $content['account_lid'], $popup);
$content['apps'][] = array(
'appname' => $app,
'title' => lang($app),
'action' => $acl_action,
'popup' => $popup,
'run' => (int)(boolean)$run_rights[$app],
);
if ($run_rights[$app]) $content['old_run'][] = $app;
$readonlys['apps']['button['.$app.']'] = !$acl_action;
}
usort($content['apps'], function($a, $b)
{
if ($a['run'] !== $b['run']) return $b['run']-$a['run'];
return strcasecmp($a['title'], $b['title']);
});
$readonlys['button[delete]'] = !$content['account_id'] ||
$GLOBALS['egw']->acl->check('group_access', 32, 'admin'); // no delete
if ($GLOBALS['egw']->acl->check('group_access', $content['account_id'] ? 16 : 4, 'admin')) // no edit / add
{
$readonlys['button[save]'] = $readonlys['button[apply]'] = true;
}
// disable DN for LDAP, AD or synced groups were the real DN is stored here
$content['disable_dn'] = $GLOBALS['egw_info']['server']['account_repository'] !== 'sql' ||
!empty($GLOBALS['egw_info']['server']['account_import_source']) && $GLOBALS['egw_info']['server']['account_import_type'] !== 'users';
$tpl->exec('admin.'.self::class.'.edit', $content, $sel_options, $readonlys, $content, 2);
}
/**
* Run the admin command to save the account change & log it
*
* @param Array $content Content from etemplate save
*
* @return int Command account
*/
public function run_command($content, &$msg)
{
$fields = array(
'account_email',
'account_lid',
'account_description',
'account_members',
);
// Only send real changes
$account = array();
$old = array_intersect_key((array)$content['old'], array_flip($fields));
foreach($fields as $field)
{
if($old && $content[$field] == $old[$field])
{
unset($old[$field]);
continue;
}
switch($field)
{
case 'account_members':
sort($content[$field]);
if (is_array($old[$field])) sort($old[$field]);
if($content[$field] == $old[$field])
{
unset($old[$field]);
continue 2;
}
default:
$account[$field] = $content[$field];
}
}
// No changes here
if(count($account) == 0) return $content['account_id'];
$cmd = new admin_cmd_edit_group(array(
'account' => (int)$content['account_id'],
'set' => $account,
'old' => $old,
// This is the documentation from policy app
)+(array)$content['admin_cmd']);
$msg = $cmd->run();
return $cmd->account;
}
/**
* Check entered data and return error-msg via json data or null
*
* @param array $data values for account_id and account_lid
*/
public static function ajax_check(array $data)
{
// set dummy member to get no error about no members yet
$data['account_members'] = array($GLOBALS['egw_info']['user']['account_id']);
try {
$cmd = new admin_cmd_edit_group($data['account_id'], $data);
$cmd->run(null, false, false, true);
}
catch(Exception $e)
{
Api\Json\Response::get()->data($e->getMessage());
}
}
/**
* Return actions for groups / edit_group hook
*
* @param string|array $location
*/
public static function edit_group($location)
{
unset($location); // unused, but required by hooks signature
$ret = array(
array(
'id' => 'edit',
'caption' => 'Edit group',
'icon' => 'edit',
'popup' => '600x400',
'url' => 'menuaction=admin.'.self::class.'.edit&account_id=$id',
'group' => 2,
),
array(
'id' => 'add_group',
'caption' => 'Add group',
'icon' => 'new',
'popup' => '600x400',
'url' => 'menuaction=admin.'.self::class.'.edit',
'group' => 2,
'enableId' => '',
),
'delete' => array(
'id' => 'delete',
'caption' => 'Delete',
'icon' => 'delete',
'confirm' => 'Delete this group',
'group' => 99,
)
);
// if policy app is used, use admin_account delete to delete groups
if ($GLOBALS['egw_info']['user']['apps']['policy'])
{
$ret['delete'] += array(
'policy_confirmation' => true,
'url' => 'menuaction=admin.admin_account.delete&account_id=$id'
);
}
return $ret;
}
/**
* Check if app uses group ACL
*
* @param string $app
* @param int $account_id
* @param string $account_lid
* @param string &$popup on return $width.'x'.$height or null
* @return boolean|string false or link for action
*/
private function _acl_action($app, $account_id, $account_lid, &$popup)
{
if (!($acl_action = $this->apps_with_acl[$app]) || !$account_id)
{
return false;
}
if ($acl_action === true)
{
return true;
}
$replacements = array(
'$app' => $app,
'$account_id' => $account_id,
'$account_lid' => $account_lid,
);
foreach($acl_action as &$value)
{
$value = str_replace(array_keys($replacements), array_values($replacements), $value);
}
$popup = $acl_action['popup'];
unset($acl_action['popup']);
return Egw::link('/index.php',$acl_action);
}
}