diff --git a/admin/inc/class.admin_cmd.inc.php b/admin/inc/class.admin_cmd.inc.php
index ad77f2b0c1..7882e529a4 100644
--- a/admin/inc/class.admin_cmd.inc.php
+++ b/admin/inc/class.admin_cmd.inc.php
@@ -1296,6 +1296,40 @@ abstract class admin_cmd
return Api\Auth::randomstring($len);
}
+ /**
+ * Get name of eTemplate used to make the change to derive UI for history
+ *
+ * @return string|null etemplate name
+ */
+ protected function get_etemplate_name()
+ {
+ return null;
+ }
+
+ /**
+ * Return eTemplate used to make the change to derive UI for history
+ *
+ * @return Api\Etemplate|null
+ */
+ protected function get_etemplate()
+ {
+ static $tpl = null; // some caching to not instanciate it twice
+
+ if (!isset($tpl))
+ {
+ $name = $this->get_etemplate_name();
+ if (empty($name))
+ {
+ $tpl = false;
+ }
+ else
+ {
+ $tpl = Api\Etemplate::instance($name);
+ }
+ }
+ return $tpl ? $tpl : null;
+ }
+
/**
* Return (human readable) labels for keys of changes
*
@@ -1303,71 +1337,82 @@ abstract class admin_cmd
*/
function get_change_labels()
{
- return [];
+ $labels = [];
+ $label = null;
+ if (($tpl = $this->get_etemplate()))
+ {
+ $tpl->run(function($cname, $expand, $widget) use (&$labels, &$label)
+ {
+ switch($widget->type)
+ {
+ // remember label from last description widget
+ case 'description':
+ if (!empty($widget->attrs['value'])) $label = $widget->attrs['value'];
+ break;
+ // ignore non input-widgets
+ case 'hbox': case 'vbox': case 'box': case 'groupbox':
+ case 'grid': case 'columns': case 'column': case 'rows': case 'row':
+ case 'template': case 'tabbox': case 'tabs': case 'tab':
+ // ignore buttons too
+ case 'button':
+ break;
+ default:
+ if (!empty($widget->id))
+ {
+ if (!empty($widget->attrs['label'])) $label = $widget->attrs['label'];
+ $labels[$widget->id] = $label;
+ $label = null;
+ }
+ break;
+ }
+ unset($cname, $expand);
+ }, ['', []]);
+ }
+ return $labels;
}
/**
* Return widget types (indexed by field key) for changes
+ *
* Used by historylog widget to show the changes the command recorded.
*/
function get_change_widgets()
{
- // TODO: Some kind of regex?
- return [];
- }
-
- /**
- * Read change labels from descriptions in template:
- * -
- * - \n
- * -
- * @param type $name
- */
- protected function change_labels_from_template($name)
- {
- $labels = [];
- $matches = null;
- if (($path = Api\Etemplate::relPath($name)) &&
- ($tpl = file_get_contents(Api\Etemplate::rel2path($path))))
+ $widgets = [];
+ $last_select = null;
+ if (($tpl = $this->get_etemplate()))
{
- if (preg_match_all('/run(function($cname, $expand, $widget) use (&$widgets, &$last_select)
{
- foreach($matches[1] as $key => $name)
+ switch($widget->type)
{
- $id = $matches[3][$key];
- $label= $matches[1][$key];
- if (!empty($id) && !empty($label))
- {
- $labels[$id] = $label;
- }
+ // ignore non input-widgets
+ case 'hbox': case 'vbox': case 'box': case 'groupbox':
+ case 'grid': case 'columns': case 'column': case 'rows': case 'row':
+ case 'template': case 'tabbox': case 'tabs': case 'tab':
+ // ignore buttons too
+ case 'button':
+ break;
+ // config templates have options in the template
+ case 'option':
+ if (!is_array($widgets[$last_select])) $widgets[$last_select] = [];
+ $widgets[$last_select][(string)$widget->attrs['value']] = $widget->attrs['#text'];
+ break;
+ default:
+ $last_select = null;
+ if (!empty($widget->id))
+ {
+ $widgets[$widget->id] = $widget->type;
+ if (in_array($widget->type, ['select']))
+ {
+ $last_select = $widget->id;
+ }
+ }
+ break;
}
- }
- if (preg_match_all('/<(checkbox).*(label|id)="([^"]+)".*(label|id)="([^"]+)"/', $tpl, $matches, PREG_PATTERN_ORDER))
- {
- foreach($matches[2] as $key => $name)
- {
- $id = $name === 'id' ? $matches[3][$key] : $matches[5][$key];
- $label= $name === 'id' ? $matches[5][$key] : $matches[3][$key];
- if (!empty($id) && !empty($label))
- {
- $labels[$id] = $label;
- }
- }
- }
- if (preg_match_all('/<(int|float).*(label|id)="([^"]+)".*(label|id)="([^"]+)"/', $tpl, $matches, PREG_PATTERN_ORDER))
- {
- foreach($matches[2] as $key => $name)
- {
- $id = $name === 'id' ? $matches[3][$key] : $matches[5][$key];
- $label= $name === 'id' ? $matches[5][$key] : $matches[3][$key];
- if (!empty($id) && !empty($label))
- {
- $labels[$id] = $label;
- }
- }
- }
+ unset($cname, $expand);
+ }, ['', []]);
}
- error_log(__METHOD__."($name) path=$path returning ".json_encode($labels));
- return $labels;
+ return $widgets;
}
}
diff --git a/admin/inc/class.admin_cmd_config.inc.php b/admin/inc/class.admin_cmd_config.inc.php
index 2a78a75c64..0f1ac1ec13 100644
--- a/admin/inc/class.admin_cmd_config.inc.php
+++ b/admin/inc/class.admin_cmd_config.inc.php
@@ -87,17 +87,27 @@ class admin_cmd_config extends admin_cmd
lang($this->appname ? $this->appname : $this->app));
}
+ /**
+ * Get name of eTemplate used to make the change to derive UI for history
+ *
+ * @return string|null etemplate name
+ */
+ function get_etemplate_name()
+ {
+ return ($this->appname ? $this->appname : $this->app).'.config';
+ }
+
/**
* Return (human readable) labels for keys of changes
*
- * Reading them from admin.account template
+ * Reimplemented to get ride of "newsettins" namespace
*
* @return array
*/
function get_change_labels()
{
$labels = [];
- foreach($this->change_labels_from_template(($this->appname ? $this->appname : $this->app).'.config') as $id => $label)
+ foreach(parent::get_change_labels() as $id => $label)
{
if (strpos($id, 'newsettings[') === 0)
{
@@ -106,4 +116,24 @@ class admin_cmd_config extends admin_cmd
}
return $labels;
}
+
+ /**
+ * Return widgets for keys of changes
+ *
+ * Reimplemented to get ride of "newsettins" namespace
+ *
+ * @return array
+ */
+ function get_change_widgets()
+ {
+ $widgets = [];
+ foreach(parent::get_change_widgets() as $id => $widget)
+ {
+ if (strpos($id, 'newsettings[') === 0)
+ {
+ $widgets[substr($id, 12, -1)] = $widget;
+ }
+ }
+ return $widgets;
+ }
}
diff --git a/admin/inc/class.admin_cmd_delete_account.inc.php b/admin/inc/class.admin_cmd_delete_account.inc.php
index 7096f2ce16..24c74d542a 100644
--- a/admin/inc/class.admin_cmd_delete_account.inc.php
+++ b/admin/inc/class.admin_cmd_delete_account.inc.php
@@ -210,12 +210,83 @@ class admin_cmd_delete_account extends admin_cmd
}
/**
+ * Return (human readable) labels for keys of changes
+ *
+ * Reading them from admin.account template
+ *
+ * @return array
+ */
+ function get_etemplate_name()
+ {
+ return $this->is_user ? 'admin.account':
+ ($GLOBALS['egw_info']['apps']['stylite'] ? 'stylite' : 'groups').'.group.edit';
+ }
+
+ /**
+ * Return widget types (indexed by field key) for changes
+ *
+ * Used by historylog widget to show the changes the command recorded.
+ */
+ function get_change_labels()
+ {
+ $widgets = parent::get_change_labels();
+
+ $widgets['account_id'] = 'numerical ID'; // normaly not displayed
+
+ return $widgets;
+ }
+
+ /**
+ * Return widget types (indexed by field key) for changes
+ *
+ * Used by historylog widget to show the changes the command recorded.
+ */
+ function get_change_widgets()
+ {
+ $widgets = parent::get_change_widgets();
+
+ $widgets['account_id'] = 'integer'; // normaly not displayed
+
+ return $widgets;
+ }
+
+ /**
+ * Return the whole object-data as array, it's a cast of the object to an array
+ *
+ * Reimplement to supress data not relevant for groups, but historically stored
+ *
+ * @return array
+ */
+ function as_array()
+ {
+ $data = parent::as_array();
+
+ if (!$this->is_user)
+ {
+ $data['old'] = array_diff_key($data['old'], array_flip([
+ 'account_pwd', 'account_status',
+ 'account_expires', 'account_primary_group',
+ 'account_lastlogin', 'account_lastloginfrom',
+ 'account_lastpwd_change', 'members-active',
+ 'account_firstname', 'account_lastname', 'account_fullname',
+ ]));
+ }
+ unset($data['old']['account_type']);
+
+ return $data;
+ }
+
+ /**
+ }
* Return a title / string representation for a given command, eg. to display it
*
* @return string
*/
function __tostring()
{
- return lang('Delete account %1',admin_cmd::display_account($this->account));
+ return lang('Delete account %1',
+ // use own data to display deleted name of user/group
+ $this->old['account_lid'] ? ($this->is_user ? lang('User') : lang('Group')).' '.$this->old['account_lid'] :
+ admin_cmd::display_account($this->account));
}
}
diff --git a/admin/inc/class.admin_cmd_edit_user.inc.php b/admin/inc/class.admin_cmd_edit_user.inc.php
index 0fda593257..f8bd81a4d8 100644
--- a/admin/inc/class.admin_cmd_edit_user.inc.php
+++ b/admin/inc/class.admin_cmd_edit_user.inc.php
@@ -225,36 +225,13 @@ class admin_cmd_edit_user extends admin_cmd_change_pw
}
/**
- * Return (human readable) labels for keys of changes
+ * Get name of eTemplate used to make the change to derive UI for history
*
- * Reading them from admin.account template
- *
- * @return array
+ * @return string|null etemplate name
*/
- function get_change_labels()
+ function get_etemplate_name()
{
- $labels = $this->change_labels_from_template('admin.account');
- $labels += array(
- 'account_firstname' => 'First name',
- 'account_lastname' => 'Last name',
- 'account_email' => 'Email',
- 'account_passwd_2' => false
- );
- return $labels;
- }
-
- /**
- * Return list of widgets to use for displaying changes
- */
- function get_change_widgets() {
- $widgets = parent::get_change_widgets();
-
- $widgets += array(
- 'account_primary_group' => 'select-account',
- 'account_groups' => 'select-account',
- 'account_expires' => 'date-time'
- );
- return $widgets;
+ return 'admin.account';
}
/**
diff --git a/api/js/etemplate/et2_extension_nextmatch.js b/api/js/etemplate/et2_extension_nextmatch.js
index bddd9556df..a5e3632078 100644
--- a/api/js/etemplate/et2_extension_nextmatch.js
+++ b/api/js/etemplate/et2_extension_nextmatch.js
@@ -2985,7 +2985,7 @@ var et2_nextmatch_header_bar = (function(){ "use strict"; return et2_DOMWidget.e
value[_widget.id] = _widget._oldValue = _widget.getValue();
var mgr = new et2_arrayMgr(value);
jQuery.extend(true, this.nextmatch.activeFilters,mgr.data);
- }
+ };
if(sub_header.instanceOf(et2_inputWidget))
{
bind_change.call(this, sub_header);
diff --git a/api/src/Etemplate/Request.php b/api/src/Etemplate/Request.php
index e21a909ede..d92027f576 100644
--- a/api/src/Etemplate/Request.php
+++ b/api/src/Etemplate/Request.php
@@ -91,7 +91,10 @@ class Request
*
* @var array
*/
- protected $data=array();
+ protected $data=[
+ 'content' => [],
+ 'readonlys' => [],
+ ];
/**
* Flag if data has been modified and therefor need to be stored again in the session
*
diff --git a/api/src/Etemplate/Widget.php b/api/src/Etemplate/Widget.php
index 45a6c45ba4..22cb36cf2c 100644
--- a/api/src/Etemplate/Widget.php
+++ b/api/src/Etemplate/Widget.php
@@ -129,6 +129,11 @@ class Widget
{
$this->children[] = self::factory($reader->name, $reader, $reader->getAttribute('id'));
}
+ // read eg. option content to "#text"
+ if ($reader->nodeType == XMLReader::TEXT)
+ {
+ $this->attrs[(string)$reader->name] = (string)$reader->value;
+ }
}
// Reset content as we leave
@@ -508,7 +513,7 @@ class Widget
*
* Default implementation only calls method on itself and run on all children
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
*/
@@ -547,6 +552,12 @@ class Widget
}
if($call) call_user_func_array(array($this, $method_name), $params);
}
+ // allow calling with a function or closure --> call it with widget as first param
+ elseif (is_callable($method_name))
+ {
+ $params[2] = $this;
+ call_user_func_array($method_name, $params);
+ }
foreach($this->children as $child)
{
// If type has something that can be expanded, we need to expand it so the correct method is run
diff --git a/api/src/Etemplate/Widget/Box.php b/api/src/Etemplate/Widget/Box.php
index 947c5e1580..5d678315a1 100644
--- a/api/src/Etemplate/Widget/Box.php
+++ b/api/src/Etemplate/Widget/Box.php
@@ -38,7 +38,7 @@ class Box extends Etemplate\Widget
* Reimplemented because grids and boxes can have an own namespace.
* GroupBox has no namespace!
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be cname!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
*/
@@ -64,6 +64,12 @@ class Box extends Etemplate\Widget
{
call_user_func_array(array($this, $method_name), $params);
}
+ // allow calling with a function or closure --> call it with widget as first param
+ elseif (is_callable($method_name))
+ {
+ $params[2] = $this;
+ call_user_func_array($method_name, $params);
+ }
// Expand children
$columns_disabled = null;
diff --git a/api/src/Etemplate/Widget/Grid.php b/api/src/Etemplate/Widget/Grid.php
index 11319e8ed0..d074cde2ee 100644
--- a/api/src/Etemplate/Widget/Grid.php
+++ b/api/src/Etemplate/Widget/Grid.php
@@ -60,7 +60,7 @@ class Grid extends Box
* - row run method checks now for each child (arbitrary widget) if it the column's key is included in $columns_disabled
* - as a grid can contain other grid's as direct child, we have to backup and initialise $columns_disabled in grid run!
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
* @param array $columns_disabled=array() disabled columns
@@ -99,6 +99,12 @@ class Grid extends Box
{
call_user_func_array(array($this, $method_name), $params);
}
+ // allow calling with a function or closure --> call it with widget as first param
+ elseif (is_callable($method_name))
+ {
+ $params[2] = $this;
+ call_user_func_array($method_name, $params);
+ }
//foreach($this->children as $n => $child)
$repeat_child = null;
for($n = 0; ; ++$n)
diff --git a/api/src/Etemplate/Widget/Nextmatch.php b/api/src/Etemplate/Widget/Nextmatch.php
index f44006f857..94a2c525a6 100644
--- a/api/src/Etemplate/Widget/Nextmatch.php
+++ b/api/src/Etemplate/Widget/Nextmatch.php
@@ -1204,7 +1204,7 @@ class Nextmatch extends Etemplate\Widget
*
* Reimplemented to add namespace, and make sure row template gets included
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be cname, second $expand!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
*/
diff --git a/api/src/Etemplate/Widget/Tabbox.php b/api/src/Etemplate/Widget/Tabbox.php
index b19e24e8ed..d4bdf9ee73 100644
--- a/api/src/Etemplate/Widget/Tabbox.php
+++ b/api/src/Etemplate/Widget/Tabbox.php
@@ -38,7 +38,7 @@ class Tabbox extends Etemplate\Widget
* Overridden here to apply readonlys for the tabbox to disabled on the tab
* content. This prevents running the method on disabled tabs.
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
*/
@@ -91,7 +91,7 @@ class Tabbox extends Etemplate\Widget
}
}
}
-
+
if($respect_disabled && $readonlys)
{
foreach($this->children[1]->children as $tab)
diff --git a/api/src/Etemplate/Widget/Template.php b/api/src/Etemplate/Widget/Template.php
index 718449475c..5b57356135 100644
--- a/api/src/Etemplate/Widget/Template.php
+++ b/api/src/Etemplate/Widget/Template.php
@@ -234,7 +234,7 @@ class Template extends Etemplate\Widget
*
* Reimplemented because templates can have an own namespace specified in attrs[content], NOT id!
*
- * @param string $method_name
+ * @param string|callable $method_name or function($cname, $expand, $widget)
* @param array $params =array('') parameter(s) first parameter has to be cname, second $expand!
* @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children
*/