Merge branch 'master' into feature/server-side-diff

This commit is contained in:
nathangray 2019-02-28 15:27:12 -07:00
commit e4d865aed2
11 changed files with 245 additions and 89 deletions

View File

@ -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";

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package admin
* @copyright (c) 2007-18 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
@ -37,13 +37,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']))
@ -145,7 +145,7 @@ class admin_cmd_change_account_id extends admin_cmd
{
throw new Api\Exception\WrongUserinput(implode("\n", $errors), 16);
}
$columns2change = $this->get_changes();
$columns2change = self::get_account_colums();
$total = 0;
foreach($columns2change as $app => $data)
{

View File

@ -5,9 +5,8 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package admin
* @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @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)

View File

@ -5,9 +5,8 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package admin
* @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-19 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @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
* @param array $extra =array() values for requested(_email), comment, ...
@ -49,6 +49,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);
@ -57,28 +64,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);
}
// store old content at time of deletion
$this->old = $GLOBALS['egw']->accounts->read($account_id);
$GLOBALS['egw']->accounts->delete($account_id);
$this->delete_account($this->is_user, $account_id, $account_lid, $new_user);
if ($account_id < 0)
{
@ -87,6 +75,140 @@ 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);
}
// store old content at time of deletion
$this->old = $GLOBALS['egw']->accounts->read($account_id);
$GLOBALS['egw']->accounts->delete($account_id);
}
/**
* Return a title / string representation for a given command, eg. to display it
*

View File

@ -41,6 +41,11 @@ var et2_editableWidget = (function(){ "use strict"; return et2_inputWidget.exten
type: "string",
default: et2_no_init,
description: "Additional parameters passed to save_callback"
},
editable_height: {
name: "Editable height",
description: "Set height for widget while in edit mode",
type: "string"
}
},

View File

@ -168,7 +168,7 @@ var et2_button = (function(){ "use strict"; return et2_baseWidget.extend([et2_II
if(!this.isInTree() || !this.options.background_image && this.image == null) return;
if (typeof _image == 'undefined')
_image = this.options.readonly ? this.options.ro_image : this.options.image;
_image = this.options.readonly ? (this.options.ro_image || this.options.image) : this.options.image;
// Silently blank for percentages instead of warning about missing image - use a progress widget
if(_image.match(/^[0-9]+\%$/))
@ -225,7 +225,7 @@ var et2_button = (function(){ "use strict"; return et2_baseWidget.extend([et2_II
{
this.options.readonly = _ro;
if (this.image)
if (this.options.image || this.options.ro_image)
{
this.update_image();
}

View File

@ -22,18 +22,18 @@
var et2_htmlarea = (function(){ "use strict"; return et2_editableWidget.extend([et2_IResizeable],
{
attributes: {
'mode': {
mode: {
'name': 'Mode',
'description': 'One of {ascii|simple|extended|advanced}',
'default': '',
'type': 'string'
},
'height': {
height: {
'name': 'Height',
'default': et2_no_init,
'type': 'string'
},
'width': {
width: {
'name': 'Width',
'default': et2_no_init,
'type': 'string'
@ -214,7 +214,7 @@ var et2_htmlarea = (function(){ "use strict"; return et2_editableWidget.extend([
this.options.readonly = _value;
if(this.options.readonly)
{
this.editor.remove();
if (this. editor) this.editor.remove();
this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea"))
.css('height', this.options.height)
.addClass('et2_textbox_ro');
@ -227,7 +227,7 @@ var et2_htmlarea = (function(){ "use strict"; return et2_editableWidget.extend([
if(!this.editor)
{
this.htmlNode = jQuery(document.createElement("textarea"))
.css('height', this.options.height)
.css('height', (this.options.editable_height ? this.options.editable_height : this.options.height))
.val(value);
this.setDOMNode(this.htmlNode[0]);
this.init_editor();

View File

@ -86,10 +86,15 @@ class HtmlArea extends Etemplate\Widget
if (!$this->is_readonly($cname, $form_name))
{
$value = Api\Html\HtmLawed::purify(
self::get_array($content, $form_name),
$this->attrs['validation_rules']
);
$value = self::get_array($content, $form_name);
// only purify for html, mode "ascii" is NO html and content get lost!
if ($this->attrs['mode'] != 'ascii')
{
$value = Api\Html\HtmLawed::purify(
self::get_array($content, $form_name),
$this->attrs['validation_rules']
);
}
$valid =& self::get_array($validated, $form_name, true);
if (true) $valid = $value;
}

View File

@ -8,7 +8,6 @@
* Copyright (C) 2000, 2001 Joseph Engo
* @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License
* @package api
* @version $Id$
*/
namespace EGroupware\Api;
@ -852,10 +851,14 @@ class Translation
* @param string|boolean $from charset $data is in or False if it should be detected
* @param string|boolean $to charset to convert to or False for the system-charset the converted string
* @param boolean $check_to_from =true internal to bypass all charset replacements
* @return string|array converted string(s) from $data
* @return NULL|string|array converted string(s) from $data
*/
static function convert($data,$from=False,$to=False,$check_to_from=true)
{
if (empty($data))
{
return $data; // no need for any charset conversation (NULL, '', 0, '0', array())
}
if ($check_to_from)
{
if ($from) $from = strtolower($from);
@ -901,6 +904,9 @@ class Translation
case 'windows-1250':
$from = 'iso-8859-2';
break;
case 'windows-1253':
$from = 'iso-8859-7';
break;
case 'windows-1257':
$from = 'iso-8859-13';
break;
@ -923,7 +929,8 @@ class Translation
{
foreach($data as $key => $str)
{
$ret[$key] = self::convert($str,$from,$to,false); // false = bypass the above checks, as they are already done
$ret[$key] = empty($str) ? $str : // do NOT convert null to '' (other empty values need no conversation too)
self::convert($str,$from,$to,false); // false = bypass the above checks, as they are already done
}
return $ret;
}

View File

@ -1,5 +1,6 @@
{
"name": "egroupware/egroupware",
"version": "dev-master",
"description": "EGroupware extends a classic groupware with an integreted CRM-system, a secure file-server and Collabora Online Office.",
"keywords": [
"groupware",
@ -40,7 +41,7 @@
{
"type": "pear",
"url": "https://pear.horde.org"
}
}
],
"config": {
},
@ -64,16 +65,17 @@
"pear-pear.horde.org/horde_mapi": "^1.0.9",
"pear-pear.horde.org/horde_managesieve": "^1.0.2",
"pear-pear.horde.org/horde_crypt": "^2.7.9",
"pear-pear.horde.org/horde_text_diff": "^2.2",
"pear/pear": "*",
"pear/auth_sasl": "*",
"pear/xml_feed_parser": "^1.0.5",
"pear/log": "*",
"pear/text_diff": "^1.2.2",
"bower-asset/jquery": "^1.12.4",
"bower-asset/fastclick":"1.0.*",
"bower-asset/jquery-touchswipe": "1.6.*",
"bower-asset/jquery-ui":"=1.11.2",
"bower-asset/cropper":"2.3.*",
"bower-asset/diff2html": "^2.7",
"npm-asset/as-jqplot" : "1.0.*",
"npm-asset/gridster":"0.5.*",
"adldap2/adldap2": "=4.0.4",
@ -105,5 +107,7 @@
"installer-paths": {
"{$name}/": ["type:egroupware-app"]
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

94
composer.lock generated
View File

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "0129834b90f9bcd8da3186b801634419",
"content-hash": "2b00ad598700859fd0600f10fd8967bb",
"packages": [
{
"name": "adldap2/adldap2",
@ -114,6 +114,22 @@
"zoom"
]
},
{
"name": "bower-asset/diff2html",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/rtfpessoa/diff2html.git",
"reference": "2512e72f32b709a3dc0615a2d5b32183dd0b3b6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rtfpessoa/diff2html/zipball/2512e72f32b709a3dc0615a2d5b32183dd0b3b6a",
"reference": "2512e72f32b709a3dc0615a2d5b32183dd0b3b6a",
"shasum": ""
},
"type": "bower-asset-library"
},
{
"name": "bower-asset/fastclick",
"version": "v1.0.6",
@ -771,15 +787,12 @@
"php": ">=5.4.0"
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"AGPL-3.0"
],
"description": "Z-Push is an open-source application to synchronize ActiveSync compatible devices",
"homepage": "http://z-push.org/",
"support": {
"source": "https://github.com/EGroupware/z-push/tree/master",
"issues": "https://github.com/EGroupware/z-push/issues"
},
"time": "2019-02-11T19:26:21+00:00"
},
{
@ -1684,6 +1697,37 @@
],
"description": "Support classes not tied to Horde but is used by it. These classes can be used outside of Horde as well."
},
{
"name": "pear-pear.horde.org/Horde_Text_Diff",
"version": "2.2.0",
"dist": {
"type": "file",
"url": "https://pear.horde.org/get/Horde_Text_Diff-2.2.0.tgz",
"reference": null,
"shasum": null
},
"require": {
"pear-pear.horde.org/horde_exception": "<3.0.0.0",
"pear-pear.horde.org/horde_util": "<3.0.0.0",
"php": "<8.0.0.0"
},
"replace": {
"pear-horde/horde_text_diff": "== 2.2.0.0"
},
"type": "pear-library",
"autoload": {
"classmap": [
""
]
},
"include-path": [
"/"
],
"license": [
"LGPL-2.1"
],
"description": "A text-based diff engine and renderers for multiple diff output formats."
},
{
"name": "pear-pear.horde.org/Horde_Text_Flowed",
"version": "2.0.3",
@ -2209,39 +2253,6 @@
"description": "More info available on: http://pear.php.net/package/Structures_Graph",
"time": "2015-07-20T20:05:12+00:00"
},
{
"name": "pear/text_diff",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/pear/Text_Diff.git",
"reference": "d12474df481bb89f52b02ba0e3fae504ce5d6d51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/Text_Diff/zipball/d12474df481bb89f52b02ba0e3fae504ce5d6d51",
"reference": "d12474df481bb89f52b02ba0e3fae504ce5d6d51",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"Text/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"description": "Engine for performing and rendering text diffs",
"homepage": "http://pear.php.net/package/Text_Diff",
"keywords": [
"PEAR",
"diff",
"text"
],
"time": "2017-03-08T15:07:59+00:00"
},
{
"name": "pear/xml_feed_parser",
"version": "v1.0.5",
@ -2347,9 +2358,8 @@
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"minimum-stability": "dev",
"stability-flags": {
"php": 15,
"egroupware/collabora": 20,
"egroupware/projectmanager": 20,
"egroupware/tracker": 20,
@ -2357,7 +2367,7 @@
"egroupware/activesync": 20,
"egroupware/adodb-php": 20
},
"prefer-stable": false,
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=7.0,<=8.0.0alpha1",