WIP admin history: using eTemplate tree instead of regexp to parse labels and selectbox values

This commit is contained in:
Ralf Becker 2019-03-19 15:34:44 +01:00
parent 6e5fbbba7e
commit fa8ee606b3
12 changed files with 241 additions and 92 deletions

View File

@ -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:
* - <description value="Expires" for="account_expires"/>
* - <description value="Label"/>\n<widget id="id"/>
* - <checkbox id="account_status" selected_value="A" label="Account active"/>
* @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('/<description.*value="([^"]+)".*\n?.*(for|id)="([^"]+)"/sU', $tpl, $matches, PREG_PATTERN_ORDER))
$tpl->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;
}
}

View File

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

View File

@ -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));
}
}

View File

@ -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';
}
/**

View File

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

View File

@ -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
*

View File

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

View File

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

View File

@ -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)

View File

@ -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
*/

View File

@ -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)

View File

@ -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
*/