From fa8ee606b3ad71346b1ee1eaff275df11eac5eae Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 19 Mar 2019 15:34:44 +0100 Subject: [PATCH] WIP admin history: using eTemplate tree instead of regexp to parse labels and selectbox values --- admin/inc/class.admin_cmd.inc.php | 151 ++++++++++++------ admin/inc/class.admin_cmd_config.inc.php | 34 +++- .../class.admin_cmd_delete_account.inc.php | 73 ++++++++- admin/inc/class.admin_cmd_edit_user.inc.php | 31 +--- api/js/etemplate/et2_extension_nextmatch.js | 2 +- api/src/Etemplate/Request.php | 5 +- api/src/Etemplate/Widget.php | 13 +- api/src/Etemplate/Widget/Box.php | 8 +- api/src/Etemplate/Widget/Grid.php | 8 +- api/src/Etemplate/Widget/Nextmatch.php | 2 +- api/src/Etemplate/Widget/Tabbox.php | 4 +- api/src/Etemplate/Widget/Template.php | 2 +- 12 files changed, 241 insertions(+), 92 deletions(-) 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 */