From 38f95f61962369f6e7f8c1d11223be910190f69d Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 27 Feb 2019 11:59:05 +0100 Subject: [PATCH] new admin-cli.php --delete-user parameter --not-existing to delete all no longer existing accounts --- admin/admin-cli.php | 5 +- .../class.admin_cmd_change_account_id.inc.php | 8 +- admin/inc/class.admin_cmd_check_acl.inc.php | 6 +- .../class.admin_cmd_delete_account.inc.php | 160 +++++++++++++++--- 4 files changed, 152 insertions(+), 27 deletions(-) diff --git a/admin/admin-cli.php b/admin/admin-cli.php index 1ac967cd69..37e7e0a5f3 100755 --- a/admin/admin-cli.php +++ b/admin/admin-cli.php @@ -152,6 +152,7 @@ function run_command(admin_cmd $cmd) break; case '--try-run': // only run checks + case '--dry-run': // only run checks $dry_run = true; break; @@ -177,7 +178,8 @@ function run_command(admin_cmd $cmd) } //_debug_array($cmd); try { - print_r($cmd->run($time, true, $skip_checks, $dry_run)); + $msg = $cmd->run($time, true, $skip_checks, $dry_run); + if (!is_bool($msg) && $msg) print_r($msg); // cli can NOT clear instance cache of APC(u), as cli uses different shared memory then webserver // --> we use a webservice call to clear cache (might fail if no domain in specified in webserver_url or on command line) @@ -327,6 +329,7 @@ function usage($action=null,$ret=0) echo " Change/set the password for a given user\n"; echo "--delete-user admin-account[@domain],admin-password,account-to-delete[,account-to-move-data]\n"; echo " Deletes a user from EGroupware. It's data can be moved to an other user or it get deleted too.\n"; + echo " You can use '--not-existing' for accounts-to-delete, to delete all no (longer) existing users and groups.\n"; echo "--edit-group admin-account[@domain],admin-password,group[=new-group-name],email[,members,...]\n"; echo " Edit or add a group to EGroupware. If you specify members, they *replace* the exiting members!\n"; echo "--delete-group admin-account[@domain],admin-password,group-to-delete\n"; diff --git a/admin/inc/class.admin_cmd_change_account_id.inc.php b/admin/inc/class.admin_cmd_change_account_id.inc.php index 22603356ad..228a9c07cb 100644 --- a/admin/inc/class.admin_cmd_change_account_id.inc.php +++ b/admin/inc/class.admin_cmd_change_account_id.inc.php @@ -5,7 +5,7 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package admin - * @copyright (c) 2007-17 by Ralf Becker + * @copyright (c) 2007-19 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -35,13 +35,13 @@ class admin_cmd_change_account_id extends admin_cmd } /** - * Query changes from all apps + * Query account columns from all apps * * Apps mark columns containing account-ids in "meta" attribute as (account|user|group)[-(abs|commasep|serialized)] * * @return array appname => array( table => array(column(s))) */ - private function get_changes() + public static function get_account_colums() { // happens if one used "root_admin" and config-password if (empty($GLOBALS['egw_info']['apps'])) @@ -135,7 +135,7 @@ class admin_cmd_change_account_id extends admin_cmd throw new Api\Exception\WrongUserinput(lang("Group #%1 must have negative sign!", $from),19); } } - $columns2change = $this->get_changes(); + $columns2change = self::get_account_colums(); $total = 0; foreach($columns2change as $app => $data) { diff --git a/admin/inc/class.admin_cmd_check_acl.inc.php b/admin/inc/class.admin_cmd_check_acl.inc.php index 63d4e5ad21..f4c5a4bcef 100644 --- a/admin/inc/class.admin_cmd_check_acl.inc.php +++ b/admin/inc/class.admin_cmd_check_acl.inc.php @@ -5,9 +5,8 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package admin - * @copyright (c) 2007-16 by Ralf Becker + * @copyright (c) 2007-19 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @version $Id$ */ /** @@ -40,7 +39,8 @@ class admin_cmd_check_acl extends admin_cmd admin_cmd::_instanciate_accounts(); $deleted = 0; - if (($all_accounts = admin_cmd::$accounts->search(array('type'=>'both')))) + // get all accounts: users+groups and also non-active ones (not yet deleted!) + if (($all_accounts = admin_cmd::$accounts->search(array('type'=>'both','active'=>false)))) { $ids = array(); foreach($all_accounts as $account) diff --git a/admin/inc/class.admin_cmd_delete_account.inc.php b/admin/inc/class.admin_cmd_delete_account.inc.php index 162e8e7cfb..4e712ee1ac 100644 --- a/admin/inc/class.admin_cmd_delete_account.inc.php +++ b/admin/inc/class.admin_cmd_delete_account.inc.php @@ -5,9 +5,8 @@ * @link http://www.egroupware.org * @author Ralf Becker * @package admin - * @copyright (c) 2007-16 by Ralf Becker + * @copyright (c) 2007-19 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @version $Id$ */ use EGroupware\Api; @@ -21,6 +20,7 @@ class admin_cmd_delete_account extends admin_cmd * Constructor * * @param string|int|array $account account name or id, or array with all parameters + * or string "--not-existing" to delete all, in account repository no longer existing, accounts * @param string $new_user =null if specified, account to transfer the data to (users only) * @param string $is_user =true type of the account: true=user, false=group */ @@ -48,6 +48,13 @@ class admin_cmd_delete_account extends admin_cmd */ protected function exec($check_only=false) { + // check creator is still admin and not explicitly forbidden to edit accounts + if ($this->creator) $this->_check_admin($this->is_user ? 'account_access' : 'group_access',32); + + if ($this->account === '--not-existing') + { + return $this->delete_not_existing($check_only); + } $account_id = admin_cmd::parse_account($this->account,$this->is_user); admin_cmd::_instanciate_accounts(); $account_lid = admin_cmd::$accounts->id2name($account_id); @@ -56,25 +63,9 @@ class admin_cmd_delete_account extends admin_cmd { $new_user = admin_cmd::parse_account($this->new_user,true); // true = user, no group } - // check creator is still admin and not explicitly forbidden to edit accounts - if ($this->creator) $this->_check_admin($this->is_user ? 'account_access' : 'group_access',32); - if ($check_only) return true; - // delete the account - $GLOBALS['hook_values'] = array( - 'account_id' => $account_id, - 'account_lid' => $account_lid, - 'account_name'=> $account_lid, // depericated name for deletegroup hook - 'new_owner' => (int)$new_user, // deleteaccount only - 'location' => $this->is_user ? 'deleteaccount' : 'deletegroup', - ); - // first all other apps, then preferences and admin - foreach(array_merge(array_diff(array_keys($GLOBALS['egw_info']['apps']),array('preferences','admin')),array('preferences','admin')) as $app) - { - Api\Hooks::single($GLOBALS['hook_values'],$app); - } - $GLOBALS['egw']->accounts->delete($account_id); + $this->delete_account($this->is_user, $account_id, $account_lid, $new_user); if ($account_id < 0) { @@ -83,6 +74,137 @@ class admin_cmd_delete_account extends admin_cmd return lang("Account '%1' deleted.",$account_lid)."\n\n"; } + /** + * Delete all in account repository no longer existing accounts + * + * @param boolean $check_only =false only run the checks (and throw the exceptions), but not the command itself + * @return string with success message + */ + protected function delete_not_existing($check_only=false) + { + admin_cmd::_instanciate_accounts(); + $repo_ids = array(); + if (($all_accounts = admin_cmd::$accounts->search(array('type'=>'both','active'=>false)))) + { + foreach($all_accounts as $account) + { + $repo_ids[] = $account['account_id']; + } + } + //print_r($repo_ids); + + static $ignore = array( + 'egw_admin_queue' => array('cmd_account'), // contains also deleted accounts / admin history + ); + $account_ids = array(); + $account_cols = admin_cmd_change_account_id::get_account_colums(); + //print_r($account_cols); + foreach($account_cols as $app => $data) + { + if (!isset($GLOBALS['egw_info']['apps'][$app])) continue; // $app is not installed + + $db = clone($GLOBALS['egw']->db); + $db->set_app($app); + if ($check_only) $db->log_updates = $db->readonly = true; + + foreach($data as $table => $columns) + { + $db->column_definitions = $db->get_table_definitions($app,$table); + $db->column_definitions = $db->column_definitions['fd']; + if (!$columns || substr($table, 0, 4) != 'egw_') + { + //echo "$app: $table no columns with account-id's\n"; + continue; // noting to do for this table + } + // never check / use accounts-table (not used for LDAP/AD, all in for SQL) + if ($table == 'egw_accounts') continue; + + if (!is_array($columns)) $columns = array($columns); + + foreach($columns as $column) + { + $type = $where = null; + if (is_array($column)) + { + $type = $column['.type']; + unset($column['.type']); + $where = $column; + $column = array_shift($where); + } + if (in_array($type, array('abs','prefs'))) // would need special handling + { + continue; + } + if (isset($ignore[$table]) && in_array($column, $ignore[$table])) + { + continue; + } + if ($table == 'egw_acl' && $column == 'acl_location') + { + $where[] = "acl_appname='phpgw_group'"; + } + $ids = array(); + foreach($rs=$db->select($table, 'DISTINCT '.$column, $where, __LINE__, __FILE__) as $row) + { + foreach(explode(',', $row[$column]) as $account_id) + { + if ($account_id && is_numeric($account_id) && !in_array($account_id, $repo_ids)) + { + $account_ids[$account_id] = $ids[] = $account_id; + } + } + } + if ($ids) echo $rs->sql.": ".implode(', ', $ids)."\n"; + } + } + } + //print_r($account_ids); + + asort($account_ids, SORT_NUMERIC); + echo count($account_ids)." not existing account_id's found in EGroupware, ".count($repo_ids)." exist in account repository\n". + "--> following should be deleted: ".implode(', ', $account_ids)."\n"; + + if ($check_only) return true; + + if ($this->new_user) + { + $new_user = admin_cmd::parse_account($this->new_user,true); // true = user, no group + } + foreach($account_ids as $account_id) + { + $this->delete_account($account_id > 0, $account_id, 'account'.$account_id, $account_id > 0 ? $new_user : null); + } + Api\Cache::flush(Api\Cache::INSTANCE); + + return lang("Total of %1 accounts deleted.", count($account_ids))."\n"; + } + + /** + * Delete account incl. calling all necessary hooks + * + * @param boolean $is_user true: user, false: group + * @param int $account_id numerical account_id of use to delete + * @param string $account_lid =null account_lid of user to delete + * @param int $new_user =null if given account_id to transfer data to + */ + protected function delete_account($is_user, $account_id, $account_lid=null, $new_user=null) + { + // delete the account + $GLOBALS['hook_values'] = array( + 'account_id' => $account_id, + 'account_lid' => $account_lid, + 'account_name'=> $account_lid, // depericated name for deletegroup hook + 'new_owner' => (int)$new_user, // deleteaccount only + 'location' => $is_user ? 'deleteaccount' : 'deletegroup', + ); + // first all other apps, then preferences and admin + foreach(array_merge(array_diff(array_keys($GLOBALS['egw_info']['apps']),array('preferences','admin')),array('preferences','admin')) as $app) + { + Api\Hooks::single($GLOBALS['hook_values'], $app, true); + } + $GLOBALS['egw']->accounts->delete($account_id); + } + /** * Return a title / string representation for a given command, eg. to display it *