diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php
index e9107dfd88..1023c2a3a1 100644
--- a/addressbook/inc/class.addressbook_ui.inc.php
+++ b/addressbook/inc/class.addressbook_ui.inc.php
@@ -2283,8 +2283,6 @@ class addressbook_ui extends addressbook_bo
$content['private'] = (int) ($content['owner'] && substr($content['owner'],-1) == 'p');
$content['owner'] = (string) (int) $content['owner'];
$content['cat_id'] = $this->config['cat_tab'] === 'Tree' ? $content['cat_id_tree'] : $content['cat_id'];
- if ($this->config['private_cf_tab']) $content = (array)$content['private_cfs'] + $content;
- unset($content['private_cfs']);
switch($button)
{
@@ -2655,16 +2653,6 @@ class addressbook_ui extends addressbook_bo
// Avoid ID conflict with tree & selectboxes
$content['cat_id_tree'] = $content['cat_id'];
- // Avoid setting conflicts with private custom fields
- $content['private_cfs'] = array();
- foreach(Api\Storage\Customfields::get('addressbook', true) as $name => $cf)
- {
- if ($this->config['private_cf_tab'] && $cf['private'] && isset($content['#'.$name]))
- {
- $content['private_cfs']['#'.$name] = $content['#'.$name];
- }
- }
-
// how to display addresses
$content['addr_format'] = $this->addr_format_by_country($content['adr_one_countryname']);
$content['addr_format2'] = $this->addr_format_by_country($content['adr_two_countryname']);
@@ -3234,8 +3222,6 @@ class addressbook_ui extends addressbook_bo
// disable not needed tabs
$readonlys['tabs']['cats'] = !($content['cat_tab'] = $this->config['cat_tab']);
- $readonlys['tabs']['custom'] = !$this->customfields;
- $readonlys['tabs']['custom_private'] = !$this->customfields || !$this->config['private_cf_tab'];
$readonlys['tabs']['distribution_list'] = !$content['distrib_lists'];#false;
$readonlys['tabs']['history'] = $this->contact_repository != 'sql' || !$content['id'] ||
$this->account_repository != 'sql' && $content['account_id'];
@@ -3385,10 +3371,6 @@ class addressbook_ui extends addressbook_bo
}
else
{
- if($this->config['private_cf_tab'])
- {
- $_content = array_merge($_content, $_content['private_cfs']);
- }
$query['advanced_search'] = array_intersect_key(
$_content,
array_flip(array_merge($this->get_contact_columns(), array('operator', 'meth_select')))
@@ -3466,15 +3448,6 @@ class addressbook_ui extends addressbook_bo
}
// Make them not required, otherwise you can't search
$this->tmpl->setElementAttribute('#' . $name, 'required', FALSE);
- if($this->config['private_cf_tab'] == 'True' && $data['private'])
- {
- if(isset($content['#' . $name]))
- {
- $content['private_cfs']['#' . $name] = $content['#' . $name];
- }
- // Private CF tab results in a different ID, turn required off there too
- $this->tmpl->setElementAttribute('private_cfs[#' . $name . ']', 'required', FALSE);
- }
}
}
// configure edit template as search dialog
@@ -3485,8 +3458,6 @@ class addressbook_ui extends addressbook_bo
$readonlys['button'] = false;
// disable not needed tabs
$readonlys['tabs']['cats'] = !($content['cat_tab'] = $this->config['cat_tab']);
- $readonlys['tabs']['custom'] = !$this->customfields;
- $readonlys['tabs']['custom_private'] = !$this->customfields || !$this->config['private_cf_tab'];
$readonlys['tabs']['links'] = true;
$readonlys['tabs']['distribution_list'] = true;
$readonlys['tabs']['history'] = true;
diff --git a/addressbook/templates/default/edit.xet b/addressbook/templates/default/edit.xet
index 7527f3d830..d30bf6e50f 100644
--- a/addressbook/templates/default/edit.xet
+++ b/addressbook/templates/default/edit.xet
@@ -380,16 +380,13 @@
-
+
-
-
-
+
@@ -398,8 +395,6 @@
-
-
diff --git a/addressbook/templates/mobile/edit.xet b/addressbook/templates/mobile/edit.xet
index 8b047fa35c..162c57ae38 100644
--- a/addressbook/templates/mobile/edit.xet
+++ b/addressbook/templates/mobile/edit.xet
@@ -261,36 +261,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -393,14 +363,13 @@
-
+
-
+
diff --git a/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts b/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts
index c80d89bad6..e9ccf473f3 100644
--- a/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts
+++ b/api/js/etemplate/Layout/Et2Tabs/Et2Tabs.ts
@@ -186,8 +186,20 @@ export class Et2Tabs extends Et2InputWidget(SlTabGroup) implements et2_IResizeab
{
let tab = this.extraTabs[i];
let tab_id = tab.id || tab.template;
- let tab_options = {id: tab_id, template: tab.template, url: tab.url, content: tab.content};
- tabData[tab.prepend ? 'unshift' : 'push'].call(tabData, {
+ let tab_options = {id: tab_id, template: tab.template, url: tab.url, content: tab.content, title: tab.statustext};
+ let pos = tabData.length;
+ if (typeof tab.prepend === "string")
+ {
+ if ((pos = tabData.findIndex(t => t.id === tab.prepend)) === -1)
+ {
+ pos = tabData.length;
+ }
+ }
+ else if (tab.prepend)
+ {
+ pos = 0;
+ }
+ tabData.splice(pos, 0, {
"id": tab_id,
"label": this.egw().lang(tab.label),
"widget": null,
@@ -543,10 +555,10 @@ export class Et2Tabs extends Et2InputWidget(SlTabGroup) implements et2_IResizeab
* get tab panel-name or label the given widget is in
*
* @param widget
- * @param label true: return label, otherwise return panel-name
+ * @param label true: return label, otherwise return panel-name / id
* @return string panel-name or undefined
*/
- static getTabPanel(widget, label)
+ static getTabPanel(widget, label? : boolean) : string|undefined
{
let tab = widget;
while(tab._parent && tab._parent.nodeName !== 'ET2-TABBOX')
diff --git a/api/js/etemplate/et2_extension_customfields.ts b/api/js/etemplate/et2_extension_customfields.ts
index 473c97d19e..bf573b4cd7 100644
--- a/api/js/etemplate/et2_extension_customfields.ts
+++ b/api/js/etemplate/et2_extension_customfields.ts
@@ -171,9 +171,17 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
}
// tab === "panel" --> use label of tab panel
+ const default_tab = Et2Tabs.getTabPanel(this)?.match(/^cf-default(-(non-)?private)?$/);
if (this.options.tab === 'panel')
{
- this.options.tab = Et2Tabs.getTabPanel(this, true);
+ if (default_tab)
+ {
+ this.options.tab = null;
+ }
+ else
+ {
+ this.options.tab = Et2Tabs.getTabPanel(this, true);
+ }
}
// filter fields additionally by tab attribute
if (typeof this.options.fields === "undefined" || !Object.keys(this.options.fields).length)
@@ -185,17 +193,39 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
{
this.options.fields[field_name] = true;
}
+ else if (default_tab)
+ {
+ if (this.options.customfields[field_name].private.length) // private cf
+ {
+ this.options.fields[field_name] = default_tab[1] !== '-non-private';
+ }
+ else // non-private cf
+ {
+ this.options.fields[field_name] = default_tab[1] !== '-private';
+ }
+ }
}
}
else
{
for(let field_name in this.options.customfields)
{
- if (this.options.customfields[field_name].tab !== this.options.tab)
+ if (default_tab ? this.options.customfields[field_name].tab : this.options.customfields[field_name].tab !== this.options.tab)
{
this.options.fields[field_name] = false;
}
- else if (this.options.tab)
+ else if (default_tab)
+ {
+ if (this.options.customfields[field_name].private.length) // private cf
+ {
+ this.options.fields[field_name] = default_tab[1] !== '-non-private';
+ }
+ else // non-private cf
+ {
+ this.options.fields[field_name] = default_tab[1] !== '-private';
+ }
+ }
+ else if (this.options.customfields[field_name].tab === this.options.tab)
{
this.options.fields[field_name] = true;
}
@@ -326,6 +356,7 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac
// Label in first column, widget in 2nd
const label = this.options.label || field.label || '';
jQuery(document.createElement("td"))
+ .attr('colspan', (attrs.type || field.type) === 'label' ? 2 : 1)
.prependTo(row);
et2_createWidget("label", {id: id + "_label", value: label.trim(), for: id}, this);
}
diff --git a/api/src/Etemplate/Widget/Tabbox.php b/api/src/Etemplate/Widget/Tabbox.php
index e8440b3924..48934a8307 100644
--- a/api/src/Etemplate/Widget/Tabbox.php
+++ b/api/src/Etemplate/Widget/Tabbox.php
@@ -20,15 +20,19 @@ use EGroupware\Api;
* eTemplate Tabs widget stacks multiple sub-templates and lets you switch between them
*
* Available attributes:
- * - add_tabs: true: tabs contain addtional tabs, false: tabs replace tabs in template
- * - tabs: array with (additional) tabs with values for following keys
+ * - addTabs: true: extraTabs contain additional tabs, false (default): tabs replace tabs in template
+ * - extraTabs: array with (additional) tabs with values for following keys
* + label: label of tab
* + template: template name with optional '?'.filemtime as cache-buster
* optional:
- * + prepend: true prepend tab to existing ones, false (default) append tabs
- * + hidden:
- * + id: optinal namespace (content attribute of template)
- * + add_tabs: true(default) add to given tabs to template, false replace tabs in template
+ * + prepend: true prepend tab to existing ones, false (default) append tabs or name of tab to prepend the tab
+ * + hidden: true: hide tab, false (default): show tab
+ * + id: id of tab
+ * + content: optional namespace (content attribute of template)
+ * + statustext: tooltip of label
+ * - cfTypeFilter: optional type-filter for automatic created custom-fields tabs
+ * - cfPrivateTab: true: create an extra tab for private custom-fields, false (default): show private ones together with non-private ones
+ * - cfPrepend: value for prepend tab-attribute for dynamic generated custom-field tabs, default "history"
*/
class Tabbox extends Etemplate\Widget
{
@@ -54,7 +58,7 @@ class Tabbox extends Etemplate\Widget
}
if($tabs && !$this->tabs_attr_evaluated)
{
- $this->tabs_attr_evaluated = true; // we must not evaluate tabs attribte more then once!
+ $this->tabs_attr_evaluated = true; // we must not evaluate tabs attribute more than once!
// add_tabs toggles replacing or adding to existing tabs
if(!($this->attrs['addTabs'] ?? $this->attrs['add_tabs']))
@@ -66,7 +70,6 @@ class Tabbox extends Etemplate\Widget
foreach($tabs as &$tab)
{
$template= clone Template::instance($tab['template']);
- if($tab['id']) $template->attrs['content'] = $tab['id'];
$this->children[1]->children[] = $template;
$tab['url'] = Template::rel2url($template->rel_path);
//$this->tabs[] = $tab;
@@ -140,12 +143,25 @@ class Tabbox extends Etemplate\Widget
public function beforeSendToClient($cname, array $expand=null)
{
[$app] = explode('.', self::$request->template['name']);
- if (empty($app) || !($cfs = Api\Storage\Customfields::get($app, false, null, null, true)))
+ // no need to run again for responses, or if we have no custom fields
+ if (!empty(self::$response) || empty($app) || !($cfs = Api\Storage\Customfields::get($app, false, null, null, true)))
{
return;
}
- $tabs = [];
- $content = self::$request->content;
+ $form_name = self::form_name($cname, $this->id, $expand);
+ $extra_private_tab = self::expand_name(self::getElementAttribute($form_name, 'cfPrivateTab') ?? $this->attrs['cfPrivateTab'] ?? false,
+ 0, 0, 0, 0, self::$request->content);
+ if (is_string($extra_private_tab) && $extra_private_tab[0] === '!')
+ {
+ $extra_private_tab = !substr($extra_private_tab, 1);
+ }
+
+ $prepend = $this->attrs['cfPrepend'] ?? 'history';
+
+ // check if template still contains a legacy customfield tab
+ $have_legacy_cf_tab = $this->haveLegacyCfTab();
+
+ $tabs = $private_tab = $default_tab = [];
foreach($cfs as $cf)
{
if (!empty($cf['tab']))
@@ -153,24 +169,81 @@ class Tabbox extends Etemplate\Widget
$tab = $tabs[$cf['tab']]['id'] ?? 'cf-tab'.(1+count($tabs));
if (!isset($tabs[$cf['tab']]))
{
- $tabs[$cf['tab']] = array(
+ $tabs[$cf['tab']] = [
'id' => $tab,
'template' => 'api.cf-tab',
'label' => $cf['tab'],
- );
+ 'prepend' => $prepend,
+ ];
}
}
- }
- if ($tabs)
- {
- self::$request->content = $content;
- self::setElementAttribute($this->id, 'addTabs', true);
- // if app already specifed extraTabs (like e.g. Addressbook), we need to add to them not overwrite them
- if (($extra_tabs = self::setElementAttribute($this->id, 'extraTabs', null)))
+ elseif ($have_legacy_cf_tab)
{
- $tabs = array_merge($extra_tabs, array_values($tabs));
+ continue;
}
- self::setElementAttribute($this->id, 'extraTabs', array_values($tabs));
+ // does app want an extra private cf tab
+ elseif (!empty($cf['private']) && $extra_private_tab)
+ {
+ if (!$private_tab)
+ {
+ $private_tab[] = [
+ 'id' => 'cf-default-private',
+ 'template' => 'api.cf-tab',
+ 'label' => 'Extra private',
+ 'statustext' => 'Private custom fields',
+ 'prepend' => $prepend,
+ ];
+ }
+ }
+ // default cf tab
+ elseif ((empty($cf['private']) || !$extra_private_tab && !empty($cf['private'])) && !$default_tab)
+ {
+ $default_tab[] = [
+ 'id' => $extra_private_tab ? 'cf-default-non-private' : 'cf-default',
+ 'template' => 'api.cf-tab',
+ 'label' => 'Custom fields',
+ 'prepend' => $prepend,
+ ];
+ }
+ }
+ if ($tabs || $default_tab || $private_tab)
+ {
+ // pass given cfTypeFilter attribute via content to all customfields widgets (set in api.cf-tab template)
+ if (($type_filter = self::getElementAttribute($form_name, 'cfTypeFilter') ?? $this->attrs['cfTypeFilter'] ?? null))
+ {
+ $content = self::$request->content;
+ $content['cfTypeFilter'] = self::expand_name($type_filter, 0, 0, 0, 0, $content);
+ self::$request->content = $content;
+ }
+
+ // addTabs is default false (= replace tabs), we need a default of true
+ $add_tabs =& self::setElementAttribute($this->id, 'addTabs', null);
+ if (!isset($add_tabs)) $add_tabs = true;
+
+ // if app already specified extraTabs (like e.g. Addressbook), we need to add to them not overwrite them
+ $extra_tabs =& self::setElementAttribute($this->id, 'extraTabs', null);
+ $extra_tabs = array_merge($extra_tabs ?? [], $default_tab, $private_tab, array_values($tabs));
+
+ // if we have no explicit default cf widget/tab, we need to call customfields::beforeSendToClient() to pass cfs to client-side
+ $cfs = new Customfields('');
+ $cfs->beforeSendToClient($cname, $expand);
}
}
+
+ /**
+ * Check if widget has a legacy custom-fields tab
+ *
+ * @return bool true: there is a tab named extra, custom or customfields
+ */
+ public function haveLegacyCfTab()
+ {
+ foreach($this->children[$this->children[0]->type === 'tabs' ? 0 : 1]->children as $tab)
+ {
+ if (preg_match('/(^|\.)(extra|custom|customfields)$/', $tab->id))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/api/templates/default/cf-tab.xet b/api/templates/default/cf-tab.xet
index 04b0c7b675..2173418971 100644
--- a/api/templates/default/cf-tab.xet
+++ b/api/templates/default/cf-tab.xet
@@ -2,6 +2,6 @@
-
+
\ No newline at end of file
diff --git a/calendar/templates/default/edit.xet b/calendar/templates/default/edit.xet
index 67464dac8f..d8528dfc9e 100644
--- a/calendar/templates/default/edit.xet
+++ b/calendar/templates/default/edit.xet
@@ -144,18 +144,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
@@ -229,7 +217,6 @@
-
@@ -237,7 +224,6 @@
-
diff --git a/calendar/templates/mobile/edit.xet b/calendar/templates/mobile/edit.xet
index 7d175a0870..12ddb7d794 100644
--- a/calendar/templates/mobile/edit.xet
+++ b/calendar/templates/mobile/edit.xet
@@ -139,9 +139,6 @@
-
-
-
@@ -249,7 +246,6 @@
-
@@ -258,7 +254,6 @@
-
diff --git a/infolog/templates/default/edit.xet b/infolog/templates/default/edit.xet
index 6a3d0c3264..6029835687 100644
--- a/infolog/templates/default/edit.xet
+++ b/infolog/templates/default/edit.xet
@@ -64,9 +64,6 @@
-
-
-
@@ -171,13 +168,12 @@
-
+
-
@@ -185,7 +181,6 @@
-
diff --git a/infolog/templates/mobile/edit.xet b/infolog/templates/mobile/edit.xet
index 398b563610..bd80f410bd 100644
--- a/infolog/templates/mobile/edit.xet
+++ b/infolog/templates/mobile/edit.xet
@@ -56,9 +56,6 @@
-
-
-
@@ -185,13 +182,12 @@
-
+
-
@@ -199,7 +195,6 @@
-
diff --git a/resources/templates/default/edit.xet b/resources/templates/default/edit.xet
index c98577008a..e29e085470 100644
--- a/resources/templates/default/edit.xet
+++ b/resources/templates/default/edit.xet
@@ -101,21 +101,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -156,19 +141,17 @@
-
+
-
-
diff --git a/resources/templates/mobile/edit.xet b/resources/templates/mobile/edit.xet
index f2c4c08add..206d565d0e 100644
--- a/resources/templates/mobile/edit.xet
+++ b/resources/templates/mobile/edit.xet
@@ -88,21 +88,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -170,20 +155,18 @@
-
+
-
-
diff --git a/timesheet/templates/default/edit.xet b/timesheet/templates/default/edit.xet
index 88afa975f4..0f872dde79 100644
--- a/timesheet/templates/default/edit.xet
+++ b/timesheet/templates/default/edit.xet
@@ -75,11 +75,6 @@
-
-
-
-
-
@@ -152,7 +147,6 @@
-
@@ -160,7 +154,6 @@
-
diff --git a/timesheet/templates/mobile/edit.xet b/timesheet/templates/mobile/edit.xet
index e08c05ee7f..f6e9f54f06 100644
--- a/timesheet/templates/mobile/edit.xet
+++ b/timesheet/templates/mobile/edit.xet
@@ -61,9 +61,6 @@
-
-
-
@@ -141,14 +138,12 @@
-
-