mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-02-04 12:30:04 +01:00
Historylog + webcomponents
- historylog widget now uses webcomponents for timestamp & user, and whenever possible for values - History::get_rows() formats customfield date/date-time values as needed - implement setDetachedAttributes(), if nextmatch & historylog are calling them on webcomponents, might as well use that to set what they pass
This commit is contained in:
parent
ed5e7414ac
commit
10af4f7514
@ -88,7 +88,10 @@ export class Et2DateDurationReadonly extends Et2DateDuration
|
|||||||
|
|
||||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||||
{
|
{
|
||||||
// Do nothing, since we can't actually stop being a DOM node...
|
for(let attr in _values)
|
||||||
|
{
|
||||||
|
this[attr] = _values[attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,10 @@ export class Et2DateReadonly extends Et2Widget(LitElement) implements et2_IDetac
|
|||||||
|
|
||||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||||
{
|
{
|
||||||
// Do nothing, since we can't actually stop being a DOM node...
|
for(let attr in _values)
|
||||||
|
{
|
||||||
|
this[attr] = _values[attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromXML()
|
loadFromXML()
|
||||||
|
@ -229,7 +229,10 @@ export class Et2Description extends Et2Widget(LitElement) implements et2_IDetach
|
|||||||
|
|
||||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||||
{
|
{
|
||||||
// Do nothing, since we can't actually stop being a DOM node...
|
for(let attr in _values)
|
||||||
|
{
|
||||||
|
this[attr] = _values[attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromXML()
|
loadFromXML()
|
||||||
|
@ -230,7 +230,10 @@ export class Et2Image extends Et2Widget(SlotMixin(LitElement)) implements et2_ID
|
|||||||
|
|
||||||
setDetachedAttributes(_nodes, _values)
|
setDetachedAttributes(_nodes, _values)
|
||||||
{
|
{
|
||||||
// Do nothing, setting attribute / property just sets it
|
for(let attr in _values)
|
||||||
|
{
|
||||||
|
this[attr] = _values[attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +51,15 @@ li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private __select_options : SelectOption[];
|
||||||
|
private __value : string[];
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.type = "";
|
this.type = "";
|
||||||
this.__select_options = <SelectOption[]>[];
|
this.__select_options = <SelectOption[]>[];
|
||||||
|
this.__value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected find_select_options(_attrs)
|
protected find_select_options(_attrs)
|
||||||
@ -108,12 +112,15 @@ li {
|
|||||||
new_value = ["" + new_value];
|
new_value = ["" + new_value];
|
||||||
}
|
}
|
||||||
|
|
||||||
super.value = new_value;
|
let oldValue = this.__value;
|
||||||
|
this.__value = new_value;
|
||||||
|
|
||||||
|
this.requestUpdate("value", oldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
get value()
|
get value()
|
||||||
{
|
{
|
||||||
return super.value;
|
return this.__value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,7 +201,10 @@ li {
|
|||||||
|
|
||||||
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
setDetachedAttributes(_nodes : HTMLElement[], _values : object, _data? : any) : void
|
||||||
{
|
{
|
||||||
// Do nothing, since we can't actually stop being a DOM node...
|
for(let attr in _values)
|
||||||
|
{
|
||||||
|
this[attr] = _values[attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromXML()
|
loadFromXML()
|
||||||
|
@ -1251,16 +1251,23 @@ export const Et2Widget = dedupeMixin(Et2WidgetMixin);
|
|||||||
// @ts-ignore Et2Widget is I guess not the right type
|
// @ts-ignore Et2Widget is I guess not the right type
|
||||||
export function loadWebComponent(_nodeName : string, _template_node : Element|{[index: string]: any}, parent : Et2Widget | et2_widget) : HTMLElement
|
export function loadWebComponent(_nodeName : string, _template_node : Element|{[index: string]: any}, parent : Et2Widget | et2_widget) : HTMLElement
|
||||||
{
|
{
|
||||||
|
let attrs = {};
|
||||||
|
let load_children = true;
|
||||||
|
|
||||||
// support attributes object instead of an Element
|
// support attributes object instead of an Element
|
||||||
if (typeof _template_node.getAttribute === 'undefined')
|
if(typeof _template_node.getAttribute === 'undefined')
|
||||||
{
|
{
|
||||||
const _names = Object.keys(_template_node);
|
attrs = _template_node;
|
||||||
_template_node.getAttributeNames = () => _names;
|
load_children = false;
|
||||||
_template_node.getAttribute = attr => _template_node[attr];
|
|
||||||
_template_node.querySelectorAll = () => [];
|
|
||||||
(<any>_template_node).nodeName = _nodeName;
|
|
||||||
(<any>_template_node).childNodes = [];
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_template_node.getAttributeNames().forEach(attribute =>
|
||||||
|
{
|
||||||
|
attrs[attribute] = _template_node.getAttribute(attribute);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Try to find the class for the given node
|
// Try to find the class for the given node
|
||||||
let widget_class = window.customElements.get(_nodeName);
|
let widget_class = window.customElements.get(_nodeName);
|
||||||
if(!widget_class)
|
if(!widget_class)
|
||||||
@ -1279,7 +1286,7 @@ export function loadWebComponent(_nodeName : string, _template_node : Element|{[
|
|||||||
}
|
}
|
||||||
const readonly = parent.getArrayMgr("readonlys") ?
|
const readonly = parent.getArrayMgr("readonlys") ?
|
||||||
(<any>parent.getArrayMgr("readonlys")).isReadOnly(
|
(<any>parent.getArrayMgr("readonlys")).isReadOnly(
|
||||||
_template_node.getAttribute("id"), _template_node.getAttribute("readonly"),
|
attrs["id"], attrs["readonly"],
|
||||||
typeof parent.readonly !== "undefined" ? parent.readonly : false) : false;
|
typeof parent.readonly !== "undefined" ? parent.readonly : false) : false;
|
||||||
if(readonly === true && typeof window.customElements.get(_nodeName + "_ro") != "undefined")
|
if(readonly === true && typeof window.customElements.get(_nodeName + "_ro") != "undefined")
|
||||||
{
|
{
|
||||||
@ -1293,20 +1300,15 @@ export function loadWebComponent(_nodeName : string, _template_node : Element|{[
|
|||||||
widget.setParent(parent);
|
widget.setParent(parent);
|
||||||
|
|
||||||
// Set read-only. Doesn't really matter if it's a ro widget, but otherwise it needs set
|
// Set read-only. Doesn't really matter if it's a ro widget, but otherwise it needs set
|
||||||
widget.readOnly = parent.getArrayMgr("readonlys") ?
|
widget.readOnly = readonly;
|
||||||
(<any>parent.getArrayMgr("readonlys")).isReadOnly(
|
|
||||||
_template_node.getAttribute("id"), _template_node.getAttribute("readonly"),
|
|
||||||
typeof parent.readonly !== "undefined" ? parent.readonly : false) : false;
|
|
||||||
|
|
||||||
let attrs = {};
|
|
||||||
_template_node.getAttributeNames().forEach(attribute =>
|
|
||||||
{
|
|
||||||
attrs[attribute] = _template_node.getAttribute(attribute);
|
|
||||||
});
|
|
||||||
widget.transformAttributes(attrs);
|
widget.transformAttributes(attrs);
|
||||||
|
|
||||||
// Children need to be loaded
|
// Children need to be loaded
|
||||||
widget.loadFromXML(_template_node);
|
if(load_children)
|
||||||
|
{
|
||||||
|
widget.loadFromXML(_template_node);
|
||||||
|
}
|
||||||
|
|
||||||
return widget;
|
return widget;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import {et2_dynheight} from "./et2_widget_dynheight";
|
|||||||
import {et2_customfields_list} from "./et2_extension_customfields";
|
import {et2_customfields_list} from "./et2_extension_customfields";
|
||||||
import {et2_selectbox} from "./et2_widget_selectbox";
|
import {et2_selectbox} from "./et2_widget_selectbox";
|
||||||
import {loadWebComponent} from "./Et2Widget/Et2Widget";
|
import {loadWebComponent} from "./Et2Widget/Et2Widget";
|
||||||
|
import {SelectOption} from "./Et2Select/FindSelectOptions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* eTemplate history log widget displays a list of changes to the current record.
|
* eTemplate history log widget displays a list of changes to the current record.
|
||||||
@ -72,9 +73,16 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
|
|
||||||
public static readonly legacyOptions = ["status_id"];
|
public static readonly legacyOptions = ["status_id"];
|
||||||
protected static columns = [
|
protected static columns = [
|
||||||
{'id': 'user_ts', caption: 'Date', 'width': '120px', widget_type: 'date-time', widget: null, nodes: null},
|
{'id': 'user_ts', caption: 'Date', 'width': '120px', widget_type: 'et2-date-time', widget: null, nodes: null},
|
||||||
{'id': 'owner', caption: 'User', 'width': '150px', widget_type: 'select-account', widget: null, nodes: null},
|
{
|
||||||
{'id': 'status', caption: 'Changed', 'width': '120px', widget_type: 'select', widget: null, nodes: null},
|
'id': 'owner',
|
||||||
|
caption: 'User',
|
||||||
|
'width': '150px',
|
||||||
|
widget_type: 'et2-select-account_ro',
|
||||||
|
widget: null,
|
||||||
|
nodes: null
|
||||||
|
},
|
||||||
|
{'id': 'status', caption: 'Changed', 'width': '120px', widget_type: 'et2-select', widget: null, nodes: null},
|
||||||
{'id': 'new_value', caption: 'New Value', 'width': '50%', widget: null, nodes: null},
|
{'id': 'new_value', caption: 'New Value', 'width': '50%', widget: null, nodes: null},
|
||||||
{'id': 'old_value', caption: 'Old Value', 'width': '50%', widget: null, nodes: null}
|
{'id': 'old_value', caption: 'Old Value', 'width': '50%', widget: null, nodes: null}
|
||||||
];
|
];
|
||||||
@ -307,17 +315,8 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
if(column.widget_type)
|
if(column.widget_type)
|
||||||
{
|
{
|
||||||
// Status ID is allowed to be remapped to something else. Only affects the widget ID though
|
// Status ID is allowed to be remapped to something else. Only affects the widget ID though
|
||||||
var attrs = {'readonly': true, 'id': (i == et2_historylog.FIELD ? this.options.status_id : column.id)};
|
let attrs = {'readonly': true, 'id': (i == et2_historylog.FIELD ? this.options.status_id : column.id)};
|
||||||
// Create widget, still preferring legacy widgets
|
column.widget = loadWebComponent(column.widget_type, attrs, this);
|
||||||
if (typeof et2_registry[column.widget_type] !== 'undefined')
|
|
||||||
{
|
|
||||||
column.widget = et2_createWidget(column.widget_type, attrs, this);
|
|
||||||
column.widget.transformAttributes(attrs);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
column.widget = loadWebComponent('et2-'+column.widget_type, attrs, this);
|
|
||||||
}
|
|
||||||
column.nodes = jQuery(column.widget.getDetachedNodes());
|
column.nodes = jQuery(column.widget.getDetachedNodes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,27 +324,36 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
// Add in handling for links
|
// Add in handling for links
|
||||||
if(typeof this.options.value['status-widgets']['~link~'] == 'undefined')
|
if(typeof this.options.value['status-widgets']['~link~'] == 'undefined')
|
||||||
{
|
{
|
||||||
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['~link~'] = this.egw().lang('link');
|
et2_historylog.columns[et2_historylog.FIELD].widget.select_options.push({
|
||||||
|
value: '~link~',
|
||||||
|
label: this.egw().lang('link')
|
||||||
|
});
|
||||||
this.options.value['status-widgets']['~link~'] = 'link';
|
this.options.value['status-widgets']['~link~'] = 'link';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add in handling for files
|
// Add in handling for files
|
||||||
if(typeof this.options.value['status-widgets']['~file~'] == 'undefined')
|
if(typeof this.options.value['status-widgets']['~file~'] == 'undefined')
|
||||||
{
|
{
|
||||||
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['~file~'] = this.egw().lang('File');
|
et2_historylog.columns[et2_historylog.FIELD].widget.select_options.push({
|
||||||
|
value: '~file~',
|
||||||
|
label: this.egw().lang('File')
|
||||||
|
});
|
||||||
this.options.value['status-widgets']['~file~'] = 'vfs';
|
this.options.value['status-widgets']['~file~'] = 'vfs';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add in handling for user-agent & action
|
// Add in handling for user-agent & action
|
||||||
if(typeof this.options.value['status-widgets']['user_agent_action'] == 'undefined')
|
if(typeof this.options.value['status-widgets']['user_agent_action'] == 'undefined')
|
||||||
{
|
{
|
||||||
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['user_agent_action'] = this.egw().lang('User-agent & action');
|
et2_historylog.columns[et2_historylog.FIELD].widget.select_options.push({
|
||||||
|
value: 'user_agent_action',
|
||||||
|
label: this.egw().lang('User-agent & action')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-field widgets - new value & old value
|
// Per-field widgets - new value & old value
|
||||||
this.fields = {};
|
this.fields = {};
|
||||||
|
|
||||||
let labels = et2_historylog.columns[et2_historylog.FIELD].widget.optionValues;
|
let labels = et2_historylog.columns[et2_historylog.FIELD].widget.select_options;
|
||||||
|
|
||||||
// Custom fields - Need to create one that's all read-only for proper display
|
// Custom fields - Need to create one that's all read-only for proper display
|
||||||
let cf_widget = <et2_customfields_list>et2_createWidget('customfields', {'readonly':true}, this);
|
let cf_widget = <et2_customfields_list>et2_createWidget('customfields', {'readonly':true}, this);
|
||||||
@ -355,7 +363,18 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
for(let key in cf_widget.widgets)
|
for(let key in cf_widget.widgets)
|
||||||
{
|
{
|
||||||
// Add label
|
// Add label
|
||||||
labels[cf_widget.prefix + key] = cf_widget.options.customfields[key].label;
|
let option = (<SelectOption[]>labels).find(option => option.value == et2_customfields_list.PREFIX + key);
|
||||||
|
if(option && !option.label)
|
||||||
|
{
|
||||||
|
option.label = cf_widget.options.customfields[key].label;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
labels.push({
|
||||||
|
value: et2_customfields_list.PREFIX + key,
|
||||||
|
label: cf_widget.options.customfields[key].label
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If it doesn't support detached nodes, just treat it as text
|
// If it doesn't support detached nodes, just treat it as text
|
||||||
if(cf_widget.widgets[key].getDetachedNodes)
|
if(cf_widget.widgets[key].getDetachedNodes)
|
||||||
@ -363,11 +382,14 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
var nodes = cf_widget.widgets[key].getDetachedNodes();
|
var nodes = cf_widget.widgets[key].getDetachedNodes();
|
||||||
for(var i = 0; i < nodes.length; i++)
|
for(var i = 0; i < nodes.length; i++)
|
||||||
{
|
{
|
||||||
if(nodes[i] == null) nodes.splice(i,1);
|
if(nodes[i] == null)
|
||||||
|
{
|
||||||
|
nodes.splice(i, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to use for each row
|
// Save to use for each row
|
||||||
this.fields[cf_widget.prefix + key] = {
|
this.fields[et2_customfields_list.PREFIX + key] = {
|
||||||
attrs: cf_widget.widgets[key].options,
|
attrs: cf_widget.widgets[key].options,
|
||||||
widget: cf_widget.widgets[key],
|
widget: cf_widget.widgets[key],
|
||||||
nodes: jQuery(nodes)
|
nodes: jQuery(nodes)
|
||||||
@ -471,7 +493,23 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
|
|
||||||
if(widget === null)
|
if(widget === null)
|
||||||
{
|
{
|
||||||
widget = et2_createWidget(typeof field === 'string' ? field : 'select', attrs, this);
|
if(typeof field === "string" && typeof window.customElements.get(field) !== "undefined")
|
||||||
|
{
|
||||||
|
widget = loadWebComponent(field, attrs, this);
|
||||||
|
}
|
||||||
|
else if(typeof field === "string" && typeof window.customElements.get("et2-" + field) !== "undefined")
|
||||||
|
{
|
||||||
|
widget = loadWebComponent("et2-" + field, attrs, this);
|
||||||
|
console.log("History specified legacy widget '" + field + "' for " + key + ", used web component instead. Please change in PHP source.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
widget = et2_createWidget(typeof field === 'string' ? field : 'select', attrs, this);
|
||||||
|
if(typeof field === "string")
|
||||||
|
{
|
||||||
|
console.log("History specified legacy widget '" + field + "' for " + key + ". Please change in PHP source.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!widget.instanceOf(et2_IDetachedDOM))
|
if(!widget.instanceOf(et2_IDetachedDOM))
|
||||||
@ -608,7 +646,12 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
))
|
))
|
||||||
{
|
{
|
||||||
widget = self.fields[_data.status].widget;
|
widget = self.fields[_data.status].widget;
|
||||||
if(!widget._children.length)
|
if(widget && typeof window.customElements.get(widget.localName) != "undefined")
|
||||||
|
{
|
||||||
|
nodes = widget.clone() // Note: Slower than cloneNode(), but simpler to deal with all the different widgets;
|
||||||
|
widget = nodes;
|
||||||
|
}
|
||||||
|
else if(!widget._children.length)
|
||||||
{
|
{
|
||||||
nodes = self.fields[_data.status].nodes.clone();
|
nodes = self.fields[_data.status].nodes.clone();
|
||||||
}
|
}
|
||||||
@ -622,15 +665,21 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (widget)
|
// WebComponent IS the node
|
||||||
|
else if(widget && typeof window.customElements.get(widget.localName) != "undefined")
|
||||||
|
{
|
||||||
|
nodes = widget.clone() // Note: Slower than cloneNode(), but simpler to deal with all the different widgets;
|
||||||
|
widget = nodes;
|
||||||
|
}
|
||||||
|
else if(widget)
|
||||||
{
|
{
|
||||||
nodes = et2_historylog.columns[i].nodes.clone();
|
nodes = et2_historylog.columns[i].nodes.clone();
|
||||||
}
|
}
|
||||||
else if ((
|
else if((
|
||||||
// Already parsed & cached
|
// Already parsed & cached
|
||||||
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] == "object" &&
|
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] == "object" &&
|
||||||
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] != "undefined" &&
|
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] != "undefined" &&
|
||||||
_data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] !== null) || // typeof null === 'object'
|
_data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] !== null) || // typeof null === 'object'
|
||||||
// Large old value
|
// Large old value
|
||||||
self._needsDiffWidget(_data['status'], _data[et2_historylog.columns[et2_historylog.OLD_VALUE].id]) ||
|
self._needsDiffWidget(_data['status'], _data[et2_historylog.columns[et2_historylog.OLD_VALUE].id]) ||
|
||||||
// Large new value
|
// Large new value
|
||||||
@ -667,7 +716,7 @@ export class et2_historylog extends et2_valueWidget implements et2_IDataProvider
|
|||||||
{
|
{
|
||||||
const id = widget._children[j].id;
|
const id = widget._children[j].id;
|
||||||
const widget_value = value ? value[id] || "" : "";
|
const widget_value = value ? value[id] || "" : "";
|
||||||
widget._children[j].setDetachedAttributes(nodes[j], {value:widget_value});
|
widget._children[j].setDetachedAttributes(nodes[j], {value: widget_value});
|
||||||
box.append(nodes[j]);
|
box.append(nodes[j]);
|
||||||
}
|
}
|
||||||
nodes = box;
|
nodes = box;
|
||||||
|
@ -367,11 +367,18 @@ class History
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is just here to hide bad values before we clean them with an update. If you're here, remove this IF block
|
// Properly format customfield date values
|
||||||
// Clear invalid share_email values
|
if($row['history_status'][0] == '#' && $cfs && array_key_exists(substr($row['history_status'], 1), $cfs) &&
|
||||||
if($row['share_email'] && stripos($row['share_email'], '@') === false)
|
in_array($cfs[substr($row['history_status'], 1)]['type'], ['date', 'date-time']))
|
||||||
{
|
{
|
||||||
$row['share_email'] = '';
|
if($row['history_new_value'])
|
||||||
|
{
|
||||||
|
$row['history_new_value'] = Api\DateTime::to($row['history_new_value'], Api\DateTime::ET2);
|
||||||
|
}
|
||||||
|
if($row['history_old_value'])
|
||||||
|
{
|
||||||
|
$row['history_old_value'] = Api\DateTime::to($row['history_old_value'], Api\DateTime::ET2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows[] = Api\Db::strip_array_keys($row, 'history_');
|
$rows[] = Api\Db::strip_array_keys($row, 'history_');
|
||||||
|
Loading…
Reference in New Issue
Block a user