Merge branch 'master' into web-components

This commit is contained in:
nathan 2021-10-18 10:56:05 -06:00
commit fbbc466c78
101 changed files with 2673 additions and 2457 deletions

View File

@ -696,10 +696,16 @@ class addressbook_groupdav extends Api\CalDAV\Handler
{ {
trim($attribute); trim($attribute);
list($key, $value) = explode('=', $attribute); list($key, $value) = explode('=', $attribute);
// check if value is enclosed in quotes
if (in_array($value[0], ['"', "'"], true) && $value[0] === substr($value, -1))
{
$value = substr($value,1,-1);
}
switch (strtolower($key)) switch (strtolower($key))
{ {
case 'charset': case 'charset':
$charset = strtoupper(substr($value,1,-1)); $charset = strtoupper($value);
break;
} }
} }
} }

View File

@ -291,41 +291,8 @@ class addressbook_hooks
if ($GLOBALS['egw_info']['user']['apps']['filemanager']) if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{ {
$settings['default_document'] = array( $merge = new Api\Contacts\Merge();
'type' => 'vfs_file', $settings += $merge->merge_preferences();
'size' => 60,
'label' => 'Default document to insert contacts',
'name' => 'default_document',
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.', lang('addressbook')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','n_fn').' '.
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
);
$settings['document_dir'] = array(
'type' => 'vfs_dirs',
'size' => 60,
'label' => 'Directory with documents to insert contacts',
'name' => 'document_dir',
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.', lang('addressbook')) . ' ' .
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'n_fn') . ' ' .
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
'default' => '/templates/addressbook',
);
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
'type' => 'taglist',
'label' => 'Document download filename',
'name' => 'document_download_name',
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
'help' => 'Choose the default filename for downloaded documents.',
'xmlrpc' => True,
'admin' => False,
'default' => 'document',
);
} }
if ($GLOBALS['egw_info']['user']['apps']['felamimail'] || $GLOBALS['egw_info']['user']['apps']['mail']) if ($GLOBALS['egw_info']['user']['apps']['felamimail'] || $GLOBALS['egw_info']['user']['apps']['mail'])

View File

@ -273,7 +273,7 @@ class admin_acl
{ {
$rows['sel_options']['filter2'][] = array( $rows['sel_options']['filter2'][] = array(
'value' => $appname, 'value' => $appname,
'label' => lang(Api\Link::get_registry($appname, 'entries')) ?? lang($appname) 'label' => lang(Api\Link::get_registry($appname, 'entries') ?: $appname)
); );
} }
usort($rows['sel_options']['filter2'], function($a,$b) { usort($rows['sel_options']['filter2'], function($a,$b) {

View File

@ -87,7 +87,7 @@ class admin_cmd_category extends admin_cmd
unset($set['old_parent'], $set['base_url'], $set['last_mod'], $set['all_cats'], $set['no_private']); unset($set['old_parent'], $set['base_url'], $set['last_mod'], $set['all_cats'], $set['no_private']);
foreach($set as $key => $value) foreach($set as $key => $value)
{ {
if(array_key_exists($key, $old) && $old[$key] == $value) if ($old && array_key_exists($key, $old) && $old[$key] == $value)
{ {
unset($set[$key]); unset($set[$key]);
unset($old[$key]); unset($old[$key]);

View File

@ -128,10 +128,10 @@ class admin_customfields
public function index($content = array()) public function index($content = array())
{ {
// determine appname // determine appname
$this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['appname'] ? $content['appname'] : false)); $this->appname = $this->appname ?: (!empty($_GET['appname']) ? $_GET['appname'] : (!empty($content['appname']) ? $content['appname'] : false));
if(!$this->appname) die(lang('Error! No appname found')); if(!$this->appname) die(lang('Error! No appname found'));
$this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || $content['use_private']; $this->use_private = !empty($_GET['use_private']) && $_GET['use_private'] !== 'undefined' || !empty($content['use_private']);
// Read fields, constructor doesn't always know appname // Read fields, constructor doesn't always know appname
$this->fields = Api\Storage\Customfields::get($this->appname,true); $this->fields = Api\Storage\Customfields::get($this->appname,true);
@ -323,10 +323,10 @@ class admin_customfields
*/ */
function edit($content = null) function edit($content = null)
{ {
$cf_id = $_GET['cf_id'] ? (int)$_GET['cf_id'] : (int)$content['cf_id']; $cf_id = isset($_GET['cf_id']) ? (int)$_GET['cf_id'] : (int)$content['cf_id'];
// determine appname // determine appname
$this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['cf_app'] ? $content['cf_app'] : false)); $this->appname = $this->appname ?: (isset($_GET['appname']) ? $_GET['appname'] : (!empty($content['cf_app']) ? $content['cf_app'] : false));
if(!$this->appname) if(!$this->appname)
{ {
if($cf_id && $this->so) if($cf_id && $this->so)
@ -339,7 +339,7 @@ class admin_customfields
{ {
die(lang('Error! No appname found')); die(lang('Error! No appname found'));
} }
$this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || $content['use_private']; $this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || !empty($content['use_private']);
// Read fields, constructor doesn't always know appname // Read fields, constructor doesn't always know appname
$this->fields = Api\Storage\Customfields::get($this->appname,true); $this->fields = Api\Storage\Customfields::get($this->appname,true);
@ -347,7 +347,7 @@ class admin_customfields
// Update based on info returned from template // Update based on info returned from template
if (is_array($content)) if (is_array($content))
{ {
$action = @key($content['button']); $action = key($content['button'] ?? []);
switch($action) switch($action)
{ {
case 'delete': case 'delete':
@ -422,7 +422,7 @@ class admin_customfields
} }
else else
{ {
$content['use_private'] = !isset($_GET['use_private']) || (boolean)$_GET['use_private']; $content['use_private'] = !empty($_GET['use_private']) && $_GET['use_private'] !== 'undefined';
} }
@ -474,11 +474,11 @@ class admin_customfields
// Show sub-type row, and get types // Show sub-type row, and get types
if($this->manage_content_types) if($this->manage_content_types)
{ {
if(count($this->content_types) == 0) if(empty($this->content_types))
{ {
$this->content_types = Api\Config::get_content_types($this->appname); $this->content_types = Api\Config::get_content_types($this->appname);
} }
if (count($this->content_types)==0) if (empty($this->content_types))
{ {
// if you define your default types of your app with the search_link hook, they are available here, if no types were found // if you define your default types of your app with the search_link hook, they are available here, if no types were found
$this->content_types = (array)Api\Link::get_registry($this->appname, 'default_types'); $this->content_types = (array)Api\Link::get_registry($this->appname, 'default_types');
@ -592,7 +592,7 @@ class admin_customfields
*/ */
function create_field(&$content) function create_field(&$content)
{ {
$new_name = trim($content['fields'][count($content['fields'])-1]['name']); $new_name = trim($content['fields'][count((array)$content['fields'])-1]['name']);
if (empty($new_name) || isset($this->fields[$new_name])) if (empty($new_name) || isset($this->fields[$new_name]))
{ {
$content['error_msg'] .= empty($new_name) ? $content['error_msg'] .= empty($new_name) ?
@ -601,7 +601,7 @@ class admin_customfields
} }
else else
{ {
$this->fields[$new_name] = $content['fields'][count($content['fields'])-1]; $this->fields[$new_name] = $content['fields'][count((array)$content['fields'])-1];
if(!$this->fields[$new_name]['label']) $this->fields[$new_name]['label'] = $this->fields[$new_name]['name']; if(!$this->fields[$new_name]['label']) $this->fields[$new_name]['label'] = $this->fields[$new_name]['name'];
$this->save_repository(); $this->save_repository();
} }

View File

@ -1258,7 +1258,7 @@ class admin_mail
if ($content['ident_id'] != $content['old_ident_id'] && if ($content['ident_id'] != $content['old_ident_id'] &&
($content['old_ident_id'] || $content['ident_id'] != $content['std_ident_id'])) ($content['old_ident_id'] || $content['ident_id'] != $content['std_ident_id']))
{ {
if ($content['ident_id'] > 0) if ((int)$content['ident_id'] > 0)
{ {
$identity = Mail\Account::read_identity($content['ident_id'], false, $content['called_for']); $identity = Mail\Account::read_identity($content['ident_id'], false, $content['called_for']);
unset($identity['account_id']); unset($identity['account_id']);
@ -1285,7 +1285,7 @@ class admin_mail
{ {
$sel_options['ident_email_alias'] = array_merge( $sel_options['ident_email_alias'] = array_merge(
array('' => $content['mailLocalAddress'].' ('.lang('Default').')'), array('' => $content['mailLocalAddress'].' ('.lang('Default').')'),
array_combine($content['mailAlternateAddress'], $content['mailAlternateAddress'])); array_combine($content['mailAlternateAddress'] ?? [], $content['mailAlternateAddress'] ?? []));
// if admin explicitly set a non-alias, we need to add it to aliases to keep it after storing signature by user // if admin explicitly set a non-alias, we need to add it to aliases to keep it after storing signature by user
if ($content['ident_email'] !== $content['mailLocalAddress'] && !isset($sel_options['ident_email_alias'][$content['ident_email']])) if ($content['ident_email'] !== $content['mailLocalAddress'] && !isset($sel_options['ident_email_alias'][$content['ident_email']]))
{ {

View File

@ -44,7 +44,7 @@
</row> </row>
<row> <row>
<description value="Category owner" for="owner"/> <description value="Category owner" for="owner"/>
<taglist statustext="Limit global category to members of a certain group" id="owner" needed="1" height="190" class="et2_fullWidth" rows="4" /> <taglist-account statustext="Limit global category to members of a certain group" id="owner" needed="1" class="et2_fullWidth" />
</row> </row>
</rows> </rows>

View File

@ -227,8 +227,9 @@ export class et2_placeholder_select extends et2_inputWidget
app.onchange = (node, widget) => app.onchange = (node, widget) =>
{ {
preview.set_value(""); preview.set_value("");
if(['user'].indexOf(widget.get_value()) >= 0) if(['user', 'filemanager'].indexOf(widget.get_value()) >= 0)
{ {
// These ones don't let you select an entry for preview (they don't work)
entry.set_disabled(true); entry.set_disabled(true);
entry.app_select.val('user'); entry.app_select.val('user');
entry.set_value({app: 'user', id: '', query: ''}); entry.set_value({app: 'user', id: '', query: ''});
@ -338,7 +339,7 @@ export class et2_placeholder_select extends et2_inputWidget
{ {
continue; continue;
} }
options[key].push({ options[this.egw().lang(key)].push({
value: key + '-' + sub, value: key + '-' + sub,
label: this.egw().lang(sub) label: this.egw().lang(sub)
}); });

View File

@ -50,6 +50,22 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
"type": "string", "type": "string",
"default": "more", "default": "more",
"description": "Define a style for list header (more ...), which can get short 3dots with no caption or bigger button with caption more ..." "description": "Define a style for list header (more ...), which can get short 3dots with no caption or bigger button with caption more ..."
},
"preference_id": {
"name": "Preference id",
"type": "string",
"default": false,
"description": "Define a custom preference id for saving the toolbar preferences." +
"This is useful when you have the same toolbar and you use it in a pop up but also in a tab, which have different dom ids" +
"When not set it defaults to the dom id of the form."
},
"preference_app": {
"name": "Preference application",
"type": "string",
"default": false,
"description": "Define a custom preference application for saving the toolbar preferences." +
"This is useful when you have the same toolbar and you use it in a pop up but also in a tab, wich have different application names" +
"When not set it defaults to the result of this.egw().app_name();"
} }
}; };
@ -94,6 +110,13 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
// Set proper id and dom_id for the widget // Set proper id and dom_id for the widget
this.set_id(this.id); this.set_id(this.id);
if(!this.options.preference_id){
this.options.preference_id = this.dom_id;
}
if(!this.options.preference_app){
this.options.preference_app = this.egw().app_name();
}
this.actionbox = jQuery(document.createElement('div')) this.actionbox = jQuery(document.createElement('div'))
.addClass("et2_toolbar_more") .addClass("et2_toolbar_more")
@ -232,7 +255,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
{ {
this.actionbox.find('.toolbar-admin-pref').click(function(e){ this.actionbox.find('.toolbar-admin-pref').click(function(e){
e.stopImmediatePropagation(); e.stopImmediatePropagation();
egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_get_default_prefs', [egw.app_name(), that.dom_id], function(_prefs){ egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_get_default_prefs', [that.options.preference_app, that.options.preference_id], function(_prefs){
let prefs = []; let prefs = [];
for (let p in _prefs) for (let p in _prefs)
{ {
@ -242,7 +265,8 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
}).sendRequest(true); }).sendRequest(true);
}); });
} }
let pref = (!egwIsMobile())? egw.preference(this.dom_id, this.egw().app_name()): undefined;
let pref = (!egwIsMobile())? egw.preference(this.options.preference_id, this.options.preference_app): undefined;
if (pref && !jQuery.isArray(pref)) this.preference = pref; if (pref && !jQuery.isArray(pref)) this.preference = pref;
//Set the default actions for the first time //Set the default actions for the first time
@ -461,7 +485,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
if (that.actionlist.find(".ui-draggable").length == 0) if (that.actionlist.find(".ui-draggable").length == 0)
{ {
that.preference = {}; that.preference = {};
egw.set_preference(that.egw().app_name(),that.dom_id,that.preference); egw.set_preference(that.options.preference_app,that.options.preference_id,that.preference);
} }
}, },
tolerance:"touch" tolerance:"touch"
@ -525,7 +549,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
{ {
this.preference[_action] = _state; this.preference[_action] = _state;
if (egwIsMobile()) return; if (egwIsMobile()) return;
egw.set_preference(this.egw().app_name(),this.dom_id,this.preference); egw.set_preference(this.options.preference_app,this.options.preference_id,this.preference);
} }
/** /**
@ -773,7 +797,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
id:child, id:child,
value: child, value: child,
label: _actions[key]['children'][child]['caption'], label: _actions[key]['children'][child]['caption'],
app: egw.app_name(), app: self.options.preference_app,
icon: _actions[key]['children'][child]['iconUrl'] icon: _actions[key]['children'][child]['iconUrl']
}); });
} }
@ -784,7 +808,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
id:key, id:key,
value: key, value: key,
label: _actions[key]['caption'], label: _actions[key]['caption'],
app: egw.app_name(), app: self.options.preference_app,
icon: _actions[key]['iconUrl'] icon: _actions[key]['iconUrl']
}); });
} }
@ -808,12 +832,12 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
_value.actions = pref; _value.actions = pref;
} }
egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_setAdminSettings', egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_setAdminSettings',
[_value, self.dom_id, egw.app_name()],function(_result){ [_value, self.options.preference_id, self.options.preference_app],function(_result){
egw.message(_result); egw.message(_result);
}).sendRequest(true); }).sendRequest(true);
} }
}, },
title: egw.lang('admin settings for %1', this.dom_id), title: egw.lang('admin settings for %1', this.options.preference_id),
buttons: buttons, buttons: buttons,
minWidth: 600, minWidth: 600,
minHeight: 300, minHeight: 300,

View File

@ -744,20 +744,24 @@ export abstract class EgwApp
// Find what we need // Find what we need
let nm = null; let nm = null;
let action = _action; let action = _action;
let as_pdf = false; let as_pdf = null;
// Find Select all // Find Select all
while(nm == null && action != null) while(nm == null && action.parent != null)
{ {
if(action.data != null && action.data.nextmatch) if(action.data != null && action.data.nextmatch)
{ {
nm = action.data.nextmatch; nm = action.data.nextmatch;
} }
if(as_pdf === null && action.getActionById('as_pdf') !== null)
{
as_pdf = action.getActionById('as_pdf').checked;
}
action = action.parent; action = action.parent;
} }
let all = nm?.getSelection().all || false; let all = nm?.getSelection().all || false;
as_pdf = action.getActionById('as_pdf')?.checked || false; as_pdf = as_pdf || false;
// Get list of entry IDs // Get list of entry IDs
let ids = []; let ids = [];

View File

@ -274,6 +274,7 @@ choose a background style. common de Wählen Sie einen Hintergrundstil.
choose a text color for the icons common de Wählen Sie eine Textfarbe für die Symbole choose a text color for the icons common de Wählen Sie eine Textfarbe für die Symbole
choose file... common de Dateien wählen... choose file... common de Dateien wählen...
choose the category common de Kategorie auswählen choose the category common de Kategorie auswählen
choose the default filename for merged documents. preferences de Wählen Sie den Standard-Dateinamen für zusammengeführte Platzhalter-Dokumente.
choose the parent category common de Wählen der übergeordneten Kategorie choose the parent category common de Wählen der übergeordneten Kategorie
choose time common de Uhrzeit auswählen choose time common de Uhrzeit auswählen
chosen parent category no longer exists common de Die ausgewählte Elternkategorie existiert nicht (mehr). chosen parent category no longer exists common de Die ausgewählte Elternkategorie existiert nicht (mehr).
@ -378,6 +379,7 @@ december common de Dezember
deck common de Deck (intern) deck common de Deck (intern)
default common de Vorgabe default common de Vorgabe
default category common de Standard-Kategorie default category common de Standard-Kategorie
default document to insert entries preferences de Standarddokument für Einfügen in Dokument
default height for the windows common de Vorgabewert für Höhe des Fensters default height for the windows common de Vorgabewert für Höhe des Fensters
default visible actions common de standardmäßig sichtbare Aktionen default visible actions common de standardmäßig sichtbare Aktionen
default width for the windows common de Vorgabewert für Breite des Fensters default width for the windows common de Vorgabewert für Breite des Fensters
@ -417,6 +419,8 @@ diable the execution a bugfixscript for internet explorer 5.5 and higher to show
direction left to right common de Richtung von links nach rechts direction left to right common de Richtung von links nach rechts
directory common de Verzeichnis directory common de Verzeichnis
directory does not exist, is not readable by the webserver or is not relative to the document root! common de Verzeichnis existiert nicht, ist nicht vom Webserver lesbar oder ist nicht entsprechend zur Dokumentroot! directory does not exist, is not readable by the webserver or is not relative to the document root! common de Verzeichnis existiert nicht, ist nicht vom Webserver lesbar oder ist nicht entsprechend zur Dokumentroot!
directory for storing merged documents preferences de Verzeichnis für zusammengeführte Platzhalter-Dokumente
directory with documents to insert entries preferences de Vorlagen-Verzeichnis für Einfügen in Dokument
disable internet explorer png-image-bugfix common de Internet Explorer PNG-Bilder-Bugfix abschalten disable internet explorer png-image-bugfix common de Internet Explorer PNG-Bilder-Bugfix abschalten
disable slider effects common de Schwebeeffekte des Navigationsmenüs abschalten disable slider effects common de Schwebeeffekte des Navigationsmenüs abschalten
disable the animated slider effects when showing or hiding menus in the page? opera and konqueror users will probably must want this. common de Die animierten Schwebeeffekte beim Anzeigen oder Verstecken des Navigationsmenüs in der Seite abschalten? Benutzer von Opera oder Konquerer müssen diese Funktion abschalten. disable the animated slider effects when showing or hiding menus in the page? opera and konqueror users will probably must want this. common de Die animierten Schwebeeffekte beim Anzeigen oder Verstecken des Navigationsmenüs in der Seite abschalten? Benutzer von Opera oder Konquerer müssen diese Funktion abschalten.
@ -1500,6 +1504,7 @@ western sahara common de WEST SAHARA
what color should all the blank space on the desktop have common de Welche Farbe soll der freie Platz auf der Arbeitsfläche haben what color should all the blank space on the desktop have common de Welche Farbe soll der freie Platz auf der Arbeitsfläche haben
what happens with overflowing content: visible (default), hidden, scroll, auto (browser decides) common de was passiert mit überbreitem Inhalt: sichtbar (standard), versteckt, rollend, automatisch (der Browser entscheidet) what happens with overflowing content: visible (default), hidden, scroll, auto (browser decides) common de was passiert mit überbreitem Inhalt: sichtbar (standard), versteckt, rollend, automatisch (der Browser entscheidet)
what style would you like the image to have? common de Welchen Stil soll das Bild haben? what style would you like the image to have? common de Welchen Stil soll das Bild haben?
when you merge entries into documents, they will be stored here. If no directory is provided, they will be stored in your home directory (%1) preferences de Wenn Sie Einträge mit Platzhalter-Dokumenten zusammenführen, werden diese hier gespeichert. Wenn Sie kein Verzeichnis angeben, werden diese in Ihrem Homeverzeichnis gespeichert (%1)
when you say yes the home and logout buttons are presented as applications in the main top applcation bar. common de Wenn Sie dies aktivieren, werden die Start und Abmelde Symbole als Anwendungen im oberen Anwendungsbalken angezeigt. when you say yes the home and logout buttons are presented as applications in the main top applcation bar. common de Wenn Sie dies aktivieren, werden die Start und Abmelde Symbole als Anwendungen im oberen Anwendungsbalken angezeigt.
where and how will the egroupware links like preferences, about and logout be displayed. common de Wo und wie werden die EGroupware Verknüpfungen wie Einstellungen, Über ..., und Abmelden angezeigt. where and how will the egroupware links like preferences, about and logout be displayed. common de Wo und wie werden die EGroupware Verknüpfungen wie Einstellungen, Über ..., und Abmelden angezeigt.
which groups common de Welche Gruppen which groups common de Welche Gruppen

View File

@ -274,6 +274,7 @@ choose a background style. common en Choose a background style
choose a text color for the icons common en Choose a text color for the icons choose a text color for the icons common en Choose a text color for the icons
choose file... common en Choose file... choose file... common en Choose file...
choose the category common en Choose the category choose the category common en Choose the category
choose the default filename for merged documents. preferences en Choose the default filename for merged documents.
choose the parent category common en Choose the parent category choose the parent category common en Choose the parent category
choose time common en Choose Time choose time common en Choose Time
chosen parent category no longer exists common en Chosen parent category no longer exists chosen parent category no longer exists common en Chosen parent category no longer exists
@ -378,6 +379,7 @@ december common en December
deck common en Deck (internal) deck common en Deck (internal)
default common en Default default common en Default
default category common en Default category default category common en Default category
default document to insert entries preferences en Default document to insert entries
default height for the windows common en Default height for the windows default height for the windows common en Default height for the windows
default visible actions common en Default visible actions default visible actions common en Default visible actions
default width for the windows common en Default width for the windows default width for the windows common en Default width for the windows
@ -417,6 +419,8 @@ diable the execution a bugfixscript for internet explorer 5.5 and higher to show
direction left to right common en Direction left to right direction left to right common en Direction left to right
directory common en Directory directory common en Directory
directory does not exist, is not readable by the webserver or is not relative to the document root! common en Directory does not exist, is not readable by the web server or is not relative to the document root! directory does not exist, is not readable by the webserver or is not relative to the document root! common en Directory does not exist, is not readable by the web server or is not relative to the document root!
directory for storing merged documents preferences en Directory for storing merged documents
directory with documents to insert entries preferences en Directory with documents to insert entries
disable internet explorer png-image-bugfix common en Disable Internet Explorer png image bugfix disable internet explorer png-image-bugfix common en Disable Internet Explorer png image bugfix
disable slider effects common en Disable slider effects disable slider effects common en Disable slider effects
disable the animated slider effects when showing or hiding menus in the page? opera and konqueror users will probably must want this. common en Disable the animated slider effects when showing or hiding menus in the page. disable the animated slider effects when showing or hiding menus in the page? opera and konqueror users will probably must want this. common en Disable the animated slider effects when showing or hiding menus in the page.
@ -857,6 +861,7 @@ maybe common en Maybe
mayotte common en MAYOTTE mayotte common en MAYOTTE
medium common en Medium medium common en Medium
menu common en Menu menu common en Menu
merged document filename preferences en Merged document filename
message common en Message message common en Message
message ... common en Message ... message ... common en Message ...
message prepared for sending. common en Message prepared for sending. message prepared for sending. common en Message prepared for sending.
@ -1501,6 +1506,7 @@ western sahara common en WESTERN SAHARA
what color should all the blank space on the desktop have common en What color should all the blank space on the desktop have? what color should all the blank space on the desktop have common en What color should all the blank space on the desktop have?
what happens with overflowing content: visible (default), hidden, scroll, auto (browser decides) common en What happens with overflowing content: visible (default), hidden, scroll, auto (browser decides) what happens with overflowing content: visible (default), hidden, scroll, auto (browser decides) common en What happens with overflowing content: visible (default), hidden, scroll, auto (browser decides)
what style would you like the image to have? common en Image style what style would you like the image to have? common en Image style
when you merge entries into documents, they will be stored here. If no directory is provided, they will be stored in your home directory (%1) preferences en When you merge entries into documents, they will be stored here. If no directory is provided, they will be stored in your home directory (%1)
when you say yes the home and logout buttons are presented as applications in the main top applcation bar. common en If you say yes, the Home and Log out buttons are presented as applications in the main top application bar. when you say yes the home and logout buttons are presented as applications in the main top applcation bar. common en If you say yes, the Home and Log out buttons are presented as applications in the main top application bar.
where and how will the egroupware links like preferences, about and logout be displayed. common en Where and how will the EGroupware links like Preferences, About and Log out be displayed. where and how will the egroupware links like preferences, about and logout be displayed. common en Where and how will the EGroupware links like Preferences, About and Log out be displayed.
which groups common en Which groups which groups common en Which groups

View File

@ -468,7 +468,7 @@ class Accounts
$data = self::cache_read($id); $data = self::cache_read($id);
// add default description for Admins and Default group // add default description for Admins and Default group
if ($data['account_type'] === 'g') if ($data && $data['account_type'] === 'g')
{ {
self::add_default_group_description($data); self::add_default_group_description($data);
} }
@ -595,11 +595,15 @@ class Accounts
/** /**
* Return formatted username for a given account_id * Return formatted username for a given account_id
* *
* @param int $account_id account id * @param ?int $account_id account id, default current user
* @return string full name of user or "#$account_id" if user not found * @return string full name of user or "#$account_id" if user not found
*/ */
static function username(int $account_id) static function username(int $account_id=null)
{ {
if (empty($account_id))
{
$account_id = $GLOBALS['egw_info']['user']['account_id'];
}
if (!($account = self::cache_read($account_id))) if (!($account = self::cache_read($account_id)))
{ {
return '#'.$account_id; return '#'.$account_id;
@ -985,7 +989,7 @@ class Accounts
$ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : $data['memberships']; $ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : $data['memberships'];
} }
//error_log(__METHOD__."($account_id, $just_id) data=".array2string($data)." returning ".array2string($ret)); //error_log(__METHOD__."($account_id, $just_id) data=".array2string($data)." returning ".array2string($ret));
return $ret; return $ret ?? [];
} }
/** /**

View File

@ -15,8 +15,7 @@
namespace EGroupware\Api; namespace EGroupware\Api;
// allow to set an application depending authentication type (eg. for syncml, groupdav, ...) // allow to set an application depending authentication type (eg. for syncml, groupdav, ...)
if (isset($GLOBALS['egw_info']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']]) && if (!empty($GLOBALS['egw_info']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']]))
$GLOBALS['egw_info']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']])
{ {
$GLOBALS['egw_info']['server']['auth_type'] = $GLOBALS['egw_info']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']]; $GLOBALS['egw_info']['server']['auth_type'] = $GLOBALS['egw_info']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']];
} }
@ -223,11 +222,11 @@ class Auth
{ {
return true; return true;
} }
if (is_null($passwordAgeBorder) && $GLOBALS['egw_info']['server']['change_pwd_every_x_days']) if (is_null($passwordAgeBorder) && !empty($GLOBALS['egw_info']['server']['change_pwd_every_x_days']))
{ {
$passwordAgeBorder = (DateTime::to('now','ts')-($GLOBALS['egw_info']['server']['change_pwd_every_x_days']*86400)); $passwordAgeBorder = (DateTime::to('now','ts')-($GLOBALS['egw_info']['server']['change_pwd_every_x_days']*86400));
} }
if (is_null($daysLeftUntilChangeReq) && $GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change']) if (is_null($daysLeftUntilChangeReq) && !empty($GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change']))
{ {
// maxage - passwordage = days left until change is required // maxage - passwordage = days left until change is required
$daysLeftUntilChangeReq = ($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] - ((DateTime::to('now','ts')-($alpwchange_val?$alpwchange_val:0))/86400)); $daysLeftUntilChangeReq = ($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] - ((DateTime::to('now','ts')-($alpwchange_val?$alpwchange_val:0))/86400));
@ -235,9 +234,9 @@ class Auth
if ($alpwchange_val == 0 || // admin requested password change if ($alpwchange_val == 0 || // admin requested password change
$passwordAgeBorder > $alpwchange_val || // change password every N days policy requests change $passwordAgeBorder > $alpwchange_val || // change password every N days policy requests change
// user should be warned N days in advance about change and is not yet // user should be warned N days in advance about change and is not yet
$GLOBALS['egw_info']['server']['change_pwd_every_x_days'] && !empty($GLOBALS['egw_info']['server']['change_pwd_every_x_days']) &&
$GLOBALS['egw_info']['user']['apps']['preferences'] && !empty($GLOBALS['egw_info']['user']['apps']['preferences']) &&
$GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change'] && !empty($GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change']) &&
$GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change'] > $daysLeftUntilChangeReq && $GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change'] > $daysLeftUntilChangeReq &&
$UserKnowsAboutPwdChange !== true) $UserKnowsAboutPwdChange !== true)
{ {
@ -255,8 +254,8 @@ class Auth
else else
{ {
// login page does not inform user about passwords about to expire // login page does not inform user about passwords about to expire
if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' && if ($GLOBALS['egw_info']['flags']['currentapp'] !== 'login' &&
($GLOBALS['egw_info']['flags']['currentapp'] != 'home' || ($GLOBALS['egw_info']['flags']['currentapp'] !== 'home' ||
strpos($_SERVER['SCRIPT_NAME'], '/home/') !== false)) strpos($_SERVER['SCRIPT_NAME'], '/home/') !== false))
{ {
$UserKnowsAboutPwdChange = true; $UserKnowsAboutPwdChange = true;

View File

@ -853,7 +853,7 @@ class Categories
if (is_null(self::$cache)) self::init_cache(); if (is_null(self::$cache)) self::init_cache();
$cat = self::$cache[$cat_id]; $cat = self::$cache[$cat_id] ?? null;
if ($item == 'path') if ($item == 'path')
{ {
if ($cat['parent']) if ($cat['parent'])
@ -864,7 +864,7 @@ class Categories
} }
if ($item == 'data') if ($item == 'data')
{ {
return $cat['data'] ? json_php_unserialize($cat['data'], true) : array(); return !empty($cat['data']) ? json_php_unserialize($cat['data'], true) : array();
} }
elseif ($cat[$item]) elseif ($cat[$item])
{ {

View File

@ -210,7 +210,7 @@ class Config
{ {
self::init_static(); self::init_static();
} }
return (array)self::$configs[$app]; return self::$configs[$app] ?? [];
} }
/** /**
@ -238,7 +238,7 @@ class Config
{ {
$config = self::read($app); $config = self::read($app);
return is_array($config['types']) ? $config['types'] : array(); return !empty($config['types']) && is_array($config['types']) ? $config['types'] : [];
} }
/** /**

View File

@ -202,9 +202,9 @@ class Contacts extends Contacts\Storage
$this->prefs['hide_accounts'] = '0'; $this->prefs['hide_accounts'] = '0';
} }
// get the default addressbook from the users prefs // get the default addressbook from the users prefs
$this->default_addressbook = $GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] ? $this->default_addressbook = !empty($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default']) ?
(int)$GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] : $this->user; (int)$GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] : $this->user;
$this->default_private = substr($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'],-1) == 'p'; $this->default_private = substr($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] ?? '',-1) == 'p';
if ($this->default_addressbook > 0 && $this->default_addressbook != $this->user && if ($this->default_addressbook > 0 && $this->default_addressbook != $this->user &&
($this->default_private || ($this->default_private ||
$this->default_addressbook == (int)$GLOBALS['egw']->preferences->forced['addressbook']['add_default'] || $this->default_addressbook == (int)$GLOBALS['egw']->preferences->forced['addressbook']['add_default'] ||
@ -312,14 +312,14 @@ class Contacts extends Contacts\Storage
'adr_two_countryname' => lang('country').' ('.lang('business').')', 'adr_two_countryname' => lang('country').' ('.lang('business').')',
); );
//_debug_array($this->contact_fields); //_debug_array($this->contact_fields);
$this->own_account_acl = $GLOBALS['egw_info']['server']['own_account_acl']; $this->own_account_acl = $GLOBALS['egw_info']['server']['own_account_acl'] ?? null;
if (!is_array($this->own_account_acl)) $this->own_account_acl = json_php_unserialize($this->own_account_acl, true); if (!is_array($this->own_account_acl)) $this->own_account_acl = json_php_unserialize($this->own_account_acl, true);
// we have only one acl (n_fn) for the whole name, as not all backends store every part in an own field // we have only one acl (n_fn) for the whole name, as not all backends store every part in an own field
if ($this->own_account_acl && in_array('n_fn',$this->own_account_acl)) if ($this->own_account_acl && in_array('n_fn',$this->own_account_acl))
{ {
$this->own_account_acl = array_merge($this->own_account_acl,array('n_prefix','n_given','n_middle','n_family','n_suffix')); $this->own_account_acl = array_merge($this->own_account_acl,array('n_prefix','n_given','n_middle','n_family','n_suffix'));
} }
if ($GLOBALS['egw_info']['server']['org_fileds_to_update']) if (!empty($GLOBALS['egw_info']['server']['org_fileds_to_update']))
{ {
$this->org_fields = $GLOBALS['egw_info']['server']['org_fileds_to_update']; $this->org_fields = $GLOBALS['egw_info']['server']['org_fileds_to_update'];
if (!is_array($this->org_fields)) $this->org_fields = unserialize($this->org_fields); if (!is_array($this->org_fields)) $this->org_fields = unserialize($this->org_fields);
@ -337,7 +337,7 @@ class Contacts extends Contacts\Storage
} }
$this->categories = new Categories($this->user,'addressbook'); $this->categories = new Categories($this->user,'addressbook');
$this->delete_history = $GLOBALS['egw_info']['server']['history']; $this->delete_history = $GLOBALS['egw_info']['server']['history'] ?? null;
} }
/** /**

View File

@ -155,119 +155,6 @@ class Merge extends Api\Storage\Merge
return $replacements; return $replacements;
} }
/**
* Generate table with replacements for the preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('Addressbook').' - '.lang('Replacements for inserting contacts into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = (bool)$_GET['nonavbar'];
ob_start();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Contact fields:')."</h3></td></tr>";
$n = 0;
foreach($this->contacts->contact_fields as $name => $label)
{
if (in_array($name,array('tid','label','geo'))) continue; // dont show them, as they are not used in the UI atm.
if (in_array($name,array('email','org_name','tel_work','url')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.$label.'</td>';
if($name == 'cat_id')
{
if ($n&1) echo "</tr>\n";
echo '<td>{{categories}}</td><td>'.lang('Category path').'</td>';
$n++;
}
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->contacts->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
foreach(array(
'link' => lang('HTML link to the current record'),
'links' => lang('Titles of any entries linked to the current record, excluding attached files'),
'attachments' => lang('List of files linked to the current record'),
'links_attachments' => lang('Links and attached files'),
'links/[appname]' => lang('Links to specified application. Example: {{links/infolog}}'),
'date' => lang('Date'),
'user/n_fn' => lang('Name of current user, all other contact fields are valid too'),
'user/account_lid' => lang('Username'),
'pagerepeat' => lang('For serial letter use this tag. Put the content, you want to repeat between two Tags.'),
'label' => lang('Use this tag for addresslabels. Put the content, you want to repeat, between two tags.'),
'labelplacement' => lang('Tag to mark positions for address labels'),
'IF fieldname' => lang('Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms.'),
'NELF' => lang('Example {{NELF role}} - if field role is not empty, you will get a new line with the value of field role'),
'NENVLF' => lang('Example {{NENVLF role}} - if field role is not empty, set a LF without any value of the field'),
'LETTERPREFIX' => lang('Example {{LETTERPREFIX}} - Gives a letter prefix without double spaces, if the title is empty for example'),
'LETTERPREFIXCUSTOM' => lang('Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller'),
) as $name => $label)
{
echo '<tr><td>{{'.$name.'}}</td><td colspan="3">'.$label."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('EPL Only').":</h3></td></tr>";
echo '<tr><td>{{share}}</td><td colspan="3">'.lang('Public sharing URL')."</td></tr>\n";
Api\Translation::add_app('calendar');
echo '<tr><td colspan="4"><h3>'.lang('Calendar fields:')." # = 1, 2, ..., 20, -1</h3></td></tr>";
foreach(array(
'title' => lang('Title'),
'description' => lang('Description'),
'participants' => lang('Participants'),
'location' => lang('Location'),
'start' => lang('Start').': '.lang('Date').'+'.lang('Time'),
'startday' => lang('Start').': '.lang('Weekday'),
'startdate'=> lang('Start').': '.lang('Date'),
'starttime'=> lang('Start').': '.lang('Time'),
'end' => lang('End').': '.lang('Date').'+'.lang('Time'),
'endday' => lang('End').': '.lang('Weekday'),
'enddate' => lang('End').': '.lang('Date'),
'endtime' => lang('End').': '.lang('Time'),
'duration' => lang('Duration'),
'category' => lang('Category'),
'priority' => lang('Priority'),
'updated' => lang('Updated'),
'recur_type' => lang('Repetition'),
'access' => lang('Access').': '.lang('public').', '.lang('private'),
'owner' => lang('Owner'),
) as $name => $label)
{
if(in_array($name, array('start',
'end')) && $n & 1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if(!($n & 1))
{
echo '<tr>';
}
echo '<td>{{calendar/#/' . $name . '}}</td><td>' . $label . '</td>';
if($n & 1)
{
echo "</tr>\n";
}
$n++;
}
echo "</table>\n";
$GLOBALS['egw']->framework->render(ob_get_clean());
}
/** /**
* Get a list of placeholders provided. * Get a list of placeholders provided.
* *
@ -352,10 +239,58 @@ class Merge extends Api\Storage\Merge
'label' => "Formatted private address" 'label' => "Formatted private address"
]; ];
$placeholders['EPL only'][] = [
'value' => $this->prefix($prefix, 'share', '{'),
'label' => 'Public sharing URL'
];
$this->add_customfield_placeholders($placeholders, $prefix); $this->add_customfield_placeholders($placeholders, $prefix);
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
$this->add_calendar_placeholders($placeholders, $prefix);
}
return $placeholders; return $placeholders;
} }
protected function add_calendar_placeholders(&$placeholders, $prefix)
{
Api\Translation::add_app('calendar');
// NB: The -1 is actually 1, a non-breaking hyphen to avoid UI issues where we split on -
$group = lang('Calendar fields:') . " # = 1, 2, ..., 20, 1";
foreach(array(
'title' => lang('Title'),
'description' => lang('Description'),
'participants' => lang('Participants'),
'location' => lang('Location'),
'start' => lang('Start') . ': ' . lang('Date') . '+' . lang('Time'),
'startday' => lang('Start') . ': ' . lang('Weekday'),
'startdate' => lang('Start') . ': ' . lang('Date'),
'starttime' => lang('Start') . ': ' . lang('Time'),
'end' => lang('End') . ': ' . lang('Date') . '+' . lang('Time'),
'endday' => lang('End') . ': ' . lang('Weekday'),
'enddate' => lang('End') . ': ' . lang('Date'),
'endtime' => lang('End') . ': ' . lang('Time'),
'duration' => lang('Duration'),
'category' => lang('Category'),
'priority' => lang('Priority'),
'updated' => lang('Updated'),
'recur_type' => lang('Repetition'),
'access' => lang('Access') . ': ' . lang('public') . ', ' . lang('private'),
'owner' => lang('Owner'),
) as $name => $label)
{
$placeholders[$group][] = array(
'value' => $this->prefix(($prefix ? $prefix . '/' : '') . 'calendar/#', $name, '{'),
'label' => $label
);
}
}
/** /**
* Get insert-in-document action with optional default document on top * Get insert-in-document action with optional default document on top
* *

View File

@ -77,15 +77,15 @@ class Sql extends Api\Storage
// Get custom fields from addressbook instead of api // Get custom fields from addressbook instead of api
$this->customfields = Api\Storage\Customfields::get('addressbook'); $this->customfields = Api\Storage\Customfields::get('addressbook');
if ($GLOBALS['egw_info']['server']['account_repository']) if (!empty($GLOBALS['egw_info']['server']['account_repository']))
{ {
$this->account_repository = $GLOBALS['egw_info']['server']['account_repository']; $this->account_repository = $GLOBALS['egw_info']['server']['account_repository'];
} }
elseif ($GLOBALS['egw_info']['server']['auth_type']) elseif (!empty($GLOBALS['egw_info']['server']['auth_type']))
{ {
$this->account_repository = $GLOBALS['egw_info']['server']['auth_type']; $this->account_repository = $GLOBALS['egw_info']['server']['auth_type'];
} }
if ($GLOBALS['egw_info']['server']['contact_repository']) if (!empty($GLOBALS['egw_info']['server']['contact_repository']))
{ {
$this->contact_repository = $GLOBALS['egw_info']['server']['contact_repository']; $this->contact_repository = $GLOBALS['egw_info']['server']['contact_repository'];
} }
@ -742,7 +742,7 @@ class Sql extends Api\Storage
$cat_filter = array(); $cat_filter = array();
foreach(is_array($cats) ? $cats : (is_numeric($cats) ? array($cats) : explode(',',$cats)) as $cat) foreach(is_array($cats) ? $cats : (is_numeric($cats) ? array($cats) : explode(',',$cats)) as $cat)
{ {
if (is_numeric($cat)) $cat_filter[] = $this->db->concat("','",cat_id,"','")." LIKE '%,$cat,%'"; if (is_numeric($cat)) $cat_filter[] = $this->db->concat("','", 'cat_id', "','")." LIKE '%,$cat,%'";
} }
return $cat_filter; return $cat_filter;
} }

View File

@ -256,7 +256,7 @@ class Storage
} }
$this->customfields = Api\Storage\Customfields::get('addressbook'); $this->customfields = Api\Storage\Customfields::get('addressbook');
// contacts backend (contacts in LDAP require accounts in LDAP!) // contacts backend (contacts in LDAP require accounts in LDAP!)
if($GLOBALS['egw_info']['server']['contact_repository'] == 'ldap' && $this->account_repository == 'ldap') if (($GLOBALS['egw_info']['server']['contact_repository']??null) === 'ldap' && $this->account_repository === 'ldap')
{ {
$this->contact_repository = 'ldap'; $this->contact_repository = 'ldap';
$this->somain = new Ldap(); $this->somain = new Ldap();
@ -264,7 +264,7 @@ class Storage
} }
else // sql or sql->ldap else // sql or sql->ldap
{ {
if ($GLOBALS['egw_info']['server']['contact_repository'] == 'sql-ldap') if (($GLOBALS['egw_info']['server']['contact_repository']??null) === 'sql-ldap')
{ {
$this->contact_repository = 'sql-ldap'; $this->contact_repository = 'sql-ldap';
} }
@ -347,9 +347,9 @@ class Storage
if ($user) if ($user)
{ {
// contacts backend (contacts in LDAP require accounts in LDAP!) // contacts backend (contacts in LDAP require accounts in LDAP!)
if($GLOBALS['egw_info']['server']['contact_repository'] == 'ldap' && $this->account_repository == 'ldap') if(($GLOBALS['egw_info']['server']['contact_repository']??null) === 'ldap' && $this->account_repository === 'ldap')
{ {
// static grants from ldap: all rights for the own personal addressbook and the group ones of the meberships // static grants from ldap: all rights for the own personal addressbook and the group ones of the memberships
$grants = array($user => ~0); $grants = array($user => ~0);
foreach($GLOBALS['egw']->accounts->memberships($user,true) as $gid) foreach($GLOBALS['egw']->accounts->memberships($user,true) as $gid)
{ {
@ -415,9 +415,9 @@ class Storage
*/ */
function allow_account_edit($user=null) function allow_account_edit($user=null)
{ {
return $GLOBALS['egw_info']['server']['allow_account_edit'] && return !empty($GLOBALS['egw_info']['server']['allow_account_edit']) &&
array_intersect($GLOBALS['egw_info']['server']['allow_account_edit'], array_intersect($GLOBALS['egw_info']['server']['allow_account_edit'],
$GLOBALS['egw']->accounts->memberships($user ? $user : $this->user, true)); $GLOBALS['egw']->accounts->memberships($user ?: $this->user, true));
} }
/** /**

View File

@ -591,7 +591,7 @@ class Db
$this->setupType = $this->Type; $this->setupType = $this->Type;
$this->Type = 'mysql'; $this->Type = 'mysql';
} }
if ($new_connection) if (!empty($new_connection))
{ {
foreach(get_included_files() as $file) foreach(get_included_files() as $file)
{ {
@ -1599,7 +1599,7 @@ class Db
{ {
return $array; return $array;
} }
if (!$column_definitions) if (empty($column_definitions))
{ {
$column_definitions = $this->column_definitions; $column_definitions = $this->column_definitions;
} }

View File

@ -125,7 +125,7 @@ class Pdo
// Exception reveals password, so we ignore the exception and connect again without pw, to get the right exception without pw // Exception reveals password, so we ignore the exception and connect again without pw, to get the right exception without pw
self::$pdo = new \PDO($dsn,$egw_db->User,'$egw_db->Password'); self::$pdo = new \PDO($dsn,$egw_db->User,'$egw_db->Password');
} }
if ($query) if (!empty($query))
{ {
self::$pdo->exec($query); self::$pdo->exec($query);
} }

View File

@ -117,22 +117,22 @@ class Etemplate extends Etemplate\Widget\Template
foreach(count(array_filter(array_keys($extras), 'is_int')) ? $extras : array($extras) as $extra) foreach(count(array_filter(array_keys($extras), 'is_int')) ? $extras : array($extras) as $extra)
{ {
if ($extra['data'] && is_array($extra['data'])) if (!empty($extra['data']) && is_array($extra['data']))
{ {
$content = array_merge($content, $extra['data']); $content = array_merge($content, $extra['data']);
} }
if ($extra['preserve'] && is_array($extra['preserve'])) if (!empty($extra['preserve']) && is_array($extra['preserve']))
{ {
$preserv = array_merge($preserv, $extra['preserve']); $preserv = array_merge($preserv, $extra['preserve']);
} }
if ($extra['readonlys'] && is_array($extra['readonlys'])) if (!empty($extra['readonlys']) && is_array($extra['readonlys']))
{ {
$readonlys = array_merge($readonlys, $extra['readonlys']); $readonlys = array_merge($readonlys, $extra['readonlys']);
} }
if ($extra['sel_options'] && is_array($extra['sel_options'])) if (!empty($extra['sel_options']) && is_array($extra['sel_options']))
{ {
$sel_options = array_merge($sel_options, $extra['sel_options']); $sel_options = array_merge($sel_options, $extra['sel_options']);
} }
@ -177,7 +177,7 @@ class Etemplate extends Etemplate\Widget\Template
} }
// some apps (eg. InfoLog) set app_header only in get_rows depending on filter settings // some apps (eg. InfoLog) set app_header only in get_rows depending on filter settings
self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header']; self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header'] ?? null;
// compile required translations translations // compile required translations translations
$currentapp = $GLOBALS['egw_info']['flags']['currentapp']; $currentapp = $GLOBALS['egw_info']['flags']['currentapp'];
@ -209,7 +209,7 @@ class Etemplate extends Etemplate\Widget\Template
'currentapp' => $currentapp, 'currentapp' => $currentapp,
); );
if($data['content']['nm']['rows'] && is_array($data['content']['nm']['rows'])) if (!empty($data['content']['nm']['rows']) && is_array($data['content']['nm']['rows']))
{ {
// Deep copy rows so we don't lose them when request is set to null // Deep copy rows so we don't lose them when request is set to null
// (some content by reference) // (some content by reference)
@ -420,7 +420,7 @@ class Etemplate extends Etemplate\Widget\Template
} }
$tcontent = is_array($content) ? $content : $tcontent = is_array($content) ? $content :
self::complete_array_merge(self::$request->preserv, $validated); self::complete_array_merge(self::$request->preserv ?? [], $validated);
$hook_data = Hooks::process( $hook_data = Hooks::process(
array( array(

View File

@ -428,9 +428,9 @@ class Request
* @param string $var * @param string $var
* @param mixed $val * @param mixed $val
*/ */
public function __set($var,$val) public function __set($var, $val)
{ {
if ($this->data[$var] !== $val) if (!isset($this->data[$var]) || $this->data[$var] !== $val)
{ {
$this->data[$var] = $val; $this->data[$var] = $val;
//error_log(__METHOD__."('$var', ...) data of id=$this->id changed ..."); //error_log(__METHOD__."('$var', ...) data of id=$this->id changed ...");

View File

@ -97,9 +97,10 @@ class Files extends Etemplate\Request
* Factory method to get a new request object or the one for an existing request * Factory method to get a new request object or the one for an existing request
* *
* @param string $id =null * @param string $id =null
* @return ?Etemplate\Request|false the object or false if $id is not found * @param bool $handle_not_found =true true: handle not found by trying to redirect, false: just return null
* @return Request|null null if Request not found and $handle_not_found === false
*/ */
static function read($id=null) public static function read($id=null, $handle_not_found=true)
{ {
$request = new Files($id); $request = new Files($id);

View File

@ -83,9 +83,10 @@ class Session extends Etemplate\Request
* Factory method to get a new request object or the one for an existing request * Factory method to get a new request object or the one for an existing request
* *
* @param string $id =null * @param string $id =null
* @return ?Etemplate\request|false the object or false if $id is not found * @param bool $handle_not_found =true true: handle not found by trying to redirect, false: just return null
* @return Request|null null if Request not found and $handle_not_found === false
*/ */
static function read($id=null) public static function read($id=null, $handle_not_found=true)
{ {
$request = new Session($id); $request = new Session($id);

View File

@ -553,12 +553,21 @@ class Widget
$method = new ReflectionMethod($this, $method_name); $method = new ReflectionMethod($this, $method_name);
foreach($method->getParameters() as $index => $param) foreach($method->getParameters() as $index => $param)
{ {
if(!$param->isOptional() && !array_key_exists($index,$params)) if(!$param->isOptional() && !array_key_exists($index, $params))
{ {
error_log("Missing required parameter {$param->getPosition()}: {$param->getName()}"); error_log("Missing required parameter {$param->getPosition()}: {$param->getName()}");
$call = false; $call = false;
} }
if($param->isArray() && !is_array($params[$index])) // Check to see if method wants an array, and we're providing it
$paramType = $param->getType();
if(!$paramType)
{
continue;
}
$types = $paramType instanceof \ReflectionUnionType
? $paramType->getTypes()
: [$paramType];
if(in_array('array', array_map(fn(\ReflectionNamedType $t) => $t->getName(), $types)) && !is_array($params[$index]))
{ {
error_log("$method_name expects an array for {$param->getPosition()}: {$param->getName()}"); error_log("$method_name expects an array for {$param->getPosition()}: {$param->getName()}");
$params[$index] = (array)$params[$index]; $params[$index] = (array)$params[$index];
@ -1044,6 +1053,10 @@ class Widget
*/ */
public static function &setElementAttribute($name,$attr,$val) public static function &setElementAttribute($name,$attr,$val)
{ {
if (!isset(self::$request))
{
throw new \Exception(__METHOD__."('$name', '$attr', ".json_encode($val)." called before instanciating Api\Etemplate!");
}
//error_log(__METHOD__."('$name', '$attr', ...) request=".get_class(self::$request).", response=".get_class(self::$response).function_backtrace()); //error_log(__METHOD__."('$name', '$attr', ...) request=".get_class(self::$request).", response=".get_class(self::$response).function_backtrace());
$ref =& self::$request->modifications[$name][$attr]; $ref =& self::$request->modifications[$name][$attr];
if(self::$request && self::$response) if(self::$request && self::$response)

View File

@ -50,7 +50,7 @@ class Box extends Etemplate\Widget
$old_expand = $params[1]; $old_expand = $params[1];
if ($this->id && $this->type != 'groupbox') $cname = self::form_name($cname, $this->id, $params[1]); if ($this->id && $this->type != 'groupbox') $cname = self::form_name($cname, $this->id, $params[1]);
if (!empty($expand['cname']) && $expand['cname'] !== $cname && trim($cname)) if (($expand['cname'] ?? null) !== $cname && trim($cname))
{ {
$expand['cont'] =& self::get_array(self::$request->content, $cname); $expand['cont'] =& self::get_array(self::$request->content, $cname);
$expand['cname'] = $cname; $expand['cname'] = $cname;

View File

@ -98,7 +98,7 @@ class Customfields extends Transformer
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
// Store properties at top level, so all customfield widgets can share // Store properties at top level, so all customfield widgets can share
if($this->attrs['app']) if (!empty($this->attrs['app']))
{ {
$app = $this->attrs['app']; $app = $this->attrs['app'];
} }
@ -141,12 +141,12 @@ class Customfields extends Transformer
// app changed // app changed
$customfields = Api\Storage\Customfields::get($app); $customfields = Api\Storage\Customfields::get($app);
} }
if($this->attrs['customfields']) if (!empty($this->attrs['customfields']))
{ {
$customfields = $this->attrs['customfields']; $customfields = $this->attrs['customfields'];
} }
// Filter fields // Filter fields
if($this->attrs['field-names']) if (!empty($this->attrs['field-names']))
{ {
$fields_name = explode(',', $this->attrs['field-names']); $fields_name = explode(',', $this->attrs['field-names']);
foreach($fields_name as &$f) foreach($fields_name as &$f)
@ -162,8 +162,8 @@ class Customfields extends Transformer
$fields = $customfields; $fields = $customfields;
$use_private = self::expand_name($this->attrs['use-private'],0,0,'','',self::$cont); $use_private = self::expand_name($this->attrs['use-private'] ?? null,0,0,'','',self::$cont);
$this->attrs['sub-type'] = self::expand_name($this->attrs['sub-type'],0,0,'','',self::$cont); $this->attrs['sub-type'] = self::expand_name($this->attrs['sub-type'] ?? null,0,0,'','',self::$cont);
foreach((array)$fields as $key => $field) foreach((array)$fields as $key => $field)
{ {
@ -174,7 +174,7 @@ class Customfields extends Transformer
} }
// Remove filtered fields // Remove filtered fields
if($field_filters && in_array($key, $negate_fields) && in_array($key, $field_filters)) if (!empty($field_filters) && in_array($key, $negate_fields) && in_array($key, $field_filters))
{ {
unset($fields[$key]); unset($fields[$key]);
} }
@ -284,7 +284,7 @@ class Customfields extends Transformer
$type = $field['type']; $type = $field['type'];
// Link-tos needs to change from appname to link-to // Link-tos needs to change from appname to link-to
if($link_types[$field['type']]) if (!empty($link_types[$field['type']]))
{ {
if($type == 'filemanager') if($type == 'filemanager')
{ {
@ -314,8 +314,8 @@ class Customfields extends Transformer
{ {
$widget->attrs['data_format'] = $type == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s'; $widget->attrs['data_format'] = $type == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s';
} }
if($field['values']['min']) $widget->attrs['min'] = $field['values']['min']; if (isset($field['values']['min'])) $widget->attrs['min'] = $field['values']['min'];
if($field['values']['max']) $widget->attrs['min'] = $field['values']['max']; if (isset($field['values']['max'])) $widget->attrs['min'] = $field['values']['max'];
break; break;
case 'vfs-upload': case 'vfs-upload':
@ -355,7 +355,7 @@ class Customfields extends Transformer
$field['values'] = Api\Storage\Customfields::get_options_from_file($field['values']['@']); $field['values'] = Api\Storage\Customfields::get_options_from_file($field['values']['@']);
} }
// keep extra values set by app code, eg. addressbook advanced search // keep extra values set by app code, eg. addressbook advanced search
if (is_array(self::$request->sel_options[self::$prefix.$fname])) if (!empty(self::$request->sel_options[self::$prefix.$fname]) && is_array(self::$request->sel_options[self::$prefix.$fname]))
{ {
self::$request->sel_options[self::$prefix.$fname] += (array)$field['values']; self::$request->sel_options[self::$prefix.$fname] += (array)$field['values'];
} }

View File

@ -42,7 +42,7 @@ class Description extends Etemplate\Widget
*/ */
public function beforeSendToClient($cname, array $expand=null) public function beforeSendToClient($cname, array $expand=null)
{ {
if ($this->attrs['activate_links']) if (!empty($this->attrs['activate_links']))
{ {
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
$value =& self::get_array(self::$request->content, $form_name); $value =& self::get_array(self::$request->content, $form_name);

View File

@ -88,8 +88,11 @@ class Grid extends Box
return false; // return return false; // return
} }
if ($this->id && $this->type !== 'row') $cname = self::form_name($cname, $this->id, $expand); if($this->id && $this->type !== 'row')
if (!empty($expand['cname']) && $expand['cname'] !== $cname && $cname) {
$cname = self::form_name($cname, $this->id, $expand);
}
if($cname && (!empty($expand['cname']) && $expand['cname'] !== $cname || !$expand['cname']))
{ {
$expand['cont'] =& self::get_array(self::$request->content, $cname); $expand['cont'] =& self::get_array(self::$request->content, $cname);
$expand['cname'] = $cname; $expand['cname'] = $cname;

View File

@ -36,17 +36,17 @@ class Image extends Etemplate\Widget
$image = $value != '' ? $value : $this->attrs['src']; $image = $value != '' ? $value : $this->attrs['src'];
if (is_string($image)) list($app,$img) = explode('/',$image,2); if (is_string($image)) list($app,$img) = explode('/',$image,2)+[null,null];
if (!$app || !$img || !is_dir(EGW_SERVER_ROOT.'/'.$app) || strpos($img,'/')!==false) if (empty($app) || empty($img) || !is_dir(EGW_SERVER_ROOT.'/'.$app) || strpos($img,'/')!==false)
{ {
$img = $image; $img = $image;
list($app) = explode('.',$form_name); list($app) = explode('.',$form_name);
} }
$src = Api\Image::find($app, $img); $src = Api\Image::find($app, $img);
if(!$this->id) /*if(!$this->id)
{ {
// self::setElementAttribute($this->attrs['src'], 'id', $this->attrs['src']); // self::setElementAttribute($this->attrs['src'], 'id', $this->attrs['src']);
} }*/
self::setElementAttribute($this->attrs['src'], 'src', $src); self::setElementAttribute($this->attrs['src'], 'src', $src);
} }
} }

View File

@ -130,13 +130,13 @@ class Nextmatch extends Etemplate\Widget
$send_value = $value; $send_value = $value;
list($app) = explode('.',$value['get_rows']); list($app) = explode('.',$value['get_rows']);
if(!$GLOBALS['egw_info']['apps'][$app]) if (empty($GLOBALS['egw_info']['apps'][$app]))
{ {
list($app) = explode('.',$this->attrs['template']); list($app) = explode('.',$this->attrs['template']);
} }
// Check for a favorite in URL // Check for a favorite in URL
if($_GET['favorite'] && $value['favorites']) if (!empty($_GET['favorite']) && !empty($value['favorites']))
{ {
$safe_name = preg_replace('/[^A-Za-z0-9-_]/','_',strip_tags($_GET['favorite'])); $safe_name = preg_replace('/[^A-Za-z0-9-_]/','_',strip_tags($_GET['favorite']));
$pref_name = "favorite_" .$safe_name; $pref_name = "favorite_" .$safe_name;
@ -210,7 +210,7 @@ class Nextmatch extends Etemplate\Widget
} }
// Favorite group for admins // Favorite group for admins
if($GLOBALS['egw_info']['apps']['admin'] && $value['favorites']) if (!empty($GLOBALS['egw_info']['apps']['admin']) && !empty($value['favorites']))
{ {
self::$request->sel_options[$form_name]['favorite']['group'] = array('all' => lang('All users')) + self::$request->sel_options[$form_name]['favorite']['group'] = array('all' => lang('All users')) +
Select::typeOptions('select-account',',groups'); Select::typeOptions('select-account',',groups');
@ -894,7 +894,7 @@ class Nextmatch extends Etemplate\Widget
if ($default_attrs) $action += $default_attrs; if ($default_attrs) $action += $default_attrs;
// Add 'Select All' after first group // Add 'Select All' after first group
if ($first_level && $group !== false && $action['group'] != $group && empty($egw_actions[$prefix.'select_all'])) if ($first_level && $group !== false && ($action['group']??null) != $group && empty($egw_actions[$prefix.'select_all']))
{ {
$egw_actions[$prefix.'select_all'] = array( $egw_actions[$prefix.'select_all'] = array(

View File

@ -49,7 +49,8 @@ class Password extends Etemplate\Widget\Textbox
{ {
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
$value =& self::get_array(self::$request->content, $form_name); $value =& self::get_array(self::$request->content, $form_name);
$plaintext = !in_array(self::expand_name($this->attrs['plaintext'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']), $plaintext = !empty($this->attrs['plaintext']) && !in_array(
self::expand_name($this->attrs['plaintext'], $expand['c'] ?? null, $expand['row'] ?? null, $expand['c_'] ?? null, $expand['row_'] ?? null, $expand['cont']),
['false', '0']); ['false', '0']);
if (!empty($value)) if (!empty($value))

View File

@ -64,9 +64,13 @@ class Placeholder extends Etemplate\Widget
if(is_null($apps)) if(is_null($apps))
{ {
$apps = ['addressbook', 'user', 'general'] + $apps = array_merge(
['addressbook', 'user', 'general'],
// We use linking for preview, so limit to apps that support links // We use linking for preview, so limit to apps that support links
array_keys(Api\Link::app_list('query')); array_keys(Api\Link::app_list('query')),
// Filemanager doesn't support links, but add it anyway
['filemanager']
);
} }
foreach($apps as $appname) foreach($apps as $appname)
@ -86,6 +90,8 @@ class Placeholder extends Etemplate\Widget
// Looks like app doesn't support merging // Looks like app doesn't support merging
continue 2; continue 2;
} }
Api\Translation::load_app($appname, $GLOBALS['egw_info']['user']['preferences']['common']['lang']);
$list = method_exists($merge, 'get_placeholder_list') ? $merge->get_placeholder_list() : []; $list = method_exists($merge, 'get_placeholder_list') ? $merge->get_placeholder_list() : [];
break; break;
} }

View File

@ -109,14 +109,14 @@ class Select extends Etemplate\Widget
{ {
parent::set_attrs($xml, $cloned); parent::set_attrs($xml, $cloned);
if ($this->attrs['multiple'] !== 'dynamic') if (!isset($this->attrs['multiple']) || $this->attrs['multiple'] !== 'dynamic')
{ {
$this->attrs['multiple'] = !isset($this->attrs['multiple']) ? false : $this->attrs['multiple'] = !isset($this->attrs['multiple']) ? false :
!(!$this->attrs['multiple'] || $this->attrs['multiple'] === 'false'); !(!$this->attrs['multiple'] || $this->attrs['multiple'] === 'false');
} }
// set attrs[multiple] from attrs[options], unset options only if it just contains number or rows // set attrs[multiple] from attrs[options], unset options only if it just contains number or rows
if ($this->attrs['options'] > 1) if (isset($this->attrs['options']) && $this->attrs['options'] > 1)
{ {
$this->attrs['multiple'] = (int)$this->attrs['options']; $this->attrs['multiple'] = (int)$this->attrs['options'];
if ((string)$this->attrs['multiple'] == $this->attrs['options']) if ((string)$this->attrs['multiple'] == $this->attrs['options'])
@ -124,7 +124,7 @@ class Select extends Etemplate\Widget
unset($this->attrs['options']); unset($this->attrs['options']);
} }
} }
elseif($this->attrs['rows'] > 1) elseif(isset($this->attrs['rows']) && $this->attrs['rows'] > 1)
{ {
$this->attrs['multiple'] = true; $this->attrs['multiple'] = true;
} }
@ -311,8 +311,8 @@ class Select extends Etemplate\Widget
{ {
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
} }
if (!is_array(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = array(); if (empty(self::$request->sel_options[$form_name]) || !is_array(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = [];
$type = $this->attrs['type'] ? $this->attrs['type'] : $this->type; $type = $this->attrs['type'] ?? $this->type;
if ($type != 'select' && $type != 'menupopup') if ($type != 'select' && $type != 'menupopup')
{ {
// Check selection preference, we may be able to skip reading some data // Check selection preference, we may be able to skip reading some data
@ -335,8 +335,8 @@ class Select extends Etemplate\Widget
if (!isset($form_names_done[$form_name]) && if (!isset($form_names_done[$form_name]) &&
($type_options = self::typeOptions($this, ($type_options = self::typeOptions($this,
// typeOptions thinks # of rows is the first thing in options // typeOptions thinks # of rows is the first thing in options
($this->attrs['rows'] && strpos($this->attrs['options'], $this->attrs['rows']) !== 0 ? $this->attrs['rows'].','.$this->attrs['options'] : $this->attrs['options']), (!empty($this->attrs['rows']) && !empty($this->attrs['options']) && strpos($this->attrs['options'], $this->attrs['rows']) !== 0 ? $this->attrs['rows'].','.$this->attrs['options'] : $this->attrs['options']),
$no_lang, $this->attrs['readonly'], self::get_array(self::$request->content, $form_name), $form_name))) $no_lang, $this->attrs['readonly'] ?? false, self::get_array(self::$request->content, $form_name), $form_name)))
{ {
self::fix_encoded_options($type_options); self::fix_encoded_options($type_options);
@ -356,7 +356,7 @@ class Select extends Etemplate\Widget
$options = (isset(self::$request->sel_options[$form_name]) ? $form_name : $this->id); $options = (isset(self::$request->sel_options[$form_name]) ? $form_name : $this->id);
if(is_array(self::$request->sel_options[$options])) if(is_array(self::$request->sel_options[$options]))
{ {
if(in_array($this->attrs['type'], self::$cached_types) && !isset($form_names_done[$options])) if (isset($this->attrs['type']) && in_array($this->attrs['type'], self::$cached_types) && !isset($form_names_done[$options]))
{ {
// Fix any custom options from application // Fix any custom options from application
self::fix_encoded_options(self::$request->sel_options[$options],true); self::fix_encoded_options(self::$request->sel_options[$options],true);
@ -561,7 +561,7 @@ class Select extends Etemplate\Widget
$field = self::expand_name($field, 0, 0,'','',self::$cont); $field = self::expand_name($field, 0, 0,'','',self::$cont);
} }
list($rows,$type,$type2,$type3,$type4,$type5) = $legacy_options; list($rows,$type,$type2,$type3,$type4,$type5) = $legacy_options+[null,null,null,null,null,null];
$no_lang = false; $no_lang = false;
$options = array(); $options = array();
switch ($widget_type) switch ($widget_type)
@ -644,7 +644,7 @@ class Select extends Etemplate\Widget
// These are extra info for easy dealing with categories // These are extra info for easy dealing with categories
// client side, without extra loading // client side, without extra loading
'main' => (int)$cat['main'], 'main' => (int)$cat['main'],
'children' => $cat['children'], 'children' => $cat['children'] ?? null,
//add different class per level to allow different styling for each category level: //add different class per level to allow different styling for each category level:
'class' => "cat_level". $cat['level'] 'class' => "cat_level". $cat['level']
); );
@ -839,7 +839,7 @@ class Select extends Etemplate\Widget
} }
foreach((array)$options as $right => $name) foreach((array)$options as $right => $name)
{ {
if(!!($value & $right)) if (!!((int)$value & (int)$right))
{ {
$new_value[] = $right; $new_value[] = $right;
} }

View File

@ -68,7 +68,7 @@ class Template extends Etemplate\Widget
list($name) = explode('?', $_name); // remove optional cache-buster list($name) = explode('?', $_name); // remove optional cache-buster
if (isset(self::$cache[$name]) || !($path = self::relPath($name, $template_set, $version, $load_via))) if (isset(self::$cache[$name]) || !($path = self::relPath($name, $template_set, $version, $load_via)))
{ {
if ((!$path || self::read($load_via, $template_set)) && isset(self::$cache[$name])) if ((empty($path) || self::read($load_via, $template_set)) && isset(self::$cache[$name]))
{ {
//error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') read from cache"); //error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') read from cache");
return self::$cache[$name]; return self::$cache[$name];
@ -146,7 +146,7 @@ class Template extends Etemplate\Widget
{ {
static $prefixes = null; static $prefixes = null;
unset($version); // not used currently unset($version); // not used currently
list($app, $rest) = explode('.', $load_via ?: $name, 2); list($app, $rest) = explode('.', $load_via ?: $name, 2)+[null,null];
if (empty($template_set)) if (empty($template_set))
{ {
@ -184,7 +184,7 @@ class Template extends Etemplate\Widget
$path = $prefix.$path; $path = $prefix.$path;
} }
//error_log(__METHOD__."('$name', '$template_set') returning ".array2string($path)); //error_log(__METHOD__."('$name', '$template_set') returning ".array2string($path));
return $path; return $path ?? null;
} }
/** /**
@ -230,16 +230,16 @@ class Template extends Etemplate\Widget
{ {
$cname =& $params[0]; $cname =& $params[0];
$old_cname = $params[0]; $old_cname = $params[0];
if ($this->attrs['content']) $cname = self::form_name($cname, $this->attrs['content'], $params[1]); if (!empty($this->attrs['content'])) $cname = self::form_name($cname, $this->attrs['content'], $params[1]);
// Check for template from content, and run over it // Check for template from content, and run over it
// templates included via template tag have their name to load them from in attribute "template" // templates included via template tag have their name to load them from in attribute "template"
$expand_name = self::expand_name($this->id ? $this->id : $this->attrs['template'], '','','','',self::$request->content); $expand_name = self::expand_name($this->id ?: $this->attrs['template'], '','','','',self::$request->content);
if(!$expand_name && $this->id && $this->attrs['template']) if(!$expand_name && $this->id && $this->attrs['template'])
{ {
$expand_name = $this->attrs['template']; $expand_name = $this->attrs['template'];
} }
if($this->original_name) if (!empty($this->original_name))
{ {
$expand_name = self::expand_name($this->original_name, '','','','',self::$request->content); $expand_name = self::expand_name($this->original_name, '','','','',self::$request->content);
} }

View File

@ -62,14 +62,14 @@ class Textbox extends Etemplate\Widget
parent::set_attrs($xml, $cloned); parent::set_attrs($xml, $cloned);
// Legacy handling only // Legacy handling only
// A negative size triggered the HTML readonly attibute, but not etemplate readonly, // A negative size triggered the HTML readonly attribute, but not etemplate readonly,
// so you got an input element, but it was not editable. // so you got an input element, but it was not editable.
if ($this->attrs['size'] < 0) if (isset($this->attrs['size']) && $this->attrs['size'] < 0)
{ {
self::setElementAttribute($this->id, 'size', abs($this->attrs['size'])); self::setElementAttribute($this->id, 'size', abs($this->attrs['size']));
self::$request->readonlys[$this->id] = false; self::$request->readonlys[$this->id] = false;
self::setElementAttribute($this->id, 'readonly', true); self::setElementAttribute($this->id, 'readonly', true);
trigger_error("Using a negative size to set textbox readonly. " .$this, E_USER_DEPRECATED); //trigger_error("Using a negative size to set textbox readonly. " .$this, E_USER_DEPRECATED);
} }
return $this; return $this;
} }

View File

@ -143,7 +143,7 @@ class Tree extends Etemplate\Widget
parent::set_attrs($xml, $cloned); parent::set_attrs($xml, $cloned);
// set attrs[multiple] from attrs[options] // set attrs[multiple] from attrs[options]
if ($this->attrs['options'] > 1) if (isset($this->attrs['options']) && (int)$this->attrs['options'] > 1)
{ {
self::setElementAttribute($this->id, 'multiple', true); self::setElementAttribute($this->id, 'multiple', true);
} }
@ -297,21 +297,21 @@ class Tree extends Etemplate\Widget
{ {
$form_name = self::form_name($cname, $this->id); $form_name = self::form_name($cname, $this->id);
if (($templated_path = self::templateImagePath($this->attrs['image_path'])) != $this->attrs['image_path']) if (($templated_path = self::templateImagePath($this->attrs['image_path'] ?? null)) !== ($this->attrs['image_path'] ?? null))
{ {
self::setElementAttribute($form_name, 'image_path', $this->attrs['image_path'] = $templated_path); self::setElementAttribute($form_name, 'image_path', $this->attrs['image_path'] = $templated_path);
//error_log(__METHOD__."() setting templated image-path for $form_name: $templated_path"); //error_log(__METHOD__."() setting templated image-path for $form_name: $templated_path");
} }
if (!is_array(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = array(); if (empty(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = [];
if ($this->attrs['type']) if (!empty($this->attrs['type']))
{ {
// += to keep further options set by app code // += to keep further options set by app code
self::$request->sel_options[$form_name] += self::typeOptions($this->attrs['type'], $this->attrs['options'], self::$request->sel_options[$form_name] += self::typeOptions($this->attrs['type'], $this->attrs['options'] ?? null,
$no_lang, $this->attrs['readonly'], self::get_array(self::$request->content, $form_name), $form_name); $no_lang, $this->attrs['readonly'] ?? null, self::get_array(self::$request->content, $form_name), $form_name);
// if no_lang was modified, forward modification to the client // if no_lang was modified, forward modification to the client
if ($no_lang != $this->attr['no_lang']) if (!isset($this->attr['no_lang']) || $no_lang != $this->attr['no_lang'])
{ {
self::setElementAttribute($form_name, 'no_lang', $no_lang); self::setElementAttribute($form_name, 'no_lang', $no_lang);
} }
@ -440,7 +440,7 @@ class Tree extends Etemplate\Widget
*/ */
public static function typeOptions($widget_type, $legacy_options, &$no_lang=false, $readonly=false, $value=null, $form_name=null) public static function typeOptions($widget_type, $legacy_options, &$no_lang=false, $readonly=false, $value=null, $form_name=null)
{ {
list($rows,$type,$type2,$type3) = explode(',',$legacy_options); list($rows,$type,$type2,$type3) = explode(',', $legacy_options)+[null,null,null,null];
$no_lang = false; $no_lang = false;
$options = array(); $options = array();

View File

@ -37,10 +37,10 @@ class Vfs extends File
*/ */
public function beforeSendToClient($cname, $expand = array()) public function beforeSendToClient($cname, $expand = array())
{ {
if($this->type == 'vfs-upload' || $this->attrs['type'] == 'vfs-upload') if ($this->type === 'vfs-upload' || !empty($this->attrs['type']) && $this->attrs['type'] === 'vfs-upload')
{ {
$form_name = self::form_name($cname, $this->id, $expand ? $expand : array('cont'=>self::$request->content)); $form_name = self::form_name($cname, $this->id, $expand ? $expand : array('cont'=>self::$request->content));
if($this->attrs['path']) if (!empty($this->attrs['path']))
{ {
$path = self::expand_name($this->attrs['path'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); $path = self::expand_name($this->attrs['path'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
} }
@ -226,7 +226,7 @@ class Vfs extends File
foreach($links as $link) foreach($links as $link)
{ {
$matches = null; $matches = null;
if (is_array($link) && preg_match('|^'.preg_quote(Api\Vfs::PREFIX,'|').'('.preg_quote(self::get_temp_dir($app, ''), '|').'[^/]+)/|', $link['id']['tmp_name'], $matches)) if (is_array($link) && !empty($link['id']['tmp_name']) && preg_match('|^'.preg_quote(Api\Vfs::PREFIX,'|').'('.preg_quote(self::get_temp_dir($app, ''), '|').'[^/]+)/|', $link['id']['tmp_name'], $matches))
{ {
$replace[substr($link['id']['tmp_name'], strlen(Api\Vfs::PREFIX))] = $replace[substr($link['id']['tmp_name'], strlen(Api\Vfs::PREFIX))] =
Api\Link::vfs_path($app, $id, Api\Vfs::basename($link['id']['tmp_name']), true); Api\Link::vfs_path($app, $id, Api\Vfs::basename($link['id']['tmp_name']), true);

View File

@ -2,12 +2,12 @@
/** /**
* EGroupware - Framework for Ajax based templates: jdots & Pixelegg * EGroupware - Framework for Ajax based templates: jdots & Pixelegg
* *
* @link http://www.stylite.de * @link https://www.egroupware.org
* @package api * @package api
* @subpackage framework * @subpackage framework
* @author Andreas Stöckel <as@stylite.de> * @author Andreas Stöckel
* @author Ralf Becker <rb@stylite.de> * @author Ralf Becker <rb@egroupware.org>
* @author Nathan Gray <ng@stylite.de> * @author Nathan Gray <ng@egroupware.org>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/ */
@ -16,7 +16,7 @@ namespace EGroupware\Api\Framework;
use EGroupware\Api; use EGroupware\Api;
/** /**
* Stylite jdots template * Framework for Ajax based templates
*/ */
abstract class Ajax extends Api\Framework abstract class Ajax extends Api\Framework
{ {
@ -88,13 +88,13 @@ abstract class Ajax extends Api\Framework
$width = self::DEFAULT_SIDEBAR_WIDTH; $width = self::DEFAULT_SIDEBAR_WIDTH;
//Check whether the width had been stored explicitly for the jdots template, use that value //Check whether the width had been stored explicitly for the jdots template, use that value
if ($GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth']) if (!empty($GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth']))
{ {
$width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth']; $width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth'];
// error_log(__METHOD__.__LINE__."($app):$width --> reading jdotssideboxwidth"); // error_log(__METHOD__.__LINE__."($app):$width --> reading jdotssideboxwidth");
} }
//Otherwise use the legacy "idotssideboxwidth" value //Otherwise use the legacy "idotssideboxwidth" value
else if ($GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth']) elseif (!empty($GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth']))
{ {
$width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth']; $width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth'];
// error_log(__METHOD__.__LINE__."($app):$width --> reading idotssideboxwidth"); // error_log(__METHOD__.__LINE__."($app):$width --> reading idotssideboxwidth");
@ -249,7 +249,7 @@ abstract class Ajax extends Api\Framework
{ {
if (empty($GLOBALS['egw_info']['flags']['java_script'])) $GLOBALS['egw_info']['flags']['java_script']=''; if (empty($GLOBALS['egw_info']['flags']['java_script'])) $GLOBALS['egw_info']['flags']['java_script']='';
// eT2 sets $GLOBALS['egw_info']['flags']['nonavbar'] === 'popup' for popups, Etemplate::exec($outputmode === 2) // eT2 sets $GLOBALS['egw_info']['flags']['nonavbar'] === 'popup' for popups, Etemplate::exec($outputmode === 2)
$extra['check-framework'] = $_GET['cd'] !== 'no' && $GLOBALS['egw_info']['flags']['nonavbar'] !== 'popup'; $extra['check-framework'] = (!isset($_GET['cd']) || $_GET['cd'] !== 'no') && $GLOBALS['egw_info']['flags']['nonavbar'] !== 'popup';
} }
} }
@ -1047,16 +1047,16 @@ abstract class Ajax extends Api\Framework
if (self::$footer_done) return; // prevent (multiple) footers if (self::$footer_done) return; // prevent (multiple) footers
self::$footer_done = true; self::$footer_done = true;
if (!isset($GLOBALS['egw_info']['flags']['nofooter']) || !$GLOBALS['egw_info']['flags']['nofooter']) if (empty($GLOBALS['egw_info']['flags']['nofooter']))
{ {
if ($no_framework && $GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time']) if ($no_framework && !empty($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time']))
{ {
$vars = $this->_get_footer(); $vars = $this->_get_footer();
$footer = "\n".$vars['page_generation_time']."\n"; $footer = "\n".$vars['page_generation_time']."\n";
} }
} }
return $footer. return ($footer??'').
$GLOBALS['egw_info']['flags']['need_footer']."\n". // eg. javascript, which need to be at the end of the page ($GLOBALS['egw_info']['flags']['need_footer']??'')."\n". // eg. javascript, which need to be at the end of the page
"</body>\n</html>\n"; "</body>\n</html>\n";
} }

View File

@ -49,12 +49,12 @@ class Bundle
unset($GLOBALS['egw_info']['server']['debug_minify']); unset($GLOBALS['egw_info']['server']['debug_minify']);
$file2bundle = array(); $file2bundle = array();
if ($GLOBALS['egw_info']['server']['debug_minify'] !== 'True') if (!isset($GLOBALS['egw_info']['server']['debug_minify']) || $GLOBALS['egw_info']['server']['debug_minify'] !== 'True')
{ {
// get used bundles and cache them on tree-level for 2h // get used bundles and cache them on tree-level for 2h
//$bundles = self::all(); Cache::setTree(__CLASS__, 'bundles', $bundles, 7200); //$bundles = self::all(); Cache::setTree(__CLASS__, 'bundles', $bundles, 7200);
$bundles = Cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'all'), array(), 7200); $bundles = Cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'all'), array(), 7200);
$bundles_ts = $bundles['.ts']; $bundles_ts = $bundles['.ts'] ?? null;
unset($bundles['.ts']); unset($bundles['.ts']);
foreach($bundles as $name => $files) foreach($bundles as $name => $files)
{ {
@ -83,13 +83,13 @@ class Bundle
if (!isset($to_include[$file])) if (!isset($to_include[$file]))
{ {
if (($bundle = $file2bundle[$file])) if (($bundle = $file2bundle[$file] ?? false))
{ {
//error_log(__METHOD__."() requiring bundle $bundle for $file"); //error_log(__METHOD__."() requiring bundle $bundle for $file");
if (!in_array($bundle, $included_bundles)) if (!in_array($bundle, $included_bundles))
{ {
$included_bundles[] = $bundle; $included_bundles[] = $bundle;
$minurl = self::$bundle2minurl[$bundle]; $minurl = self::$bundle2minurl[$bundle] ?? null;
if (!isset($minurl) && isset($GLOBALS['egw_info']['apps'][$bundle])) if (!isset($minurl) && isset($GLOBALS['egw_info']['apps'][$bundle]))
{ {
$minurl = '/'.$bundle.'/js/app.min.js'; $minurl = '/'.$bundle.'/js/app.min.js';
@ -108,10 +108,10 @@ class Bundle
else else
{ {
unset($query); unset($query);
list($path, $query) = explode('?', $file, 2); list($path, $query) = explode('?', $file, 2)+[null,null];
$mod = filemtime(EGW_SERVER_ROOT.$path); $mod = filemtime(EGW_SERVER_ROOT.$path);
// check if we have a more recent minified version of the file and use it // check if we have a more recent minified version of the file and use it
if ($GLOBALS['egw_info']['server']['debug_minify'] !== 'True' && if ((!isset($GLOBALS['egw_info']['server']['debug_minify']) || $GLOBALS['egw_info']['server']['debug_minify'] !== 'True') &&
substr($path, -3) == '.js' && file_exists(EGW_SERVER_ROOT.($min_path = substr($path, 0, -3).'.min.js')) && substr($path, -3) == '.js' && file_exists(EGW_SERVER_ROOT.($min_path = substr($path, 0, -3).'.min.js')) &&
(($min_mod = filemtime(EGW_SERVER_ROOT.$min_path)) >= $mod)) (($min_mod = filemtime(EGW_SERVER_ROOT.$min_path)) >= $mod))
{ {
@ -148,7 +148,7 @@ class Bundle
*/ */
protected static function urls(array $js_includes, &$max_modified=null, $minurl=null) protected static function urls(array $js_includes, &$max_modified=null, $minurl=null)
{ {
$debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; $debug_minify = !empty($GLOBALS['egw_info']['server']['debug_minify']) && $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
// ignore not existing minurl // ignore not existing minurl
if (!empty($minurl) && !file_exists(EGW_SERVER_ROOT.$minurl)) $minurl = null; if (!empty($minurl) && !file_exists(EGW_SERVER_ROOT.$minurl)) $minurl = null;
$to_include_first = $to_include = $to_minify = array(); $to_include_first = $to_include = $to_minify = array();
@ -158,7 +158,7 @@ class Bundle
{ {
if ($path == '/api/js/jsapi/egw.js') continue; // Leave egw.js out of bundle if ($path == '/api/js/jsapi/egw.js') continue; // Leave egw.js out of bundle
unset($query); unset($query);
list($path,$query) = explode('?',$path,2); list($path,$query) = explode('?',$path,2)+[null,null];
$mod = filemtime(EGW_SERVER_ROOT.$path); $mod = filemtime(EGW_SERVER_ROOT.$path);
if ($mod > $max_modified) $max_modified = $mod; if ($mod > $max_modified) $max_modified = $mod;

View File

@ -113,7 +113,7 @@ class CssIncludes
{ {
foreach(self::resolve_css_includes($path) as $path) foreach(self::resolve_css_includes($path) as $path)
{ {
list($file,$query) = explode('?',$path,2); list($file,$query) = explode('?',$path,2)+[null,null];
if (($mod = filemtime(EGW_SERVER_ROOT.$file)) > $max_modified) $max_modified = $mod; if (($mod = filemtime(EGW_SERVER_ROOT.$file)) > $max_modified) $max_modified = $mod;
// do NOT include app.css or categories.php, as it changes from app to app // do NOT include app.css or categories.php, as it changes from app to app

View File

@ -100,6 +100,7 @@ class Html
// use preg_replace_callback as we experienced problems with links such as <www.example.tld/pfad/zu/einer/pdf-Datei.pdf> // use preg_replace_callback as we experienced problems with links such as <www.example.tld/pfad/zu/einer/pdf-Datei.pdf>
$result4 = preg_replace_callback( $Expr, function ($match) { $result4 = preg_replace_callback( $Expr, function ($match) {
//error_log(__METHOD__.__LINE__.array2string($match)); //error_log(__METHOD__.__LINE__.array2string($match));
$match += [null,null,null,null];
if ($match[4]==';' && (strlen($match[3])-4) >=0 && strpos($match[3],'&gt',strlen($match[3])-4)!==false) if ($match[4]==';' && (strlen($match[3])-4) >=0 && strpos($match[3],'&gt',strlen($match[3])-4)!==false)
{ {
$match[3] = substr($match[3],0,strpos($match[3],'&gt',strlen($match[3])-4)); $match[3] = substr($match[3],0,strpos($match[3],'&gt',strlen($match[3])-4));
@ -111,7 +112,7 @@ class Html
$match[4] = "&gt;"; $match[4] = "&gt;";
} }
//error_log(__METHOD__.__LINE__.array2string($match)); //error_log(__METHOD__.__LINE__.array2string($match));
return $match[1]."<a href=\"http://www".$match[2].$match[3]."\" target=\"_blank\">"."www".$match[2].$match[3]."</a>".$match[4]; return $match[1]."<a href=\"https://www".$match[2].$match[3]."\" target=\"_blank\">"."www".$match[2].$match[3]."</a>".$match[4];
}, $result3 ); }, $result3 );
} }
return $result4; return $result4;
@ -755,7 +756,7 @@ tinymce.init({
{ {
parse_str($vars,$vars); parse_str($vars,$vars);
} }
list($url,$v) = explode('?', $_url); // url may contain additional vars list($url,$v) = explode('?', $_url)+[null,null]; // url may contain additional vars
if ($v) if ($v)
{ {
parse_str($v,$v); parse_str($v,$v);

View File

@ -391,7 +391,7 @@ function hl_email_tag_transform($element, $attribute_array=0)
// $GLOBALS['egw_info']['user']['preferences']['mail']['allowExternalIMGs'] ? '' : 'match' => '/^cid:.*/'), // $GLOBALS['egw_info']['user']['preferences']['mail']['allowExternalIMGs'] ? '' : 'match' => '/^cid:.*/'),
if (isset($attribute_array['src'])) if (isset($attribute_array['src']))
{ {
if (!(strlen($attribute_array['src'])>4 && strlen($attribute_array['src'])<400)) if (!(strlen($attribute_array['src'])>4 && strlen($attribute_array['src'])<800))
{ {
$attribute_array['alt']= $attribute_array['alt'].' [blocked (reason: url length):'.$attribute_array['src'].']'; $attribute_array['alt']= $attribute_array['alt'].' [blocked (reason: url length):'.$attribute_array['src'].']';
if (!isset($attribute_array['title'])) $attribute_array['title']=$attribute_array['alt']; if (!isset($attribute_array['title'])) $attribute_array['title']=$attribute_array['alt'];

View File

@ -471,7 +471,7 @@ class Link extends Link\Storage
*/ */
static function temp_link_id($app,$id) static function temp_link_id($app,$id)
{ {
return $app.':'.(!in_array($app, array(self::VFS_APPNAME,self::VFS_LINK, self::DATA_APPNAME)) ? $id : $id['name']); return $app.':'.(!in_array($app, array(self::VFS_APPNAME,self::VFS_LINK, self::DATA_APPNAME)) || !is_array($id) ? $id : $id['name']);
} }
/** /**
@ -683,15 +683,15 @@ class Link extends Link\Storage
{ {
echo "<p>Link::unlink('$link_id','$app',".array2string($id).",'$owner','$app2','$id2', $hold_for_purge)</p>\n"; echo "<p>Link::unlink('$link_id','$app',".array2string($id).",'$owner','$app2','$id2', $hold_for_purge)</p>\n";
} }
if ($link_id < 0) // vfs-link? if ((int)$link_id < 0) // vfs-link?
{ {
return self::delete_attached(-$link_id); return self::delete_attached(-$link_id);
} }
elseif ($app == self::VFS_APPNAME) elseif ($app === self::VFS_APPNAME)
{ {
return self::delete_attached($app2,$id2,$id); return self::delete_attached($app2,$id2,$id);
} }
elseif ($app2 == self::VFS_APPNAME) elseif ($app2 === self::VFS_APPNAME)
{ {
return self::delete_attached($app,$id,$id2); return self::delete_attached($app,$id,$id2);
} }

View File

@ -127,7 +127,7 @@ class Storage
{ {
echo "<p>solink.get_links($app,".print_r($id,true).",$only_app,$order,$deleted)</p>\n"; echo "<p>solink.get_links($app,".print_r($id,true).",$only_app,$order,$deleted)</p>\n";
} }
if (($not_only = $only_app[0] == '!')) if (!empty($only_app) && ($not_only = $only_app[0] == '!'))
{ {
$only_app = substr($only_app,1); $only_app = substr($only_app,1);
} }
@ -173,7 +173,7 @@ class Storage
catch(Api\Db\Exception $e) { catch(Api\Db\Exception $e) {
_egw_log_exception($e); _egw_log_exception($e);
} }
return is_array($id) ? $links : ($links[$id] ? $links[$id] : array()); return is_array($id) ? $links : ($links[$id] ?? []);
} }
private static function _add2links($row,$left,$only_app,$not_only,array &$links) private static function _add2links($row,$left,$only_app,$not_only,array &$links)

View File

@ -414,14 +414,10 @@ class Mail
{ {
//error_log(__METHOD__." Session restore ".function_backtrace()); //error_log(__METHOD__." Session restore ".function_backtrace());
$this->restoreSessionData(); $this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
} }
else else
{ {
$this->restoreSessionData(); $this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
$this->sessionData = array(); $this->sessionData = array();
} }
if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache); if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
@ -1000,9 +996,7 @@ class Mail
*/ */
static function getTimeOut($_use='IMAP') static function getTimeOut($_use='IMAP')
{ {
$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout']; return $_use=='SIEVE' ? 10 : 20; // this is the default value
if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
return $timeout;
} }
/** /**
@ -3211,7 +3205,7 @@ class Mail
} }
//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects)); //error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
if (!$isGoogleMail) { if (!$isGoogleMail) {
$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'] ?? [],(array)$typeFolderObject['shared'] ?? []); $folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)($typeFolderObject['others'] ?? []),(array)($typeFolderObject['shared'] ?? []));
} else { } else {
// avoid calling sortByAutoFolder as it is not regarding subfolders // avoid calling sortByAutoFolder as it is not regarding subfolders
$gAutoFolderObjectsTmp = $googleAutoFolderObjects; $gAutoFolderObjectsTmp = $googleAutoFolderObjects;
@ -5589,10 +5583,10 @@ class Mail
if (empty($_folder)) $_folder = $this->sessionData['mailbox']?: $this->icServer->getCurrentMailbox(); if (empty($_folder)) $_folder = $this->sessionData['mailbox']?: $this->icServer->getCurrentMailbox();
$_uid = !(is_object($_uid) || is_array($_uid)) ? (array)$_uid : $_uid; $_uid = !(is_object($_uid) || is_array($_uid)) ? (array)$_uid : $_uid;
if (!$_stream && isset($rawBody[$this->icServer->ImapServerId][$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)])) if (!$_stream && isset($rawBody[$this->icServer->ImapServerId][(string)$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)]))
{ {
//error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder"); //error_log(__METHOD__.' ('.__LINE__.') '." Using Cache for raw Body $_uid, $_partID in Folder $_folder");
return $rawBody[$this->icServer->ImapServerId][$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)]; return $rawBody[$this->icServer->ImapServerId][(string)$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)];
} }
$uidsToFetch = new Horde_Imap_Client_Ids(); $uidsToFetch = new Horde_Imap_Client_Ids();
@ -5629,7 +5623,7 @@ class Mail
if (!$_stream) if (!$_stream)
{ {
//error_log(__METHOD__.' ('.__LINE__.') '."[{$this->icServer->ImapServerId}][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]"); //error_log(__METHOD__.' ('.__LINE__.') '."[{$this->icServer->ImapServerId}][$_folder][$_uid][".(empty($_partID)?'NIL':$_partID)."]");
$rawBody[$this->icServer->ImapServerId][$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)] = $body; $rawBody[$this->icServer->ImapServerId][(string)$_folder][$_uid[0]][(empty($_partID)?'NIL':$_partID)] = $body;
} }
return $body; return $body;
} }
@ -6775,6 +6769,8 @@ class Mail
//error_log(__METHOD__."()"); //error_log(__METHOD__."()");
$imageC = 0; $imageC = 0;
$images = null; $images = null;
$attachments = null;
if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2])) if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
{ {
foreach($images[2] as $i => $url) foreach($images[2] as $i => $url)
@ -6782,18 +6778,18 @@ class Mail
//$isData = false; //$isData = false;
$basedir = $data = ''; $basedir = $data = '';
$needTempFile = true; $needTempFile = true;
$attachmentData = ['name' => '', 'type' => '', 'file' => '', 'tmp_name' => ''];
try try
{ {
// do not change urls for absolute images (thanks to corvuscorax) // do not change urls for absolute images (thanks to corvuscorax)
if (substr($url, 0, 5) !== 'data:') if (!str_starts_with($url, 'data:'))
{ {
$filename = basename($url); // need to resolve all sort of url $attachmentData['name'] = basename($url); // need to resolve all sort of url
if (($directory = dirname($url)) == '.') $directory = ''; if (($directory = dirname($url)) == '.') $directory = '';
$ext = pathinfo($filename, PATHINFO_EXTENSION); $ext = pathinfo($attachmentData['name'], PATHINFO_EXTENSION);
$mimeType = MimeMagic::ext2mime($ext); $attachmentData['type'] = MimeMagic::ext2mime($ext);
if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; } if ( strlen($directory) > 1 && !str_ends_with($directory, '/')) { $directory .= '/'; }
$myUrl = $directory.$filename; $myUrl = $directory.$attachmentData['name'];
if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs) if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
{ {
$basedir = Framework::getUrl('/'); $basedir = Framework::getUrl('/');
@ -6801,7 +6797,7 @@ class Mail
// use vfs instead of url containing webdav.php // use vfs instead of url containing webdav.php
// ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign // ToDo: we should test if the webdav url is of our own scope, as we cannot handle foreign
// webdav.php urls as vfs // webdav.php urls as vfs
if (strpos($myUrl,'/webdav.php') !== false) // we have a webdav link, so we build a vfs/sqlfs link of it. if (str_contains($myUrl, '/webdav.php')) // we have a webdav link, so we build a vfs/sqlfs link of it.
{ {
Vfs::load_wrapper('vfs'); Vfs::load_wrapper('vfs');
list(,$myUrl) = explode('/webdav.php',$myUrl,2); list(,$myUrl) = explode('/webdav.php',$myUrl,2);
@ -6811,7 +6807,7 @@ class Mail
// If it is an inline image url, we need to fetch the actuall attachment // If it is an inline image url, we need to fetch the actuall attachment
// content and later on to be able to store its content as temp file // content and later on to be able to store its content as temp file
if (strpos($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage') !== false && $mail_bo) if ($mail_bo && str_contains($myUrl, '/index.php?menuaction=mail.mail_ui.displayImage'))
{ {
$URI_params = array(); $URI_params = array();
// Strips the url and store it into a temp for further procss // Strips the url and store it into a temp for further procss
@ -6826,50 +6822,50 @@ class Mail
if ($attachment) if ($attachment)
{ {
$data = $attachment->getContents(); $data = $attachment->getContents();
$mimeType = $attachment->getType(); $attachmentData['type'] = $attachment->getType();
$filename = $attachment->getDispositionParameter('filename'); $attachmentData['name'] = $attachment->getDispositionParameter('filename');
} }
} }
} }
if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; } if ( $myUrl[0]!='/' && strlen($basedir) > 1 && !str_ends_with($basedir, '/')) { $basedir .= '/'; }
if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl)); if ($needTempFile && !$attachment && !str_starts_with($myUrl, "http")) $data = file_get_contents($basedir.urldecode($myUrl));
} }
if (substr($url,0,strlen('data:'))=='data:') if (str_starts_with($url, 'data:'))
{ {
//error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i])); //error_log(__METHOD__.' ('.__LINE__.') '.' -> '.$i.': '.array2string($images[$i]));
// we only support base64 encoded data // we only support base64 encoded data
$tmp = substr($url,strlen('data:')); $tmp = substr($url,strlen('data:'));
list($mimeType,$data_base64) = explode(';base64,',$tmp); list($attachmentData['type'],$data_base64) = explode(';base64,',$tmp);
$data = base64_decode($data_base64); $data = base64_decode($data_base64);
// FF currently does NOT add any mime-type // FF currently does NOT add any mime-type
if (strtolower(substr($mimeType, 0, 6)) != 'image/') if (strtolower(substr($attachmentData['type'], 0, 6)) != 'image/')
{ {
$mimeType = MimeMagic::analyze_data($data); $attachmentData['type'] = MimeMagic::analyze_data($data);
} }
list($what,$exactly) = explode('/',$mimeType); list($what,$exactly) = explode('/',$attachmentData['type']);
$needTempFile = true; $needTempFile = true;
$filename = ($what?$what:'data').$imageC++.'.'.$exactly; $attachmentData['name'] = ($what ?: 'data').$imageC++.'.'.$exactly;
} }
if ($data || $needTempFile === false) if ($data || $needTempFile === false)
{ {
if ($needTempFile) if ($needTempFile)
{ {
$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_"); $attachmentData['file'] =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
$tmpfile = fopen($attachment_file,'w'); $tmpfile = fopen($attachmentData['file'],'w');
fwrite($tmpfile,$data); fwrite($tmpfile,$data);
fclose($tmpfile); fclose($tmpfile);
} }
else else
{ {
$attachment_file = $basedir.urldecode($myUrl); $attachmentData['file'] = $basedir.urldecode($myUrl);
} }
// we use $attachment_file as base for cid instead of filename, as it may be image.png // we use $attachmentData['file'] as base for cid instead of filename, as it may be image.png
// (or similar) in all cases (when cut&paste). This may lead to more attached files, in case // (or similar) in all cases (when cut&paste). This may lead to more attached files, in case
// we use the same image multiple times, but, if we do this, we should try to detect that // we use the same image multiple times, but, if we do this, we should try to detect that
// on upload. filename itself is not sufficient to determine the sameness of images // on upload. filename itself is not sufficient to determine the sameness of images
$cid = 'cid:' . md5($attachment_file); $cid = 'cid:' . md5($attachmentData['file']);
if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null) if ($_mailObject->AddEmbeddedImage($attachmentData['file'], substr($cid, 4), urldecode($attachmentData['file']), $attachmentData['type']) !== null)
{ {
//$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse); //$_html2parse = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $_html2parse);
$_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse); $_html2parse = str_replace($images[0][$i], $images[1][$i].'="'.$cid.'"', $_html2parse);
@ -6882,12 +6878,8 @@ class Mail
error_log("Error adding inline attachment. " . $e->getMessage()); error_log("Error adding inline attachment. " . $e->getMessage());
error_log($e->getTraceAsString()); error_log($e->getTraceAsString());
} }
$attachments [] = array( $attachmentData['tmp_name'] = $attachmentData['file'];
'name' => $filename, $attachments [] = $attachmentData;
'type' => $mimeType,
'file' => $attachment_file,
'tmp_name' => $attachment_file
);
} }
return is_array($attachments) ? $attachments : null; return is_array($attachments) ? $attachments : null;
} }

View File

@ -549,7 +549,7 @@ class Account implements \ArrayAccess
$row = array_merge($row, Credentials::from_session($row)); $row = array_merge($row, Credentials::from_session($row));
} }
// fill an empty ident_realname or ident_email of current user with data from user account // fill an empty ident_realname or ident_email of current user with data from user account
if ($replace_placeholders && (!isset($user) || $user == $GLOBALS['egw_info']['user']['acount_id'])) if ($replace_placeholders && (!isset($user) || $user == $GLOBALS['egw_info']['user']['account_id']))
{ {
if (empty($row['ident_realname'])) $row['ident_realname'] = $GLOBALS['egw_info']['user']['account_fullname']; if (empty($row['ident_realname'])) $row['ident_realname'] = $GLOBALS['egw_info']['user']['account_fullname'];
if (empty($row['ident_email'])) $row['ident_email'] = $GLOBALS['egw_info']['user']['account_email']; if (empty($row['ident_email'])) $row['ident_email'] = $GLOBALS['egw_info']['user']['account_email'];
@ -737,13 +737,13 @@ class Account implements \ArrayAccess
if (empty($data['ident_email']) && $is_current_user) if (empty($data['ident_email']) && $is_current_user)
{ {
$data['ident_email'] = $GLOBALS['egw_info']['user']['account_email']; $data['ident_email'] = $GLOBALS['egw_info']['user']['account_email'] ?? null;
} }
} }
if (empty($data['ident_realname'])) if (empty($data['ident_realname']))
{ {
$data['ident_realname'] = $account->ident_realname || !$is_current_user ? $data['ident_realname'] = $account->ident_realname || !$is_current_user ?
$account->ident_realname : $GLOBALS['egw_info']['user']['account_fullname']; $account->ident_realname : ($GLOBALS['egw_info']['user']['account_fullname'] ?? null);
} }
} }
} }
@ -1414,7 +1414,7 @@ class Account implements \ArrayAccess
{ {
// for current user prefer account with ident_email matching user email or domain // for current user prefer account with ident_email matching user email or domain
// (this also helps notifications to account allowing to send with from address of current user / account_email) // (this also helps notifications to account allowing to send with from address of current user / account_email)
if ($only_current_user && $GLOBALS['egw_info']['user']['account_email']) if ($only_current_user && !empty($GLOBALS['egw_info']['user']['account_email']))
{ {
list(,$domain) = explode('@', $account_email = $GLOBALS['egw_info']['user']['account_email']); list(,$domain) = explode('@', $account_email = $GLOBALS['egw_info']['user']['account_email']);
// empty ident_email will be replaced with account_email! // empty ident_email will be replaced with account_email!

View File

@ -265,10 +265,10 @@ class Credentials
throw new Api\Exception\WrongParameter("Unknown data[acc_imap_logintype]=".array2string($data['acc_imap_logintype']).'!'); throw new Api\Exception\WrongParameter("Unknown data[acc_imap_logintype]=".array2string($data['acc_imap_logintype']).'!');
} }
$password = base64_decode(Api\Cache::getSession('phpgwapi', 'password')); $password = base64_decode(Api\Cache::getSession('phpgwapi', 'password'));
$realname = !$set_identity || $data['ident_realname'] ? $data['ident_realname'] : $realname = !$set_identity || !empty($data['ident_realname']) ? $data['ident_realname'] :
$GLOBALS['egw_info']['user']['account_fullname']; ($GLOBALS['egw_info']['user']['account_fullname'] ?? null);
$email = !$set_identity || $data['ident_email'] ? $data['ident_email'] : $email = !$set_identity || !empty($data['ident_email']) ? $data['ident_email'] :
$GLOBALS['egw_info']['user']['account_email']; ($GLOBALS['egw_info']['user']['account_email'] ?? null);
return array( return array(
'ident_realname' => $realname, 'ident_realname' => $realname,

View File

@ -169,7 +169,7 @@ class Html
if ($addbracesforendtag === true ) if ($addbracesforendtag === true )
{ {
if (stripos($_body,'<'.$tag)!==false) $ct = preg_match_all('#<'.$tag.'(?:\s.*)?>(.+)</'.$endtag.'>#isU', $_body, $found); if (stripos($_body,'<'.$tag)!==false) $ct = preg_match_all('#<'.$tag.'(?:\s.*)?>(.+)</'.$endtag.'>#isU', $_body, $found);
if ($ct>0) if (isset($ct) && $ct>0)
{ {
//error_log(__METHOD__.__LINE__.array2string($found[0])); //error_log(__METHOD__.__LINE__.array2string($found[0]));
// only replace what we have found // only replace what we have found
@ -495,7 +495,7 @@ class Html
$html = preg_replace('/&(?!#?[a-zA-Z0-9]+;)/', '&amp;', $html); $html = preg_replace('/&(?!#?[a-zA-Z0-9]+;)/', '&amp;', $html);
$dom = new \DOMDocument('1.0','UTF-8'); $dom = new \DOMDocument('1.0','UTF-8');
if(!$dom->loadHTML( if (!@$dom->loadHTML(
'<?xml encoding="UTF-8">'. Api\Translation::convert($html,preg_match('/<meta[^>]+content="[^>"]+charset=([^;"]+)/i', $html, $matches) ? $matches[1] : false, 'utf8'), '<?xml encoding="UTF-8">'. Api\Translation::convert($html,preg_match('/<meta[^>]+content="[^>"]+charset=([^;"]+)/i', $html, $matches) ? $matches[1] : false, 'utf8'),
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS
)) ))

View File

@ -360,9 +360,7 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
*/ */
static function getTimeOut($_use='IMAP') static function getTimeOut($_use='IMAP')
{ {
$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout']; return $_use == 'SIEVE' ? 10 : 20; // this is the default value
if (empty($timeout) || !($timeout > 0)) $timeout = $_use == 'SIEVE' ? 10 : 20; // this is the default value
return $timeout;
} }
/** /**
@ -742,7 +740,7 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
* @param string $returnAttributes true means return an assoc array containing mailbox names and mailbox attributes * @param string $returnAttributes true means return an assoc array containing mailbox names and mailbox attributes
* false - the default - means return an array of mailboxes with only selected attributes like delimiter * false - the default - means return an array of mailboxes with only selected attributes like delimiter
* *
* @return mixed array of mailboxes * @return ?array array of mailboxes or null
*/ */
function listSubscribedMailboxes($reference = '' , $restriction_search = 0, $returnAttributes = false) function listSubscribedMailboxes($reference = '' , $restriction_search = 0, $returnAttributes = false)
{ {
@ -794,10 +792,10 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
} }
else else
{ {
$ret[$k]=array('MAILBOX'=>$k,'ATTRIBUTES'=>$box['attributes'],'delimiter'=>($box['delimiter']?$box['delimiter']:$this->getDelimiter('personal')),'SUBSCRIBED'=>true); $ret[$k]=array('MAILBOX'=>$k,'ATTRIBUTES'=>$box['attributes'],'delimiter'=>($box['delimiter']?:$this->getDelimiter('personal')),'SUBSCRIBED'=>true);
} }
} }
return $ret; return $ret ?? null;
} }
/** /**
@ -1376,6 +1374,7 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
case 'retrieveRules': case 'retrieveRules':
case 'getVacation': case 'getVacation':
case 'setVacation': case 'setVacation':
case 'getExtensions':
if (is_null($this->sieve)) if (is_null($this->sieve))
{ {
$this->sieve = new Sieve($this); $this->sieve = new Sieve($this);

View File

@ -73,7 +73,7 @@ class Notifications
$account_specific = 0; $account_specific = 0;
foreach($rows as $row) foreach($rows as $row)
{ {
if ($row['account_id']) if (!empty($row['account_id']))
{ {
$account_specific = $row['account_id']; $account_specific = $row['account_id'];
} }
@ -82,7 +82,7 @@ class Notifications
{ {
self::$cache[$acc_id][$row['account_id']][] = $row['notif_folder']; self::$cache[$acc_id][$row['account_id']][] = $row['notif_folder'];
} // make sure set the account_specific correctly when notify_folder gets removed } // make sure set the account_specific correctly when notify_folder gets removed
elseif (!$row['account_id'] && !is_array($account_id) && is_array($rows[$account_id])) elseif (empty($row['account_id']) && !is_array($account_id) && is_array($rows[$account_id]))
{ {
$account_specific = $account_id; $account_specific = $account_id;
} }

View File

@ -34,6 +34,7 @@ class Script
var $emailNotification; /* email notification settings. */ var $emailNotification; /* email notification settings. */
var $pcount; /* highest priority value in ruleset. */ var $pcount; /* highest priority value in ruleset. */
var $errstr; /* error text. */ var $errstr; /* error text. */
var $extensions; /* contains extensions status*/
/** /**
* Body transform content types * Body transform content types
* *
@ -69,6 +70,21 @@ class Script
$this->emailNotification = array(); // Added email notifications $this->emailNotification = array(); // Added email notifications
$this->pcount = 0; $this->pcount = 0;
$this->errstr = ''; $this->errstr = '';
$this->extensions = [];
}
private function _setExtensionsStatus(Sieve $connection)
{
$this->extensions = [
'vacation' => $connection->hasExtension('vacation'),
'regex' => $connection->hasExtension('regex'),
'enotify' => $connection->hasExtension('enotify'),
'body' => $connection->hasExtension('body'),
'variables' => $connection->hasExtension('variables'),
'date' => $connection->hasExtension('date'),
'imap4flags' => $connection->hasExtension('imap4flags'),
'relational' => $connection->hasExtension('relational'),
];
} }
// get sieve script rules for this user // get sieve script rules for this user
@ -86,6 +102,7 @@ class Script
$anyofbit = 4; $anyofbit = 4;
$keepbit = 8; $keepbit = 8;
$regexbit = 128; $regexbit = 128;
$this->_setExtensionsStatus($connection);
if (!isset($this->name)){ if (!isset($this->name)){
$this->errstr = 'retrieveRules: no script name specified'; $this->errstr = 'retrieveRules: no script name specified';
@ -150,10 +167,10 @@ class Script
$rule['anyof'] = ($bits[8] & $anyofbit); $rule['anyof'] = ($bits[8] & $anyofbit);
$rule['keep'] = ($bits[8] & $keepbit); $rule['keep'] = ($bits[8] & $keepbit);
$rule['regexp'] = ($bits[8] & $regexbit); $rule['regexp'] = ($bits[8] & $regexbit);
$rule['bodytransform'] = ($bits[12]); $rule['bodytransform'] = ($bits[12]??null);
$rule['field_bodytransform'] = ($bits[13]); $rule['field_bodytransform'] = ($bits[13]??null);
$rule['ctype'] = ($bits[14]); $rule['ctype'] = ($bits[14]??null);
$rule['field_ctype_val'] = ($bits[15]); $rule['field_ctype_val'] = ($bits[15]??null);
$rule['unconditional'] = 0; $rule['unconditional'] = 0;
if (!$rule['from'] && !$rule['to'] && !$rule['subject'] && if (!$rule['from'] && !$rule['to'] && !$rule['subject'] &&
!$rule['field'] && !$rule['size'] && $rule['action']) { !$rule['field'] && !$rule['size'] && $rule['action']) {
@ -188,7 +205,7 @@ class Script
} }
$vacation['addresses'] = &$vaddresses; $vacation['addresses'] = &$vaddresses;
$vacation['forwards'] = $bits[5]; $vacation['forwards'] = $bits[5]??null;
} }
break; break;
case "notify": case "notify":
@ -236,7 +253,6 @@ class Script
$activerules = 0; $activerules = 0;
$regexused = 0; $regexused = 0;
$regexsupported = true;
$rejectused = 0; $rejectused = 0;
$vacation_active = false; $vacation_active = false;
@ -245,13 +261,10 @@ class Script
//include "$default->lib_dir/version.php"; //include "$default->lib_dir/version.php";
// lets generate the main body of the script from our rules // set extensions status
$enotify = $variables= $supportsbody = false; $this->_setExtensionsStatus($connection);
if ($connection->hasExtension('enotify')) $enotify = true;
if ($connection->hasExtension('variables')) $variables = true; if (!$this->extensions['vacation']) $this->vacation = false;
if ($connection->hasExtension('body')) $supportsbody = true;
if (!$connection->hasExtension('vacation')) $this->vacation = false;
if (!$connection->hasExtension('regex')) $regexsupported = false;
$newscriptbody = ""; $newscriptbody = "";
$continue = 1; $continue = 1;
@ -334,7 +347,7 @@ class Script
$newruletext .= "size " . $xthan . $rule['size'] . "K"; $newruletext .= "size " . $xthan . $rule['size'] . "K";
$started = 1; $started = 1;
} }
if ($supportsbody){ if ($this->extensions['body']){
if (!empty($rule['field_bodytransform'])){ if (!empty($rule['field_bodytransform'])){
if ($started) $newruletext .= ", "; if ($started) $newruletext .= ", ";
$btransform = " :raw "; $btransform = " :raw ";
@ -379,6 +392,9 @@ class Script
if (preg_match("/discard/i",$rule['action'])) { if (preg_match("/discard/i",$rule['action'])) {
$newruletext .= "discard;"; $newruletext .= "discard;";
} }
if (preg_match("/flags/i",$rule['action'])) {
$newruletext .= "addflag \"".$rule['action_arg']."\";";
}
if ($rule['keep']) $newruletext .= "\n\tkeep;"; if ($rule['keep']) $newruletext .= "\n\tkeep;";
if (!$rule['unconditional']) $newruletext .= "\n}"; if (!$rule['unconditional']) $newruletext .= "\n}";
@ -417,7 +433,7 @@ class Script
$vacation_active = true; $vacation_active = true;
if ($vacation['text']) if ($vacation['text'])
{ {
if ($regexsupported) if ($this->extensions['regex'])
{ {
$newscriptbody .= "if header :regex ".'"X-Spam-Status" '.'"\\\\bYES\\\\b"'."{\n\tstop;\n}\n"; //stop vacation reply if it is spam $newscriptbody .= "if header :regex ".'"X-Spam-Status" '.'"\\\\bYES\\\\b"'."{\n\tstop;\n}\n"; //stop vacation reply if it is spam
$regexused = 1; $regexused = 1;
@ -441,17 +457,17 @@ class Script
} }
$newscriptbody .= "\tkeep;\n}\n"; $newscriptbody .= "\tkeep;\n}\n";
} }
$newscriptbody .= "vacation :days " . $vacation['days']; $vac_rule = "vacation :days " . $vacation['days'];
$first = 1; $first = 1;
if (!empty($vacation['addresses'][0])) if (!empty($vacation['addresses'][0]))
{ {
$newscriptbody .= " :addresses ["; $vac_rule .= " :addresses [";
foreach ($vacation['addresses'] as $vaddress) { foreach ($vacation['addresses'] as $vaddress) {
if (!$first) $newscriptbody .= ", "; if (!$first) $vac_rule .= ", ";
$newscriptbody .= "\"" . trim($vaddress) . "\""; $vac_rule .= "\"" . trim($vaddress) . "\"";
$first = 0; $first = 0;
} }
$newscriptbody .= "] "; $vac_rule .= "] ";
} }
$message = $vacation['text']; $message = $vacation['text'];
if ($vacation['start_date'] || $vacation['end_date']) if ($vacation['start_date'] || $vacation['end_date'])
@ -463,7 +479,20 @@ class Script
date($format_date,$vacation['end_date']), date($format_date,$vacation['end_date']),
),$message); ),$message);
} }
$newscriptbody .= " text:\n" . $message . "\n.\n;\n\n"; $vac_rule .= " text:\n" . $message . "\n.\n;\n\n";
if ($this->extensions['date'] && $vacation['start_date'] && $vacation['end_date'])
{
$newscriptbody .= "if allof (\n".
"currentdate :value \"ge\" \"date\" \"". date('Y-m-d', $vacation['start_date']) ."\",\n".
"currentdate :value \"le\" \"date\" \"". date('Y-m-d', $vacation['end_date']) ."\")\n".
"{\n".
$vac_rule."\n".
"}\n";
}
else
{
$newscriptbody .= $vac_rule;
}
} }
// update with any changes. // update with any changes.
@ -476,10 +505,10 @@ class Script
// format notification body // format notification body
$egw_site_title = $GLOBALS['egw_info']['server']['site_title']; $egw_site_title = $GLOBALS['egw_info']['server']['site_title'];
if ($enotify==true) if ($this->extensions['enotify']==true)
{ {
$notification_body = lang("You have received a new message on the")." {$egw_site_title}"; $notification_body = lang("You have received a new message on the")." {$egw_site_title}";
if ($variables) if ($this->extensions['variables'])
{ {
$notification_body .= ", "; $notification_body .= ", ";
$notification_body .= 'From: ${from}'; $notification_body .= 'From: ${from}';
@ -522,13 +551,18 @@ class Script
if ($activerules) { if ($activerules) {
$newscripthead .= "require [\"fileinto\""; $newscripthead .= "require [\"fileinto\"";
if ($regexsupported && $regexused) $newscripthead .= ",\"regex\""; if ($this->extensions['regex'] && $regexused) $newscripthead .= ",\"regex\"";
if ($rejectused) $newscripthead .= ",\"reject\""; if ($rejectused) $newscripthead .= ",\"reject\"";
if ($this->vacation && $vacation_active) { if ($this->vacation && $vacation_active) {
$newscripthead .= ",\"vacation\""; $newscripthead .= ",\"vacation\"";
} }
if ($supportsbody) $newscripthead .= ",\"body\""; if ($this->extensions['body']) $newscripthead .= ",\"body\"";
if ($this->emailNotification && $this->emailNotification['status'] == 'on') $newscripthead .= ',"'.($enotify?'e':'').'notify"'.($variables?',"variables"':''); // Added email notifications if ($this->extensions['date']) $newscripthead .= ",\"date\"";
if ($this->extensions['relational']) $newscripthead .= ",\"relational\"";
if ($this->extensions['variables']) $newscripthead .= ",\"variables\"";
if ($this->extensions['imap4flags']) $newscripthead .= ",\"imap4flags\"";
if ($this->emailNotification && $this->emailNotification['status'] == 'on') $newscripthead .= ',"'.($this->extensions['enotify']?'e':'').'notify"'.($this->extensions['variables']?',"variables"':''); // Added email notifications
$newscripthead .= "];\n\n"; $newscripthead .= "];\n\n";
} else { } else {
// no active rules, but might still have an active vacation rule // no active rules, but might still have an active vacation rule
@ -536,18 +570,21 @@ class Script
if ($this->vacation && $vacation_active) if ($this->vacation && $vacation_active)
{ {
$newscripthead .= "require [\"vacation\""; $newscripthead .= "require [\"vacation\"";
if ($regexsupported && $regexused) $newscripthead .= ",\"regex\""; if ($this->extensions['regex'] && $regexused) $newscripthead .= ",\"regex\"";
if ($this->extensions['date']) $newscripthead .= ",\"date\"";
if ($this->extensions['relational']) $newscripthead .= ",\"relational\"";
$closeRequired=true; $closeRequired=true;
} }
if ($this->emailNotification && $this->emailNotification['status'] == 'on') if ($this->emailNotification && $this->emailNotification['status'] == 'on')
{ {
if ($this->vacation && $vacation_active) if ($this->vacation && $vacation_active)
{ {
$newscripthead .= ",\"".($enotify?'e':'')."notify\"".($variables?',"variables"':'')."];\n\n"; // Added email notifications $newscripthead .= ",\"".($this->extensions['enotify']?'e':'')."notify\"".($this->extensions['variables']?',"variables"':'')."];\n\n"; // Added email notifications
} }
else else
{ {
$newscripthead .= "require [\"".($enotify?'e':'')."notify\"".($variables?',"variables"':'')."];\n\n"; // Added email notifications $newscripthead .= "require [\"".($this->extensions['enotify']?'e':'')."notify\"".($this->extensions['variables']?',"variables"':'')."];\n\n"; // Added email notifications
} }
} }
if ($closeRequired) $newscripthead .= "];\n\n"; if ($closeRequired) $newscripthead .= "];\n\n";
@ -570,7 +607,7 @@ class Script
$newscriptfoot .= "#rule&&" . $rule['priority'] . "&&" . $rule['status'] . "&&" . $newscriptfoot .= "#rule&&" . $rule['priority'] . "&&" . $rule['status'] . "&&" .
addslashes($rule['from']) . "&&" . addslashes($rule['to']) . "&&" . addslashes($rule['subject']) . "&&" . $rule['action'] . "&&" . addslashes($rule['from']) . "&&" . addslashes($rule['to']) . "&&" . addslashes($rule['subject']) . "&&" . $rule['action'] . "&&" .
$rule['action_arg'] . "&&" . $rule['flg'] . "&&" . addslashes($rule['field']) . "&&" . addslashes($rule['field_val']) . "&&" . $rule['size']; $rule['action_arg'] . "&&" . $rule['flg'] . "&&" . addslashes($rule['field']) . "&&" . addslashes($rule['field_val']) . "&&" . $rule['size'];
if ($supportsbody && (!empty($rule['field_bodytransform']) || ($rule['ctype']!= '0' && !empty($rule['ctype'])))) $newscriptfoot .= "&&" . $rule['bodytransform'] . "&&" . $rule['field_bodytransform']. "&&" . $rule['ctype'] . "&&" . $rule['field_ctype_val']; if ($this->extensions['body'] && (!empty($rule['field_bodytransform']) || ($rule['ctype']!= '0' && !empty($rule['ctype'])))) $newscriptfoot .= "&&" . $rule['bodytransform'] . "&&" . $rule['field_bodytransform']. "&&" . $rule['ctype'] . "&&" . $rule['field_ctype_val'];
$newscriptfoot .= "\n"; $newscriptfoot .= "\n";
$pcount = $pcount+2; $pcount = $pcount+2;
//error_log(__CLASS__."::".__METHOD__.__LINE__.array2string($newscriptfoot)); //error_log(__CLASS__."::".__METHOD__.__LINE__.array2string($newscriptfoot));
@ -616,7 +653,7 @@ class Script
} }
catch (\Exception $e) { catch (\Exception $e) {
$this->errstr = 'updateScript: putscript failed: ' . $e->getMessage().($e->details?': '.$e->details:''); $this->errstr = 'updateScript: putscript failed: ' . $e->getMessage().($e->details?': '.$e->details:'');
if ($regexused&&!$regexsupported) $this->errstr .= " REGEX is not an supported CAPABILITY"; if ($regexused && !$this->extensions['regex']) $this->errstr .= " REGEX is not an supported CAPABILITY";
error_log(__METHOD__.__LINE__.' # Error: ->'.$this->errstr); error_log(__METHOD__.__LINE__.' # Error: ->'.$this->errstr);
error_log(__METHOD__.__LINE__.' # ScriptName:'.$this->name.' Script:'.$newscript); error_log(__METHOD__.__LINE__.' # ScriptName:'.$this->name.' Script:'.$newscript);
error_log(__METHOD__.__LINE__.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']); error_log(__METHOD__.__LINE__.' # Instance='.$GLOBALS['egw_info']['user']['domain'].', User='.$GLOBALS['egw_info']['user']['account_lid']);

View File

@ -94,7 +94,7 @@ class Sql extends Mail\Smtp
); );
} }
} }
if ($this->debug) error_log(__METHOD__."('$_accountName') returning ".array2string($emailAddresses)); if (!empty($this->debug)) error_log(__METHOD__."('$_accountName') returning ".array2string($emailAddresses));
return $emailAddresses; return $emailAddresses;
} }
@ -191,7 +191,7 @@ class Sql extends Mail\Smtp
case self::TYPE_MAILBOX: case self::TYPE_MAILBOX:
$userData['mailMessageStore'] = $row['mail_value']; $userData['mailMessageStore'] = $row['mail_value'];
//error_log(__METHOD__."('$user') row=".array2string($row).', enabled[$row[account_id]]='.array2string($enabled[$row['account_id']]).', forwardOnly[$row[account_id]]='.array2string($forwardOnly[$row['account_id']])); //error_log(__METHOD__."('$user') row=".array2string($row).', enabled[$row[account_id]]='.array2string($enabled[$row['account_id']]).', forwardOnly[$row[account_id]]='.array2string($forwardOnly[$row['account_id']]));
if ($row['account_id'] > 0 && $enabled[$row['account_id']] && !$forwardOnly[$row['account_id']]) if ($row['account_id'] > 0 && !empty($enabled[$row['account_id']]) && empty($forwardOnly[$row['account_id']]))
{ {
$userData['uid'][] = $this->accounts->id2name($row['account_id'], 'account_lid'); $userData['uid'][] = $this->accounts->id2name($row['account_id'], 'account_lid');
$userData['mailbox'][] = $row['mail_value']; $userData['mailbox'][] = $row['mail_value'];
@ -218,7 +218,7 @@ class Sql extends Mail\Smtp
} }
} }
} }
if ($this->debug) error_log(__METHOD__."('$user') returning ".array2string($userData)); if (!empty($this->debug)) error_log(__METHOD__."('$user') returning ".array2string($userData));
return $userData; return $userData;
} }
@ -240,7 +240,7 @@ class Sql extends Mail\Smtp
function setUserData($_uidnumber, array $_mailAlternateAddress, array $_mailForwardingAddress, $_deliveryMode, function setUserData($_uidnumber, array $_mailAlternateAddress, array $_mailForwardingAddress, $_deliveryMode,
$_accountStatus, $_mailLocalAddress, $_quota, $_forwarding_only=false, $_setMailbox=null) $_accountStatus, $_mailLocalAddress, $_quota, $_forwarding_only=false, $_setMailbox=null)
{ {
if ($this->debug) error_log(__METHOD__."($_uidnumber, ".array2string($_mailAlternateAddress).', '.array2string($_mailForwardingAddress).", '$_deliveryMode', '$_accountStatus', '$_mailLocalAddress', $_quota, forwarding_only=".array2string($_forwarding_only).') '.function_backtrace()); if (!empty($this->debug)) error_log(__METHOD__."($_uidnumber, ".array2string($_mailAlternateAddress).', '.array2string($_mailForwardingAddress).", '$_deliveryMode', '$_accountStatus', '$_mailLocalAddress', $_quota, forwarding_only=".array2string($_forwarding_only).') '.function_backtrace());
if (!$_forwarding_only && $this->accounts->id2name($_uidnumber, 'account_email') !== $_mailLocalAddress) if (!$_forwarding_only && $this->accounts->id2name($_uidnumber, 'account_email') !== $_mailLocalAddress)
{ {

View File

@ -564,7 +564,7 @@ class Mailer extends Horde_Mime_Mail
if (!isset($flowed)) $flowed = $this->_body && !in_array($this->_body->getType(), array('multipart/encrypted', 'multipart/signed')); if (!isset($flowed)) $flowed = $this->_body && !in_array($this->_body->getType(), array('multipart/encrypted', 'multipart/signed'));
// check if flowed is disabled in mail site configuration // check if flowed is disabled in mail site configuration
if (($config = Config::read('mail')) && $config['disable_rfc3676_flowed']) if (($config = Config::read('mail')) && !empty($config['disable_rfc3676_flowed']))
{ {
$flowed = false; $flowed = false;
} }
@ -616,7 +616,7 @@ class Mailer extends Horde_Mime_Mail
} }
// log mails to file specified in $GLOBALS['egw_info']['server']['log_mail'] or error_log for true // log mails to file specified in $GLOBALS['egw_info']['server']['log_mail'] or error_log for true
if ($GLOBALS['egw_info']['server']['log_mail']) if (!empty($GLOBALS['egw_info']['server']['log_mail']))
{ {
$msg = $GLOBALS['egw_info']['server']['log_mail'] !== true ? date('Y-m-d H:i:s')."\n" : ''; $msg = $GLOBALS['egw_info']['server']['log_mail'] !== true ? date('Y-m-d H:i:s')."\n" : '';
$msg .= (!isset($e) ? 'Mail send' : 'Mail NOT send'). $msg .= (!isset($e) ? 'Mail send' : 'Mail NOT send').
@ -732,7 +732,7 @@ class Mailer extends Horde_Mime_Mail
$recipients->add($h->getAddressList()); $recipients->add($h->getAddressList());
} }
} }
if ($this->_bcc) { if (!empty($this->_bcc)) {
$recipients->add($this->_bcc); $recipients->add($this->_bcc);
} }

View File

@ -1538,7 +1538,7 @@ class Session
} }
// check if the url already contains a query and ensure that vars is an array and all strings are in extravars // check if the url already contains a query and ensure that vars is an array and all strings are in extravars
if (strpos($ret_url=$url, '?') !== false) list($ret_url,$othervars) = explode('?', $url, 2)+[null,null]; list($ret_url,$othervars) = explode('?', $url, 2)+[null,null];
if ($extravars && is_array($extravars)) if ($extravars && is_array($extravars))
{ {
$vars += $extravars; $vars += $extravars;

View File

@ -674,7 +674,7 @@ class Storage extends Storage\Base
elseif (is_string($name) && $val!=null && in_array($name, $this->db_cols)) elseif (is_string($name) && $val!=null && in_array($name, $this->db_cols))
{ {
$extra_columns = $this->db->get_table_definitions($this->app, $this->extra_table); $extra_columns = $this->db->get_table_definitions($this->app, $this->extra_table);
if ($extra_columns['fd'][array_search($name, $this->db_cols)]) if (!empty($extra_columns['fd'][array_search($name, $this->db_cols)]))
{ {
$filter[] = $this->db->expression($this->table_name,$this->table_name.'.',array( $filter[] = $this->db->expression($this->table_name,$this->table_name.'.',array(
array_search($name, $this->db_cols) => $val, array_search($name, $this->db_cols) => $val,

View File

@ -1034,14 +1034,14 @@ class Base
$this->total = $this->db->select($this->table_name,$colums,$query,__LINE__,__FILE__,false,$order_by,false,0,$join)->NumRows(); $this->total = $this->db->select($this->table_name,$colums,$query,__LINE__,__FILE__,false,$order_by,false,0,$join)->NumRows();
} }
} }
$rs = $this->db->select($this->table_name,$mysql_calc_rows.$colums,$query,__LINE__,__FILE__, $rs = $this->db->select($this->table_name,($mysql_calc_rows??'').$colums,$query,__LINE__,__FILE__,
$start,$order_by,$this->app,$num_rows,$join); $start,$order_by,$this->app,$num_rows,$join);
if ($this->debug) error_log(__METHOD__."() ".$this->db->Query_ID->sql); if ($this->debug) error_log(__METHOD__."() ".$this->db->Query_ID->sql);
$cols = $this->_get_columns($only_keys,$extra_cols); $cols = $this->_get_columns($only_keys,$extra_cols);
} }
if ((int) $this->debug >= 4) echo "<p>sql='{$this->db->Query_ID->sql}'</p>\n"; if ((int) $this->debug >= 4) echo "<p>sql='{$this->db->Query_ID->sql}'</p>\n";
if ($mysql_calc_rows) if (!empty($mysql_calc_rows))
{ {
$this->total = $this->db->query('SELECT FOUND_ROWS()')->fetchColumn(); $this->total = $this->db->query('SELECT FOUND_ROWS()')->fetchColumn();
} }
@ -1157,8 +1157,8 @@ class Base
} }
} }
} }
if (is_array($query) && $op != 'AND') $query = $this->db->column_data_implode(' '.$op.' ',$query); if (!empty($query) && is_array($query) && $op != 'AND') $query = $this->db->column_data_implode(' '.$op.' ',$query);
return $query; return $query ?? null;
} }
/** /**

View File

@ -190,7 +190,7 @@ class Customfields implements \IteratorAggregate
/** /**
* Format a single custom field value as string * Format a single custom field value as string
* *
* @param array $field field defintion incl. type * @param array $field field definition incl. type
* @param string $value field value * @param string $value field value
* @return string formatted value * @return string formatted value
*/ */
@ -204,7 +204,7 @@ class Customfields implements \IteratorAggregate
$values = array(); $values = array();
foreach($field['rows'] > 1 ? explode(',', $value) : (array) $value as $value) foreach($field['rows'] > 1 ? explode(',', $value) : (array) $value as $value)
{ {
$values[] = Api\Accounts::username($value); $values[] = is_numeric($value) ? Api\Accounts::username($value) : $value;
} }
$value = implode(', ',$values); $value = implode(', ',$values);
} }

File diff suppressed because it is too large Load Diff

View File

@ -283,7 +283,7 @@ abstract class Tracking
//error_log(__METHOD__."() $name: data['#$name']=".array2string($data['#'.$name]).", field[values]=".array2string($field['values'])); //error_log(__METHOD__."() $name: data['#$name']=".array2string($data['#'.$name]).", field[values]=".array2string($field['values']));
$details['#'.$name] = array( $details['#'.$name] = array(
'label' => $field['label'], 'label' => $field['label'],
'value' => Customfields::format($field, $data['#'.$name]), 'value' => Customfields::format($field, $data['#'.$name] ?? null),
); );
//error_log("--> details['#$name']=".array2string($details['#'.$name])); //error_log("--> details['#$name']=".array2string($details['#'.$name]));
} }
@ -636,13 +636,13 @@ abstract class Tracking
{ {
//error_log(__METHOD__."() data[$this->assigned_field]=".print_r($data[$this->assigned_field],true).", old[$this->assigned_field]=".print_r($old[$this->assigned_field],true)); //error_log(__METHOD__."() data[$this->assigned_field]=".print_r($data[$this->assigned_field],true).", old[$this->assigned_field]=".print_r($old[$this->assigned_field],true));
$old_assignees = array(); $old_assignees = array();
$assignees = $assigned ? $assigned : array(); $assignees = $assigned ?? array();
if ($data[$this->assigned_field]) // current assignments if (!empty($data[$this->assigned_field])) // current assignments
{ {
$assignees = is_array($data[$this->assigned_field]) ? $assignees = is_array($data[$this->assigned_field]) ?
$data[$this->assigned_field] : explode(',',$data[$this->assigned_field]); $data[$this->assigned_field] : explode(',',$data[$this->assigned_field]);
} }
if ($old && $old[$this->assigned_field]) if ($old && !empty($old[$this->assigned_field]))
{ {
$old_assignees = is_array($old[$this->assigned_field]) ? $old_assignees = is_array($old[$this->assigned_field]) ?
$old[$this->assigned_field] : explode(',',$old[$this->assigned_field]); $old[$this->assigned_field] : explode(',',$old[$this->assigned_field]);
@ -1050,7 +1050,7 @@ abstract class Tracking
// remove the session-id in the notification mail! // remove the session-id in the notification mail!
$link = preg_replace('/(sessionid|kp3|domain)=[^&]+&?/','',$link); $link = preg_replace('/(sessionid|kp3|domain)=[^&]+&?/','',$link);
if ($popup) $link .= '&nopopup=1'; if (!empty($popup)) $link .= '&nopopup=1';
} }
//error_log(__METHOD__."(..., $allow_popup, $receiver) returning ".array2string($allow_popup ? array($link,$popup) : $link)); //error_log(__METHOD__."(..., $allow_popup, $receiver) returning ".array2string($allow_popup ? array($link,$popup) : $link));
return $allow_popup ? array($link,$popup) : $link; return $allow_popup ? array($link,$popup) : $link;
@ -1123,22 +1123,22 @@ abstract class Tracking
{ {
// if there's no old entry, the entry is not modified by definition // if there's no old entry, the entry is not modified by definition
// if both values are '', 0 or null, we count them as equal too // if both values are '', 0 or null, we count them as equal too
$modified = $old && $data[$name] != $old[$name] && !(!$data[$name] && !$old[$name]); $modified = $old && ($data[$name] ?? null) != ($old[$name] ?? null) && !(empty($data[$name]) && empty($old[$name]));
//if ($modified) error_log("data[$name]=".print_r($data[$name],true).", old[$name]=".print_r($old[$name],true)." --> modified=".(int)$modified); //if ($modified) error_log("data[$name]=".print_r($data[$name],true).", old[$name]=".print_r($old[$name],true)." --> modified=".(int)$modified);
if (empty($detail['value']) && !$modified) continue; // skip unchanged, empty values if (empty($detail['value']) && !$modified) continue; // skip unchanged, empty values
$body .= $this->format_line($html_email,$detail['type'],$modified, $body .= $this->format_line($html_email, $detail['type'] ?? null, $modified,
$detail['label'] ? $detail['label'] : '', $detail['value']); $detail['label'] ?? '', $detail['value']);
} }
if ($html_email) if ($html_email)
{ {
$body .= "</table>\n"; $body .= "</table>\n";
} }
if(($sig = $this->get_signature($data,$old,$receiver))) if (($sig = $this->get_signature($data,$old,$receiver)))
{ {
$body .= ($html_email ? '<br />':'') . "\n$sig"; $body .= ($html_email ? '<br />':'') . "\n$sig";
} }
if (!$html_email && $data['tr_edit_mode'] == 'html') if (!$html_email && isset($data['tr_edit_mode']) && $data['tr_edit_mode'] === 'html')
{ {
$body = Api\Mail\Html::convertHTMLToText($body); $body = Api\Mail\Html::convertHTMLToText($body);
} }
@ -1271,7 +1271,7 @@ abstract class Tracking
$merge_class = $this->app.'_merge'; $merge_class = $this->app.'_merge';
$merge = new $merge_class(); $merge = new $merge_class();
$error = null; $error = null;
$sig = $merge->merge_string($config['signature'], array($data[$this->id_field]), $error, 'text/html'); $sig = $merge->merge_string($config['signature']??null, array($data[$this->id_field]), $error, 'text/html');
if($error) if($error)
{ {
error_log($error); error_log($error);

View File

@ -335,21 +335,20 @@ class Base
$url = str_replace($matches[0], $matches[1] . Vfs::concat($matches[2], substr($parts['path'], strlen($mounted))), $url); $url = str_replace($matches[0], $matches[1] . Vfs::concat($matches[2], substr($parts['path'], strlen($mounted))), $url);
} }
if($replace_user_pass_host) if ($replace_user_pass_host)
{ {
$url = str_replace(array('$user', $url = strtr($url, [
'$pass', '$user' => $parts['user'],
'$host', '$pass' => $parts['pass'],
'$home'), array($parts['user'], '$host' => $parts['host'],
$parts['pass'], '$home' => $parts['home'],
$parts['host'], ]);
$parts['home']), $url);
} }
if($parts['query']) if (isset($parts['query']))
{ {
$url .= '?' . $parts['query']; $url .= '?' . $parts['query'];
} }
if($parts['fragment']) if (isset($parts['fragment']))
{ {
$url .= '#' . $parts['fragment']; $url .= '#' . $parts['fragment'];
} }
@ -657,7 +656,7 @@ class Base
return false; return false;
} }
$k = (string)Vfs::parse_url($url, PHP_URL_SCHEME); $k = (string)Vfs::parse_url($url, PHP_URL_SCHEME);
if(!(is_array($scheme2urls[$k]))) if (!isset($scheme2urls[$k]))
{ {
$scheme2urls[$k] = array(); $scheme2urls[$k] = array();
} }

View File

@ -798,11 +798,11 @@ class StreamWrapper extends Base implements StreamWrapperIface
{ {
$stat['url'] = $url; $stat['url'] = $url;
} }
if (($stat['mode'] & 0222) && self::url_is_readonly($stat['url'])) if ($stat && ($stat['mode'] & 0222) && self::url_is_readonly($stat['url']))
{ {
$stat['mode'] &= ~0222; $stat['mode'] &= ~0222;
} }
if($stat['url'] && $query && strpos($stat['url'],'?'.$query)===false) if ($stat && $stat['url'] && $query && strpos($stat['url'],'?'.$query) === false)
{ {
$stat['url'] .= '?'.$query; $stat['url'] .= '?'.$query;
} }
@ -998,7 +998,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
} }
else else
{ {
$vfs_fstab = $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab']; $vfs_fstab = $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'] ?? [];
} }
if (!empty($vfs_fstab) && is_array($vfs_fstab)) if (!empty($vfs_fstab) && is_array($vfs_fstab))
{ {

View File

@ -71,6 +71,7 @@ function _egw_log_exception($e,&$headline=null)
{ {
error_log($headline.($e instanceof egw_exception_warning ? ': ' : ' ('.get_class($e).'): '). error_log($headline.($e instanceof egw_exception_warning ? ': ' : ' ('.get_class($e).'): ').
$e->getMessage().(!empty($e->details) ? ': '.$e->details : '')); $e->getMessage().(!empty($e->details) ? ': '.$e->details : ''));
error_log('File: '.str_replace(EGW_SERVER_ROOT, '', $e->getFile()).', Line: '.$e->getLine());
foreach($trace as $line) foreach($trace as $line)
{ {
error_log($line); error_log($line);
@ -103,6 +104,7 @@ function egw_exception_handler($e)
if(!isset($_SERVER['HTTP_HOST']) || $GLOBALS['egw_info']['flags']['no_exception_handler'] == 'cli') if(!isset($_SERVER['HTTP_HOST']) || $GLOBALS['egw_info']['flags']['no_exception_handler'] == 'cli')
{ {
echo ($headline ? $headline.': ' : '').$e->getMessage()."\n"; echo ($headline ? $headline.': ' : '').$e->getMessage()."\n";
echo $e->getFile().' ('.$e->getLine().")\n";
if ($GLOBALS['egw_info']['server']['exception_show_trace']) if ($GLOBALS['egw_info']['server']['exception_show_trace'])
{ {
echo $e->getTraceAsString()."\n"; echo $e->getTraceAsString()."\n";
@ -116,6 +118,8 @@ function egw_exception_handler($e)
$message = '<h3>'.Api\Html::htmlspecialchars($headline)."</h3>\n". $message = '<h3>'.Api\Html::htmlspecialchars($headline)."</h3>\n".
'<pre><b>'.Api\Html::htmlspecialchars($e->getMessage())."</b>\n\n"; '<pre><b>'.Api\Html::htmlspecialchars($e->getMessage())."</b>\n\n";
echo $e->getFile().' ('.$e->getLine().")\n";
// only show trace (incl. function arguments) if explicitly enabled, eg. on a development system // only show trace (incl. function arguments) if explicitly enabled, eg. on a development system
if ($GLOBALS['egw_info']['server']['exception_show_trace']) if ($GLOBALS['egw_info']['server']['exception_show_trace'])
{ {
@ -174,6 +178,7 @@ function egw_error_handler ($errno, $errstr, $errfile, $errline)
{ {
case E_RECOVERABLE_ERROR: case E_RECOVERABLE_ERROR:
case E_USER_ERROR: case E_USER_ERROR:
error_log(__METHOD__."($errno, '$errstr', '$errfile', $errline)");
throw new ErrorException($errstr, $errno, 0, $errfile, $errline); throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
case E_WARNING: case E_WARNING:

View File

@ -175,6 +175,8 @@ function php_safe_unserialize($str)
*/ */
function json_php_unserialize($str, $allow_not_serialized=false) function json_php_unserialize($str, $allow_not_serialized=false)
{ {
if (!isset($str)) return $str;
if ((in_array($str[0], array('a', 'i', 's', 'b', 'O', 'C')) && $str[1] == ':' || $str === 'N;') && if ((in_array($str[0], array('a', 'i', 's', 'b', 'O', 'C')) && $str[1] == ':' || $str === 'N;') &&
($arr = php_safe_unserialize($str)) !== false || $str === 'b:0;') ($arr = php_safe_unserialize($str)) !== false || $str === 'b:0;')
{ {

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
<!-- $Id$ -->
<overlay>
<template id="api.show_replacements.placeholder_list">
<description id="title" class="title"/>
<grid id="placeholders" width="100%">
<columns>
<column width="30%"/>
<column/>
</columns>
<rows>
<row>
<description id="${row}[value]"/>
<description id="${row}[label]"/>
</row>
</rows>
</grid>
</template>
<template id="api.show_replacements" template="" lang="" group="0" version="21.1.001">
<vbox>
<description value="Placeholders" class="group title"/>
<box id="placeholders">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
<template template="@extra_template"/>
<details title="Common">
<description value="Common" class="group title"/>
<box id="common">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
<details title="Current user">
<description value="Current user" class="group title"/>
<box id="user">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
</vbox>
<styles>
.et2_details_title, .title {
display: inline-block;
font-weight: bold;
font-size: 130%;
margin-top: 2ex;
}
.et2_details_title, .group {
margin-top: 3ex;
font-size: 150%;
}
/** Cosmetics **/
#api-show_replacements_title:first-letter, .title {
text-transform: capitalize;
}
</styles>
</template>
</overlay>

View File

@ -1634,7 +1634,7 @@ class calendar_boupdate extends calendar_bo
$memberships = $GLOBALS['egw']->accounts->memberships($uid,true); $memberships = $GLOBALS['egw']->accounts->memberships($uid,true);
} }
$memberships[] = $uid; $memberships[] = $uid;
return array_intersect($memberships, array_keys($event['participants'])) && $this->check_perms(Acl::EDIT,0,$uid); return array_intersect($memberships, array_keys($event['participants'] ?? [])) && $this->check_perms(Acl::EDIT,0,$uid);
} }
/** /**

View File

@ -660,31 +660,8 @@ class calendar_hooks
// Merge print // Merge print
if ($GLOBALS['egw_info']['user']['apps']['filemanager']) if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{ {
$settings['default_document'] = array( $merge = new calendar_merge();
'type' => 'vfs_file', $settings += $merge->merge_preferences();
'size' => 60,
'label' => 'Default document to insert entries',
'name' => 'default_document',
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.',lang('calendar')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','calendar_title').' '.
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
);
$settings['document_dir'] = array(
'type' => 'vfs_dirs',
'size' => 60,
'label' => 'Directory with documents to insert entries',
'name' => 'document_dir',
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.',lang('calendar')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','calendar_title').' '.
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
'default' => '/templates/calendar',
);
} }
$settings += array( $settings += array(

View File

@ -3082,7 +3082,7 @@ class calendar_ical extends calendar_boupdate
// check if json_encoded attribute is to big for our table // check if json_encoded attribute is to big for our table
if (($attributes['params'] || count($attributes['values']) > 1) && if (($attributes['params'] || count($attributes['values']) > 1) &&
strlen($event['##'.$attributes['name']]) > strlen($event['##'.$attributes['name']]) >
Api\Db::get_column_attribute('cal_extra_value', 'egw_cal_extra', 'calendar', 'precision')) $GLOBALS['egw']->db->get_column_attribute('cal_extra_value', 'egw_cal_extra', 'calendar', 'precision'))
{ {
// store content compressed (Outlook/Exchange HTML garbadge is very good compressable) // store content compressed (Outlook/Exchange HTML garbadge is very good compressable)
if (function_exists('gzcompress')) if (function_exists('gzcompress'))
@ -3093,7 +3093,7 @@ class calendar_ical extends calendar_boupdate
} }
// if that's not enough --> unset it, as truncating the json gives nothing // if that's not enough --> unset it, as truncating the json gives nothing
if (strlen($event['##'.$attributes['name']]) > if (strlen($event['##'.$attributes['name']]) >
Api\Db::get_column_attribute('cal_extra_value', 'egw_cal_extra', 'calendar', 'precision')) $GLOBALS['egw']->db->get_column_attribute('cal_extra_value', 'egw_cal_extra', 'calendar', 'precision'))
{ {
unset($event['##'.$attributes['name']]); unset($event['##'.$attributes['name']]);
} }

File diff suppressed because it is too large Load Diff

View File

@ -878,7 +878,14 @@ class calendar_so
$where[] = "$this->user_table.cal_recur_date=0"; $where[] = "$this->user_table.cal_recur_date=0";
$cols = str_replace(array('cal_start','cal_end'),array('range_start AS cal_start','(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id) AS cal_end'),$cols); $cols = str_replace(array('cal_start','cal_end'),array('range_start AS cal_start','(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id) AS cal_end'),$cols);
// in case cal_start is used in a query, eg. calendar_ical::find_event // in case cal_start is used in a query, eg. calendar_ical::find_event
$where = str_replace(array('cal_start','cal_end'), array('range_start','(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id)'), $where); // in contrary to the docu on php.net, 3rd parameter can not be an array: https://3v4l.org/budKH
foreach($where as &$val)
{
if (!is_array($val))
{
$val = str_replace(array('cal_start','cal_end'), array('range_start','(SELECT MIN(cal_end) FROM egw_cal_dates WHERE egw_cal.cal_id=egw_cal_dates.cal_id)'), $val);
}
}
$params['order'] = str_replace('cal_start', 'range_start', $params['order']); $params['order'] = str_replace('cal_start', 'range_start', $params['order']);
if ($end) $where[] = (int)$end.' > range_start'; if ($end) $where[] = (int)$end.' > range_start';
} }

View File

@ -642,16 +642,20 @@ class calendar_ui
* *
* @param int $event_id * @param int $event_id
* @param Api\DateTime $recurrence_date * @param Api\DateTime $recurrence_date
* @param array|bool|int|null $old_event
* *
* @return boolean True if the event was updated, false if it could not be * @return boolean True if the event was updated, false if it could not be
* updated or was removed. * updated or was removed.
*/ */
public function update_client($event_id, Api\DateTime $recurrence_date = null) public function update_client($event_id, Api\DateTime $recurrence_date = null, $old_event = array())
{ {
if(!$event_id) return false; if(!$event_id)
if(is_string($event_id) && strpos($event_id,':') !== FALSE)
{ {
list($event_id, $date) = explode(':',$event_id); return false;
}
if(is_string($event_id) && strpos($event_id, ':') !== FALSE)
{
list($event_id, $date) = explode(':', $event_id);
$recurrence_date = new Api\DateTime($date); $recurrence_date = new Api\DateTime($date);
} }
@ -698,6 +702,21 @@ class calendar_ui
else if($event['recur_type'] ) else if($event['recur_type'] )
{ {
$this_month = new Api\DateTime('next month'); $this_month = new Api\DateTime('next month');
$data = [];
if($old_event && ($old_event['start'] != $event['start'] || $old_event['recur_enddate'] != $event['recur_enddate']))
{
// Set up to clear old events in case recurrence start/end date changed
$old_rrule = calendar_rrule::event2rrule($old_event, true);
$old_rrule->rewind();
do
{
$occurrence = $old_rrule->current();
$data['calendar::' . $old_event['id'] . ':' . $occurrence->format('ts')] = null;
$old_rrule->next();
}
while($old_rrule->valid() && $occurrence <= $this_month);
}
$rrule = calendar_rrule::event2rrule($event, true); $rrule = calendar_rrule::event2rrule($event, true);
$rrule->rewind(); $rrule->rewind();
do do
@ -705,10 +724,17 @@ class calendar_ui
$occurrence = $rrule->current(); $occurrence = $rrule->current();
$converted = $this->bo->read($event['id'], $occurrence); $converted = $this->bo->read($event['id'], $occurrence);
$this->to_client($converted); $this->to_client($converted);
$response->generic('data', array('uid' => 'calendar::'.$converted['row_id'], 'data' => $converted)); $data['calendar::' . $converted['row_id']] = $converted;
$rrule->next(); $rrule->next();
} }
while ($rrule->valid() && $occurrence <= $this_month ); while($rrule->valid() && $occurrence <= $this_month);
// Now we have to go through and send each one individually, since client side data can't handle more than one
foreach($data as $uid => $cal_data)
{
$response->apply('egw.dataStoreUID', [$uid, $cal_data]);
}
$response->apply('app.calendar.update_events', [array_keys($data)]);
} }
return true; return true;
} }

View File

@ -1007,7 +1007,7 @@ class calendar_uiforms extends calendar_ui
$response = Api\Json\Response::get(); $response = Api\Json\Response::get();
if($response && $update_type != 'delete' && !$client_updated) if($response && $update_type != 'delete' && !$client_updated)
{ {
$client_updated = $this->update_client($event['id']); $client_updated = $this->update_client($event['id'], null, is_array($old_event) ? $old_event : []);
} }
$msg = $message . ($msg ? ', ' . $msg : ''); $msg = $message . ($msg ? ', ' . $msg : '');
@ -1267,8 +1267,6 @@ class calendar_uiforms extends calendar_ui
Api\DateTime::to($as_of_date,'ts') < time() Api\DateTime::to($as_of_date,'ts') < time()
) )
{ {
unset($orig_event);
// copy event by unsetting the id(s) // copy event by unsetting the id(s)
unset($event['id']); unset($event['id']);
unset($event['uid']); unset($event['uid']);
@ -1325,7 +1323,8 @@ class calendar_uiforms extends calendar_ui
} }
$last->setTime(0, 0, 0); $last->setTime(0, 0, 0);
$old_event['recur_enddate'] = Api\DateTime::to($last, 'ts'); $old_event['recur_enddate'] = Api\DateTime::to($last, 'ts');
if (!$this->bo->update($old_event,true,true,false,true,$dummy=null,$no_notifications)) $dummy = null;
if (!$this->bo->update($old_event,true,true,false,true,$dummy, $no_notifications))
{ {
$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'. $msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'. lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
@ -1925,7 +1924,7 @@ class calendar_uiforms extends calendar_ui
$sel_options['owner'][$uid] = $this->bo->participant_name($uid); $sel_options['owner'][$uid] = $this->bo->participant_name($uid);
} }
} }
$content['no_add_alarm'] = !count($sel_options['owner']); // no rights to set any alarm $content['no_add_alarm'] = empty($sel_options['owner']) || !count((array)$sel_options['owner']); // no rights to set any alarm
if (!$event['id']) if (!$event['id'])
{ {
$etpl->set_cell_attribute('button[new_alarm]','type','checkbox'); $etpl->set_cell_attribute('button[new_alarm]','type','checkbox');
@ -2182,7 +2181,7 @@ class calendar_uiforms extends calendar_ui
} }
$user_and_memberships = $GLOBALS['egw']->accounts->memberships($user, true); $user_and_memberships = $GLOBALS['egw']->accounts->memberships($user, true);
$user_and_memberships[] = $user; $user_and_memberships[] = $user;
if (!array_intersect(array_keys($event['participants']), $user_and_memberships)) if (!array_intersect(array_keys($event['participants'] ?? []), $user_and_memberships))
{ {
$event['error'] .= ($event['error'] ? "\n" : '').lang('You are not invited to that event!'); $event['error'] .= ($event['error'] ? "\n" : '').lang('You are not invited to that event!');
if ($event['id']) if ($event['id'])

View File

@ -651,22 +651,34 @@ export class CalendarApp extends EgwApp
} }
// Do we already have "fresh" data? Most user actions give fresh data in response // Do we already have "fresh" data? Most user actions give fresh data in response
let existing = egw.dataGetUIDdata('calendar::'+pushData.id); let existing = egw.dataGetUIDdata('calendar::' + pushData.id);
if(existing && Math.abs(existing.timestamp - new Date().valueOf()) < 1000) if(existing && Math.abs(existing.timestamp - new Date().valueOf()) < 1000)
{ {
// Update directly // Update directly
this._update_events(this.state, ['calendar::'+pushData.id]); this._update_events(this.state, ['calendar::' + pushData.id]);
return; return;
}; }
;
// Ask for the real data, we don't have it // Ask for the real data, we don't have it
egw.request("calendar.calendar_ui.ajax_get", [[pushData.id]]).then((data) => let process_data = (data) =>
{ {
// Store it, which will call all registered listeners // Store it, which will call all registered listeners
egw.dataStoreUID(data.uid, data.data); egw.dataStoreUID(data.uid, data.data);
// Any existing events were updated. Run this to catch new events or events moved into view // Any existing events were updated. Run this to catch new events or events moved into view
this._update_events(this.state, [data.uid]); this._update_events(this.state, [data.uid]);
}
egw.request("calendar.calendar_ui.ajax_get", [[pushData.id]]).then((data) =>
{
if(typeof data.uid !== "undefined")
{
return process_data(data)
}
for(let e of data)
{
process_data(e);
}
}); });
} }
@ -3736,13 +3748,22 @@ export class CalendarApp extends EgwApp
else if(typeof framework !== 'undefined') else if(typeof framework !== 'undefined')
{ {
framework.applications.calendar.sidemenuEntry.hideAjaxLoader(); framework.applications.calendar.sidemenuEntry.hideAjaxLoader();
egw.loading_prompt('calendar',false) egw.loading_prompt('calendar', false)
} }
}, this,null }, this, null
); );
} }
/**
* We have a list of calendar UIDs of events that need updating.
* Public wrapper for _update_events so we can call it from server
*/
update_events(uids : string[])
{
return this._update_events(this.state, uids);
}
/** /**
* We have a list of calendar UIDs of events that need updating. * We have a list of calendar UIDs of events that need updating.
* *

View File

@ -46,6 +46,14 @@
"type": "pear", "type": "pear",
"url": "https://pear.horde.org" "url": "https://pear.horde.org"
}, },
{
"type": "vcs",
"url": "https://github.com/egroupware/Crypt"
},
{
"type": "vcs",
"url": "https://github.com/egroupware/Compress"
},
{ {
"type": "vcs", "type": "vcs",
"url": "https://github.com/IMSGlobal/lti-1-3-php-library" "url": "https://github.com/IMSGlobal/lti-1-3-php-library"
@ -81,6 +89,8 @@
"egroupware/adodb-php": "self.version", "egroupware/adodb-php": "self.version",
"egroupware/bookmarks": "self.version", "egroupware/bookmarks": "self.version",
"egroupware/collabora": "self.version", "egroupware/collabora": "self.version",
"egroupware/compress": "^2.2.3",
"egroupware/crypt": "^2.7.13",
"egroupware/guzzlestream": "dev-master", "egroupware/guzzlestream": "dev-master",
"egroupware/icalendar": "^2.1.9", "egroupware/icalendar": "^2.1.9",
"egroupware/magicsuggest": "^2.1", "egroupware/magicsuggest": "^2.1",
@ -99,8 +109,6 @@
"npm-asset/as-jqplot": "1.0.*", "npm-asset/as-jqplot": "1.0.*",
"npm-asset/gridster": "0.5.*", "npm-asset/gridster": "0.5.*",
"oomphinc/composer-installers-extender": "^1.1", "oomphinc/composer-installers-extender": "^1.1",
"pear-pear.horde.org/horde_compress": "^2.0.8",
"pear-pear.horde.org/horde_crypt": "^2.7.9",
"pear-pear.horde.org/horde_imap_client": "^2.30.3", "pear-pear.horde.org/horde_imap_client": "^2.30.3",
"pear-pear.horde.org/horde_mail": "^2.1.2", "pear-pear.horde.org/horde_mail": "^2.1.2",
"pear-pear.horde.org/horde_managesieve": "^1.0.2", "pear-pear.horde.org/horde_managesieve": "^1.0.2",

956
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -171,49 +171,19 @@ class filemanager_hooks
), ),
); );
$settings[Api\Storage\Merge::PREF_STORE_LOCATION] = array( $merge = new filemanager_merge();
'type' => 'vfs_dir', $settings += $merge->merge_preferences();
'size' => 60,
'label' => 'Directory for storing merged documents',
'name' => Api\Storage\Merge::PREF_STORE_LOCATION,
'help' => lang('When you merge entries into documents, they will be stored here. If no directory is provided, they will be stored in %1', Vfs::get_home_dir())
);
$settings['default_document'] = array(
'type' => 'vfs_file',
'size' => 60,
'label' => 'Default document to insert entries',
'name' => 'default_document',
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.', lang('filemanager')) . ' ' .
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'name') . ' ' .
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
);
$settings['document_dir'] = array(
'type' => 'vfs_dirs',
'size' => 60,
'label' => 'Directory with documents to insert entries',
'name' => 'document_dir',
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the %1 data inserted.', lang('filemanager')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','name').' '.
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
'default' => '/templates/filemanager',
);
$editorLink = self::getEditorLink(); $editorLink = self::getEditorLink();
$mimes = array('0' => lang('None')); $mimes = array('0' => lang('None'));
foreach ((array)$editorLink['mime'] as $mime => $value) foreach((array)$editorLink['mime'] as $mime => $value)
{ {
$mimes[$mime] = lang('%1 file', strtoupper($value['ext'])).' ('.$mime.')'; $mimes[$mime] = lang('%1 file', strtoupper($value['ext'])) . ' (' . $mime . ')';
if (!empty($value['extra_extensions'])) if(!empty($value['extra_extensions']))
{ {
$mimes[$mime] .= ', '.strtoupper(implode(', ', $value['extra_extensions'])); $mimes[$mime] .= ', ' . strtoupper(implode(', ', $value['extra_extensions']));
} }
} }

View File

@ -33,8 +33,7 @@ class filemanager_merge extends Api\Storage\Merge
/** /**
* Fields that are numeric, for special numeric handling * Fields that are numeric, for special numeric handling
*/ */
protected $numeric_fields = array( protected $numeric_fields = array();
);
/** /**
* Fields that are dates or timestamps * Fields that are dates or timestamps
@ -74,12 +73,12 @@ class filemanager_merge extends Api\Storage\Merge
* Get replacements * Get replacements
* *
* @param int $id id of entry * @param int $id id of entry
* @param string &$content=null content to create some replacements only if they are use * @param string &$content =null content to create some replacements only if they are use
* @return array|boolean * @return array|boolean
*/ */
protected function get_replacements($id,&$content=null) protected function get_replacements($id, &$content = null)
{ {
if (!($replacements = $this->filemanager_replacements($id, '', $content))) if(!($replacements = $this->filemanager_replacements($id, '', $content)))
{ {
return false; return false;
} }
@ -90,35 +89,35 @@ class filemanager_merge extends Api\Storage\Merge
* Get filemanager replacements * Get filemanager replacements
* *
* @param int $id id (vfs path) of entry * @param int $id id (vfs path) of entry
* @param string $prefix='' prefix like eg. 'erole' * @param string $prefix ='' prefix like eg. 'erole'
* @return array|boolean * @return array|boolean
*/ */
public function filemanager_replacements($id,$prefix='', &$content = null) public function filemanager_replacements($id, $prefix = '', &$content = null)
{ {
$info = array(); $info = array();
$file = Vfs::lstat($id,true); $file = Vfs::lstat($id, true);
$file['mtime'] = Api\DateTime::to($file['mtime']); $file['mtime'] = Api\DateTime::to($file['mtime']);
$file['ctime'] = Api\DateTime::to($file['ctime']); $file['ctime'] = Api\DateTime::to($file['ctime']);
$file['name'] = Vfs::basename($id); $file['name'] = Vfs::basename($id);
$file['dir'] = ($dir = Vfs::dirname($id)) ? Vfs::decodePath($dir) : ''; $file['dir'] = ($dir = Vfs::dirname($id)) ? Vfs::decodePath($dir) : '';
$dirlist = explode('/',$file['dir']); $dirlist = explode('/', $file['dir']);
$file['folder'] = array_pop($dirlist); $file['folder'] = array_pop($dirlist);
$file['folder_file'] = $file['folder'] . '/'.$file['name']; $file['folder_file'] = $file['folder'] . '/' . $file['name'];
$file['path'] = $id; $file['path'] = $id;
$file['rel_path'] = str_replace($this->dir.'/', '', $id); $file['rel_path'] = str_replace($this->dir . '/', '', $id);
$file['hsize'] = Vfs::hsize($file['size']); $file['hsize'] = Vfs::hsize($file['size']);
$file['mime'] = Vfs::mime_content_type($id); $file['mime'] = Vfs::mime_content_type($id);
$file['gid'] *= -1; // our widgets use negative gid's $file['gid'] *= -1; // our widgets use negative gid's
if (($props = Vfs::propfind($id))) if(($props = Vfs::propfind($id)))
{ {
foreach($props as $prop) foreach($props as $prop)
{ {
$file[$prop['name']] = $prop['val']; $file[$prop['name']] = $prop['val'];
} }
} }
if (($file['is_link'] = Vfs::is_link($id))) if(($file['is_link'] = Vfs::is_link($id)))
{ {
$file['symlink'] = Vfs::readlink($id); $file['symlink'] = Vfs::readlink($id);
} }
@ -131,17 +130,17 @@ class filemanager_merge extends Api\Storage\Merge
foreach(Api\Storage\Customfields::get('filemanager') as $name => $field) foreach(Api\Storage\Customfields::get('filemanager') as $name => $field)
{ {
// Set any missing custom fields, or the marker will stay // Set any missing custom fields, or the marker will stay
if(!$file['#'.$name]) if(!$file['#' . $name])
{ {
$file['#'.$name] = ''; $file['#' . $name] = '';
continue; continue;
} }
// Format date cfs per user Api\Preferences // Format date cfs per user Api\Preferences
if($field['type'] == 'date' || $field['type'] == 'date-time') if($field['type'] == 'date' || $field['type'] == 'date-time')
{ {
$this->date_fields[] = '#'.$name; $this->date_fields[] = '#' . $name;
$file['#'.$name] = Api\DateTime::to($file['#'.$name], $field['type'] == 'date' ? true : ''); $file['#' . $name] = Api\DateTime::to($file['#' . $name], $field['type'] == 'date' ? true : '');
} }
} }
} }
@ -150,17 +149,19 @@ class filemanager_merge extends Api\Storage\Merge
if($dirlist[1] == 'apps' && count($dirlist) > 1) if($dirlist[1] == 'apps' && count($dirlist) > 1)
{ {
// Try this first - a normal path /apps/appname/id/file // Try this first - a normal path /apps/appname/id/file
list($app, $app_id) = explode('/', substr($file['path'], strpos($file['path'], 'apps/')+5)); list($app, $app_id) = explode('/', substr($file['path'], strpos($file['path'], 'apps/') + 5));
// Symlink? // Symlink?
if(!$app || !(int)$app_id || !array_key_exists($app, $GLOBALS['egw_info']['user']['apps'])) { if(!$app || !(int)$app_id || !array_key_exists($app, $GLOBALS['egw_info']['user']['apps']))
{
// Try resolving just app + ID - /apps/App Name/Record Title/file // Try resolving just app + ID - /apps/App Name/Record Title/file
$resolved = Vfs::resolve_url_symlinks(implode('/',array_slice(explode('/',$file['dir']),0,4))); $resolved = Vfs::resolve_url_symlinks(implode('/', array_slice(explode('/', $file['dir']), 0, 4)));
list($app, $app_id) = explode('/', substr($resolved, strpos($resolved, 'apps/')+5)); list($app, $app_id) = explode('/', substr($resolved, strpos($resolved, 'apps/') + 5));
if(!$app || !(int)$app_id || !array_key_exists($app, $GLOBALS['egw_info']['user']['apps'])) { if(!$app || !(int)$app_id || !array_key_exists($app, $GLOBALS['egw_info']['user']['apps']))
{
// Get rid of any virtual folders (eg: All$) and symlinks // Get rid of any virtual folders (eg: All$) and symlinks
$resolved = Vfs::resolve_url_symlinks($file['path']); $resolved = Vfs::resolve_url_symlinks($file['path']);
list($app, $app_id) = explode('/', substr($resolved, strpos($resolved, 'apps/')+5)); list($app, $app_id) = explode('/', substr($resolved, strpos($resolved, 'apps/') + 5));
} }
} }
if($app && $app_id) if($app && $app_id)
@ -170,7 +171,7 @@ class filemanager_merge extends Api\Storage\Merge
$app_merge = null; $app_merge = null;
try try
{ {
$classname = $app .'_merge'; $classname = $app . '_merge';
if(class_exists($classname)) if(class_exists($classname))
{ {
$app_merge = new $classname(); $app_merge = new $classname();
@ -181,7 +182,8 @@ class filemanager_merge extends Api\Storage\Merge
} }
} }
// Silently discard & continue // Silently discard & continue
catch(Exception $e) { catch (Exception $e)
{
unset($e); // not used unset($e); // not used
} }
} }
@ -211,7 +213,7 @@ class filemanager_merge extends Api\Storage\Merge
foreach($file as $key => &$value) foreach($file as $key => &$value)
{ {
if(!$value) $value = ''; if(!$value) $value = '';
$info['$$'.($prefix ? $prefix.'/':'').$key.'$$'] = $value; $info['$$' . ($prefix ? $prefix . '/' : '') . $key . '$$'] = $value;
} }
if($app_placeholders) if($app_placeholders)
{ {
@ -239,14 +241,18 @@ class filemanager_merge extends Api\Storage\Merge
{ {
return $session; return $session;
} }
else if (($session = \EGroupware\Api\Cache::getSession(Api\Sharing::class, "$app::$id")) && else
{
if(($session = \EGroupware\Api\Cache::getSession(Api\Sharing::class, "$app::$id")) &&
substr($session['share_path'], -strlen($path)) === $path) substr($session['share_path'], -strlen($path)) === $path)
{ {
return $session; return $session;
} }
}
// Need to create the share here. // Need to create the share here.
// No way to know here if it should be writable, or who it's going to // No way to know here if it should be writable, or who it's going to
$mode = /* ? ? Sharing::WRITABLE :*/ Api\Sharing::READONLY; $mode = /* ? ? Sharing::WRITABLE :*/
Api\Sharing::READONLY;
$recipients = array(); $recipients = array();
$extra = array(); $extra = array();
@ -254,19 +260,27 @@ class filemanager_merge extends Api\Storage\Merge
} }
/** /**
* Generate table with replacements for the Api\Preferences * Hook for extending apps to customise the replacements UI without having to override the whole method
* *
* @param string $template_name
* @param $content
* @param $sel_options
* @param $readonlys
*/ */
public function show_replacements() protected function show_replacements_hook(&$template_name, &$content, &$sel_options, &$readonlys)
{ {
$GLOBALS['egw_info']['flags']['app_header'] = lang('filemanager').' - '.lang('Replacements for inserting entries into documents'); $content['extra_template'] = 'filemanager.replacements';
$GLOBALS['egw_info']['flags']['nonavbar'] = false; }
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n"; /**
echo '<tr><td colspan="4"><h3>'.lang('Filemanager fields:')."</h3></td></tr>"; * Get a list of placeholders provided.
*
* Placeholders are grouped logically. Group key should have a user-friendly translation.
*/
public function get_placeholder_list($prefix = '')
{
$placeholders = parent::get_placeholder_list($prefix);
$n = 0;
$fields = array( $fields = array(
'name' => 'name', 'name' => 'name',
'path' => 'Absolute path', 'path' => 'Absolute path',
@ -283,43 +297,22 @@ class filemanager_merge extends Api\Storage\Merge
'hsize' => 'Size', 'hsize' => 'Size',
'size' => 'Size (in bytes)', 'size' => 'Size (in bytes)',
); );
$group = 'placeholders';
foreach($fields as $name => $label) foreach($fields as $name => $label)
{ {
if (!($n&1)) echo '<tr>'; $marker = $this->prefix($prefix, $name, '{');
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>'; if(!array_filter($placeholders, function ($a) use ($marker)
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach(Api\Storage\Customfields::get('filemanager') as $name => $field)
{ {
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n"; return array_key_exists($marker, $a);
} }))
echo '<tr><td colspan="4"><h3>'.lang('Application fields').":</h3></td></tr>";
echo '<tr><td colspan="4">'.lang('For files linked to an application entry (inside /apps/appname/id/) the placeholders for that application are also available. See the specific application for a list of available placeholders.').'</td></tr>';
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
foreach(array(
'date' => lang('Date'),
'user/n_fn' => lang('Name of current user, all other contact fields are valid too'),
'user/account_lid' => lang('Username'),
'pagerepeat' => lang('For serial letter use this tag. Put the content, you want to repeat between two Tags.'),
'label' => lang('Use this tag for addresslabels. Put the content, you want to repeat, between two tags.'),
'labelplacement' => lang('Tag to mark positions for address labels'),
'IF fieldname' => lang('Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms.'),
'NELF' => lang('Example {{NELF role}} - if field role is not empty, you will get a new line with the value of field role'),
'NENVLF' => lang('Example {{NENVLF role}} - if field role is not empty, set a LF without any value of the field'),
'LETTERPREFIX' => lang('Example {{LETTERPREFIX}} - Gives a letter prefix without double spaces, if the title is empty for example'),
'LETTERPREFIXCUSTOM' => lang('Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller'),
) as $name => $label)
{ {
echo '<tr><td>{{'.$name.'}}</td><td colspan="3">'.$label."</td></tr>\n"; $placeholders[$group][] = [
'value' => $marker,
'label' => $label
];
}
} }
echo "</table>\n"; return $placeholders;
echo $GLOBALS['egw']->framework->footer();
} }
} }

View File

@ -684,7 +684,7 @@ class filemanager_ui
*/ */
static public function action($action,$selected,$dir=null,&$errs=null,&$files=null,&$dirs=null) static public function action($action,$selected,$dir=null,&$errs=null,&$files=null,&$dirs=null)
{ {
if (!count($selected)) if (!count((array)$selected))
{ {
return lang('You need to select some files first!'); return lang('You need to select some files first!');
} }

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
<!-- This template adds the extra bits to the replacements list UI -->
<overlay>
<template id="filemanager.replacements">
<vbox>
<description class="title" value="Application fields"/>
<description
value="For files linked to an application entry (inside /apps/appname/id/) the placeholders for that application are also available. See the specific application for a list of available placeholders."/>
</vbox>
</template>
</overlay>

View File

@ -126,7 +126,10 @@ class importexport_definitions_bo {
$definition = $this->read($key); $definition = $this->read($key);
if($definition['owner'] && $definition['owner'] == $GLOBALS['egw_info']['user']['account_id'] || $GLOBALS['egw_info']['user']['apps']['admin']) { if($definition['owner'] && $definition['owner'] == $GLOBALS['egw_info']['user']['account_id'] || $GLOBALS['egw_info']['user']['apps']['admin']) {
// clear private cache // clear private cache
unset($this->definitions[array_search($key,$this->definitions)]); if(is_array($this->definitions))
{
unset($this->definitions[array_search($key, $this->definitions)]);
}
} else { } else {
unset($keys[$index]); unset($keys[$index]);
} }

View File

@ -279,7 +279,7 @@ class importexport_export_csv implements importexport_iface_export_record
} }
// Fall through for other settings // Fall through for other settings
case 'select': case 'select':
if (count($c_field['values']) == 1 && isset($c_field['values']['@'])) if (!empty($c_field['values']) && count($c_field['values']) == 1 && isset($c_field['values']['@']))
{ {
$c_field['values'] = Api\Storage\Customfields::get_options_from_file($c_field['values']['@']); $c_field['values'] = Api\Storage\Customfields::get_options_from_file($c_field['values']['@']);
} }

View File

@ -183,18 +183,27 @@ class importexport_wizard_basic_export_csv
$content['step'] = 'wizard_step40'; $content['step'] = 'wizard_step40';
// If editing an existing definition, these will be in plugin_options // If editing an existing definition, these will be in plugin_options
if(!$content['delimiter'] && $content['plugin_options']['delimiter']) { if(!$content['delimiter'] && $content['plugin_options']['delimiter'])
{
$content['delimiter'] = $content['plugin_options']['delimiter']; $content['delimiter'] = $content['plugin_options']['delimiter'];
} elseif (!$content['delimiter']) { }
elseif(!$content['delimiter'])
{
$content['delimiter'] = ';'; $content['delimiter'] = ';';
} }
if(!$content['charset'] && $content['plugin_options']['charset']) { if(!$content['charset'] && $content['plugin_options']['charset'])
{
$content['charset'] = $content['plugin_options']['charset'] ? $content['plugin_options']['charset'] : 'user'; $content['charset'] = $content['plugin_options']['charset'] ? $content['plugin_options']['charset'] : 'user';
} }
if(!array_key_exists('begin_with_fieldnames', $content) && array_key_exists('begin_with_fieldnames', $content['plugin_options'])) { if(!array_key_exists('begin_with_fieldnames', $content) &&
is_array($content['plugin_options']) &&
array_key_exists('begin_with_fieldnames', $content['plugin_options']))
{
$content['begin_with_fieldnames'] = $content['plugin_options']['begin_with_fieldnames']; $content['begin_with_fieldnames'] = $content['plugin_options']['begin_with_fieldnames'];
} }
if(!array_key_exists('convert', $content) && array_key_exists('convert', $content['plugin_options'])) { if(!array_key_exists('convert', $content) &&
is_array($content['plugin_options']) && array_key_exists('convert', $content['plugin_options']))
{
$content['convert'] = $content['plugin_options']['convert']; $content['convert'] = $content['plugin_options']['convert'];
} }
@ -204,7 +213,7 @@ class importexport_wizard_basic_export_csv
1 => lang('Field names'), 1 => lang('Field names'),
'label' => lang('Field labels') 'label' => lang('Field labels')
); );
$sel_options['charset'] = Api\Translation::get_installed_charsets()+ $sel_options['charset'] = Api\Translation::get_installed_charsets() +
array( array(
'user' => lang('User preference'), 'user' => lang('User preference'),
); );
@ -273,13 +282,20 @@ class importexport_wizard_basic_export_csv
unset ($preserv['button']); unset ($preserv['button']);
$content['set_filter']['fields'] = importexport_helper_functions::get_filter_fields( $content['set_filter']['fields'] = importexport_helper_functions::get_filter_fields(
$content['application'],$content['plugin'],$this $content['application'], $content['plugin'], $this
); );
// Load existing filter from either content or definition // Load existing filter from either content or definition
if(!array_key_exists('filter', $content) || !is_array($content['filter']))
{
$content['filter'] = [];
}
foreach($content['set_filter']['fields'] as $field => $settings) foreach($content['set_filter']['fields'] as $field => $settings)
{
if(array_key_exists($field, $content['filter']))
{ {
$content['set_filter'][$field] = $content['filter'][$field]; $content['set_filter'][$field] = $content['filter'][$field];
} }
}
if(!$content['set_filter']['fields']) if(!$content['set_filter']['fields'])
{ {

View File

@ -228,7 +228,7 @@ class infolog_bo
{ {
foreach(array_keys($config_data['status']) as $key) foreach(array_keys($config_data['status']) as $key)
{ {
if (!is_array($this->status[$key])) if (!isset($this->status[$key]) || !is_array($this->status[$key]))
{ {
$this->status[$key] = array(); $this->status[$key] = array();
} }
@ -262,17 +262,17 @@ class infolog_bo
$save_config = true; $save_config = true;
} }
} }
if ($save_config) Api\Config::save_value('customfields',$this->customfields,'infolog'); if (!empty($save_config)) Api\Config::save_value('customfields',$this->customfields,'infolog');
} }
if (is_array($config_data['responsible_edit'])) if (isset($config_data['responsible_edit']) && is_array($config_data['responsible_edit']))
{ {
$this->responsible_edit = array_merge($this->responsible_edit,$config_data['responsible_edit']); $this->responsible_edit = array_merge($this->responsible_edit,$config_data['responsible_edit']);
} }
if (is_array($config_data['copy_excludefields'])) if (isset($config_data['copy_excludefields']) && is_array($config_data['copy_excludefields']))
{ {
$this->copy_excludefields = array_merge($this->copy_excludefields,$config_data['copy_excludefields']); $this->copy_excludefields = array_merge($this->copy_excludefields,$config_data['copy_excludefields']);
} }
if (is_array($config_data['sub_excludefields']) && $config_data['sub_excludefields']) if (!empty($config_data['sub_excludefields']) && is_array($config_data['sub_excludefields']))
{ {
$this->sub_excludefields = array_merge($this->sub_excludefields,$config_data['sub_excludefields']); $this->sub_excludefields = array_merge($this->sub_excludefields,$config_data['sub_excludefields']);
} }
@ -286,7 +286,7 @@ class infolog_bo
} }
$this->history = $config_data['history']; $this->history = $config_data['history'];
$this->limit_modified_n_month = $config_data['limit_modified_n_month']; $this->limit_modified_n_month = $config_data['limit_modified_n_month'] ?? null;
} }
// sort types by there translation // sort types by there translation
foreach($this->enums['type'] as $key => $val) foreach($this->enums['type'] as $key => $val)
@ -629,12 +629,14 @@ class infolog_bo
if (!$info_id || ($data = $this->so->read($info_id)) === False) if (!$info_id || ($data = $this->so->read($info_id)) === False)
{ {
return null; $null = null;
return $null;
} }
if (!$ignore_acl && !$this->check_access($data,Acl::READ)) // check behind read, to prevent a double read if (!$ignore_acl && !$this->check_access($data,Acl::READ)) // check behind read, to prevent a double read
{ {
return False; $false = False;
return $false;
} }
if ($data['info_subject'] == $this->subject_from_des($data['info_des'])) if ($data['info_subject'] == $this->subject_from_des($data['info_des']))
@ -1092,10 +1094,14 @@ class infolog_bo
* Checks for info_contact properly linked, project properly linked and * Checks for info_contact properly linked, project properly linked and
* adds or removes to correct. * adds or removes to correct.
* *
* @param Array $values * @param array $values
*/ */
protected function write_check_links(&$values) protected function write_check_links(array &$values)
{ {
if(!$this->check_access($values, Acl::EDIT))
{
return;
}
$old_link_id = (int)$values['info_link_id']; $old_link_id = (int)$values['info_link_id'];
$from = $values['info_from']; $from = $values['info_from'];
@ -1113,7 +1119,7 @@ class infolog_bo
$id = (int)$values['info_contact']['id']; $id = (int)$values['info_contact']['id'];
$from = $values['info_contact']['search']; $from = $values['info_contact']['search'];
} }
else if ($values['info_contact']) else if($values['info_contact'])
{ {
list($app, $id) = explode(':', $values['info_contact'], 2); list($app, $id) = explode(':', $values['info_contact'], 2);
} }

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* eGroupWare - Infolog - importexport * EGroupware - InfoLog - importexport
* *
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package infolog * @package infolog
@ -8,13 +8,12 @@
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @author Nathan Gray * @author Nathan Gray
* @copyright Nathan Gray * @copyright Nathan Gray
* @version $Id$
*/ */
/** /**
* class infolog_egw_record * class infolog_egw_record
* *
* compability layer for iface_egw_record needet for importexport * compatibility layer for iface_egw_record needed for importexport
*/ */
class infolog_egw_record implements importexport_iface_egw_record class infolog_egw_record implements importexport_iface_egw_record
{ {
@ -53,7 +52,7 @@ class infolog_egw_record implements importexport_iface_egw_record
* @param string $_attribute_name * @param string $_attribute_name
*/ */
public function __get($_attribute_name) { public function __get($_attribute_name) {
return $this->record[$_attribute_name]; return $this->record[$_attribute_name] ?? null;
} }
/** /**

View File

@ -458,41 +458,8 @@ class infolog_hooks
// Merge print // Merge print
if ($GLOBALS['egw_info']['user']['apps']['filemanager']) if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{ {
$settings['default_document'] = array( $merge = new infolog_merge();
'type' => 'vfs_file', $settings += $merge->merge_preferences();
'size' => 60,
'label' => 'Default document to insert entries',
'name' => 'default_document',
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.',lang('infolog')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','info_subject').' '.
lang('The following document-types are supported:').'*.rtf, *.txt',
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
);
$settings['document_dir'] = array(
'type' => 'vfs_dirs',
'size' => 60,
'label' => 'Directory with documents to insert entries',
'name' => 'document_dir',
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.', lang('infolog')) . ' ' .
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'info_subject') . ' ' .
lang('The following document-types are supported:') . '*.rtf, *.txt',
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
'default' => '/templates/infolog',
);
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
'type' => 'taglist',
'label' => 'Document download filename',
'name' => 'document_download_name',
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
'help' => 'Choose the default filename for downloaded documents.',
'xmlrpc' => True,
'admin' => False,
'default' => 'document',
);
} }
if ($GLOBALS['egw_info']['user']['apps']['calendar']) if ($GLOBALS['egw_info']['user']['apps']['calendar'])

View File

@ -138,7 +138,7 @@ class infolog_merge extends Api\Storage\Merge
// Set any missing custom fields, or the marker will stay // Set any missing custom fields, or the marker will stay
foreach($this->bo->customfields as $name => $field) foreach($this->bo->customfields as $name => $field)
{ {
if(!$array['#'.$name]) if (empty($array['#'.$name]))
{ {
$array['#'.$name] = ''; $array['#'.$name] = '';
} }
@ -183,9 +183,9 @@ class infolog_merge extends Api\Storage\Merge
$info += $this->get_all_links('infolog', $id, $prefix, $content); $info += $this->get_all_links('infolog', $id, $prefix, $content);
// Add contact fields // Add contact fields
if($array['info_link'] && $array['info_link']['app'] && $array['info_link']['id']) if($array['info_link'] && is_array($array['info_link']) && $array['info_link']['app'] && $array['info_link']['id'])
{ {
$info+=$this->get_app_replacements($array['info_link']['app'], $array['info_link']['id'], $content, 'info_contact'); $info += $this->get_app_replacements($array['info_link']['app'], $array['info_link']['id'], $content, 'info_contact');
} }
// Add owner fields // Add owner fields
$info += $this->contact_replacements(Api\Accounts::id2name($info_owner,'person_id'),'info_owner'); $info += $this->contact_replacements(Api\Accounts::id2name($info_owner,'person_id'),'info_owner');
@ -198,98 +198,20 @@ class infolog_merge extends Api\Storage\Merge
return $info; return $info;
} }
/**
* Generate table with replacements for the Api\Preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('infolog').' - '.lang('Replacements for inserting entries into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Infolog fields:')."</h3></td></tr>";
$n = 0;
$tracking = new infolog_tracking($this->bo);
$fields = array('info_id' => lang('Infolog ID'), 'pm_id' => lang('Project ID'), 'project' => lang('Project name')) + $tracking->field2label + array('info_sum_timesheets' => lang('Used time'));
Api\Translation::add_app('projectmanager');
foreach($fields as $name => $label)
{
if (in_array($name,array('custom'))) continue; // dont show them
if (in_array($name,array('info_subject', 'info_des')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
$contact_custom = false;
foreach($this->bo->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label'].($field['type'] == 'select-account' ? '*':'')."</td></tr>\n";
if($field['type'] == 'select-account') $contact_custom = true;
}
if($contact_custom)
{
echo '<tr><td /><td colspan="3">* '.lang('Addressbook placeholders available'). '</td></tr>';
}
echo '<tr><td colspan="4"><h3>'.lang('Parent').":</h3></td></tr>";
echo '<tr><td>{{info_id_parent/info_subject}}</td><td colspan="3">'.lang('All other %1 fields are valid',lang('infolog'))."</td></tr>\n";
echo '<tr><td colspan="4"><h3>'.lang('Contact fields').':</h3></td></tr>';
$i = 0;
foreach($this->contacts->contact_fields as $name => $label)
{
if (in_array($name,array('tid','label','geo'))) continue; // dont show them, as they are not used in the UI atm.
if (in_array($name,array('email','org_name','tel_work','url')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$i++;
}
if (!($i&1)) echo '<tr>';
echo '<td>{{info_contact/'.$name.'}}</td><td>'.$label.'</td>';
if ($i&1) echo "</tr>\n";
$i++;
}
echo '<tr><td colspan="4">'.lang('Owner contact fields also available under info_owner/...').'</td></tr>';
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->contacts->customfields as $name => $field)
{
echo '<tr><td>{{info_contact/#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>' . lang('General fields:') . "</h3></td></tr>";
foreach($this->get_common_replacements() as $name => $label)
{
echo '<tr><td>{{' . $name . '}}</td><td colspan="3">' . $label . "</td></tr>\n";
}
echo "</table>\n";
echo $GLOBALS['egw']->framework->footer();
}
public function get_placeholder_list($prefix = '') public function get_placeholder_list($prefix = '')
{ {
$placeholders = parent::get_placeholder_list($prefix);
$tracking = new infolog_tracking($this->bo); $tracking = new infolog_tracking($this->bo);
$placeholders = array(
'infolog' => [],
lang('parent') => [],
lang($tracking->field2label['info_from']) => []
) + parent::get_placeholder_list($prefix);
$fields = array('info_id' => lang('Infolog ID'), 'pm_id' => lang('Project ID'), $fields = array('info_id' => lang('Infolog ID'), 'pm_id' => lang('Project ID'),
'project' => lang('Project name')) + $tracking->field2label + array('info_sum_timesheets' => lang('Used time')); 'project' => lang('Project name')) + $tracking->field2label + array('info_sum_timesheets' => lang('Used time'));
Api\Translation::add_app('projectmanager'); Api\Translation::add_app('projectmanager');
$group = 'placeholders'; $group = 'infolog';
foreach($fields as $name => $label) foreach($fields as $name => $label)
{ {
if(in_array($name, array('custom'))) if(in_array($name, array('custom')))
@ -310,15 +232,27 @@ class infolog_merge extends Api\Storage\Merge
} }
} }
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// Add contact placeholders // Add contact placeholders
$insert_index = 1;
$placeholders = array_slice($placeholders, 0, $insert_index, true) +
[lang($tracking->field2label['info_from']) => []] +
array_slice($placeholders, $insert_index, count($placeholders) - $insert_index, true);
$contact_merge = new Api\Contacts\Merge(); $contact_merge = new Api\Contacts\Merge();
$contact = $contact_merge->get_placeholder_list('info_contact'); $contact = $contact_merge->get_placeholder_list($this->prefix($prefix, 'info_contact'));
$this->add_linked_placeholders($placeholders, lang($tracking->field2label['info_from']), $contact); $this->add_linked_placeholders($placeholders, lang($tracking->field2label['info_from']), $contact);
// Add parent placeholders
$this->add_linked_placeholders(
$placeholders,
lang('parent'),
$this->get_placeholder_list(($prefix ? $prefix . '/' : '') . 'info_id_parent')
);
}
else
{
unset($placeholders[lang('parent')]);
unset($placeholders[lang($tracking->field2label['info_from'])]);
}
return $placeholders; return $placeholders;
} }
} }

View File

@ -144,7 +144,7 @@ class infolog_tracking extends Api\Storage\Tracking
*/ */
function get_subject($data, $old, $deleted = null, $receiver = null) function get_subject($data, $old, $deleted = null, $receiver = null)
{ {
if ($data['prefix']) if (!empty($data['prefix']))
{ {
$prefix = $data['prefix']; // async notification $prefix = $data['prefix']; // async notification
} }
@ -172,7 +172,7 @@ class infolog_tracking extends Api\Storage\Tracking
*/ */
function get_message($data, $old, $receiver = null) function get_message($data, $old, $receiver = null)
{ {
if ($data['message']) return $data['message']; // async notification if (!empty($data['message'])) return $data['message']; // async notification
if (!$old || $old['info_status'] == 'deleted') if (!$old || $old['info_status'] == 'deleted')
{ {
@ -345,16 +345,16 @@ class infolog_tracking extends Api\Storage\Tracking
return ''; return '';
} }
// Per-type notification // Per-type notification
$type_config = $info_config[self::CUSTOM_NOTIFICATION][$data['info_type']]; $type_config = $info_config[self::CUSTOM_NOTIFICATION][$data['info_type']] ?? null;
$global = $info_config[self::CUSTOM_NOTIFICATION]['~global~']; $global = $info_config[self::CUSTOM_NOTIFICATION]['~global~'];
// Disabled // Disabled
if(!$type_config['use_custom'] && !$global['use_custom']) return ''; if(empty($type_config['use_custom']) && empty($global['use_custom'])) return '';
// Type or globabl // Type or global
$config = trim(strip_tags($type_config['message'])) != '' && $type_config['use_custom'] ? $type_config['message'] : $global['message']; $config = trim(strip_tags($type_config['message'])) != '' && $type_config['use_custom'] ? $type_config['message'] : $global['message'];
break; break;
} }
return $config; return $config ?? null;
} }
} }

View File

@ -1874,7 +1874,7 @@ class infolog_ui
$content['link_to']['to_app'] = 'infolog'; $content['link_to']['to_app'] = 'infolog';
$content['link_to']['to_id'] = $info_id; $content['link_to']['to_id'] = $info_id;
/* $info_link_id is never defined
if ($info_link_id && strpos($info_link_id,':') !== false) // updating info_link_id if necessary if ($info_link_id && strpos($info_link_id,':') !== false) // updating info_link_id if necessary
{ {
list($app,$id) = explode(':',$info_link_id); list($app,$id) = explode(':',$info_link_id);
@ -1903,7 +1903,7 @@ class infolog_ui
// we need eg. the new modification date, for further updates // we need eg. the new modification date, for further updates
$content = array_merge($content,$to_write); $content = array_merge($content,$to_write);
} }
} }*/
// Need to purge description history after encryption? // Need to purge description history after encryption?
if($content['clean_history']) if($content['clean_history'])
@ -2115,7 +2115,7 @@ class infolog_ui
// remove types owned by groups the user has no edit grant (current type is made readonly) // remove types owned by groups the user has no edit grant (current type is made readonly)
foreach($this->bo->group_owners as $type => $group) foreach($this->bo->group_owners as $type => $group)
{ {
if (!($this->bo->grants[$group] & Acl::EDIT)) if (!(($this->bo->grants[$group]??0) & Acl::EDIT))
{ {
if ($type == $content['info_type']) if ($type == $content['info_type'])
{ {
@ -2172,7 +2172,7 @@ class infolog_ui
$readonlys['action'] = true; $readonlys['action'] = true;
} }
// ToDo: use the old status before the delete // ToDo: use the old status before the delete
if ($info_id && $undelete) if ($info_id && !empty($undelete))
{ {
$content['info_status'] = $this->bo->status['defaults'][$content['info_type']]; $content['info_status'] = $this->bo->status['defaults'][$content['info_type']];
$this->tmpl->setElementAttribute('button[save]', 'label', 'Un-Delete'); $this->tmpl->setElementAttribute('button[save]', 'label', 'Un-Delete');
@ -2187,7 +2187,7 @@ class infolog_ui
// use a typ-specific template (infolog.edit.xyz), if one exists, otherwise fall back to the generic one // use a typ-specific template (infolog.edit.xyz), if one exists, otherwise fall back to the generic one
if (!$this->tmpl->read('infolog.edit.'.$content['info_type'])) if (!$this->tmpl->read('infolog.edit.'.$content['info_type']))
{ {
$this->tmpl->read($print ? 'infolog.edit.print':'infolog.edit'); $this->tmpl->read(!empty($print) ? 'infolog.edit.print' : 'infolog.edit');
} }
if ($this->bo->has_customfields($content['info_type'])) if ($this->bo->has_customfields($content['info_type']))
{ {
@ -2252,7 +2252,7 @@ class infolog_ui
$tracking = new infolog_tracking($this); $tracking = new infolog_tracking($this);
foreach($tracking->field2history as $field => $history) foreach($tracking->field2history as $field => $history)
{ {
$history_stati[$history] = $tracking->field2label[$field]; $history_stati[$history] = $tracking->field2label[$field] ?? null;
} }
// Modified date removed from field2history, we don't need that in the history // Modified date removed from field2history, we don't need that in the history
$history_stati['Mo'] = $tracking->field2label['info_datemodified']; $history_stati['Mo'] = $tracking->field2label['info_datemodified'];
@ -2276,20 +2276,20 @@ class infolog_ui
'to_tracker' => array('label' => 'Tracker', 'title' => 'Convert to a ticket'), 'to_tracker' => array('label' => 'Tracker', 'title' => 'Convert to a ticket'),
), ),
); );
if ($GLOBALS['egw_info']['user']['apps']['calendar']) if (!empty($GLOBALS['egw_info']['user']['apps']['calendar']))
{ {
$sel_options['action']['schedule'] = array('label' => 'Schedule', 'title' => 'Schedule appointment'); $sel_options['action']['schedule'] = array('label' => 'Schedule', 'title' => 'Schedule appointment');
} }
if ($GLOBALS['egw_info']['user']['apps']['stylite'] && !$GLOBALS['egw_info']['server']['disable_pgp_encryption']) if (!empty($GLOBALS['egw_info']['user']['apps']['stylite']) && empty($GLOBALS['egw_info']['server']['disable_pgp_encryption']))
{ {
$content['encryption_ts'] = filemtime(EGW_SERVER_ROOT.'/stylite/js/app.js'); $content['encryption_ts'] = filemtime(EGW_SERVER_ROOT.'/stylite/js/app.js');
} }
elseif ($GLOBALS['egw_info']['server']['disable_pgp_encryption']) elseif (!empty($GLOBALS['egw_info']['server']['disable_pgp_encryption']))
{ {
$readonlys['encrypt'] = true; $readonlys['encrypt'] = true;
} }
$GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '. $GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '.
($content['status_only'] ? lang('Edit Status') : lang('Edit')); (!empty($content['status_only']) ? lang('Edit Status') : lang('Edit'));
$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => ($info_id ? 'ManualInfologEdit' : 'ManualInfologAdd')); $GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => ($info_id ? 'ManualInfologEdit' : 'ManualInfologAdd'));
//error_log(substr($content['info_des'],1793,10)); //error_log(substr($content['info_des'],1793,10));
//$content['info_des'] = substr($content['info_des'],0,1793); //$content['info_des'] = substr($content['info_des'],0,1793);

View File

@ -60,6 +60,7 @@ function ajax_exception_handler($e)
$response = Json\Response::get(); $response = Json\Response::get();
$message .= ($message ? "\n\n" : '').$e->getMessage(); $message .= ($message ? "\n\n" : '').$e->getMessage();
$message .= "\n\n".$e->getFile().' ('.$e->getLine().')';
// only show trace (incl. function arguments) if explicitly enabled, eg. on a development system // only show trace (incl. function arguments) if explicitly enabled, eg. on a development system
if ($GLOBALS['egw_info']['server']['exception_show_trace']) if ($GLOBALS['egw_info']['server']['exception_show_trace'])
{ {

View File

@ -216,7 +216,7 @@ class mail_compose
); );
$acc_smime = Mail\Smime::get_acc_smime($content['mailaccount']); $acc_smime = Mail\Smime::get_acc_smime($content['mailaccount']);
if ($acc_smime['acc_smime_password']) if ($acc_smime && !empty($acc_smime['acc_smime_password']))
{ {
$actions = array_merge($actions, array( $actions = array_merge($actions, array(
'smime_sign' => array ( 'smime_sign' => array (
@ -271,9 +271,9 @@ class mail_compose
} }
unset($actions['pgp']); unset($actions['pgp']);
} }
if ($GLOBALS['egw_info']['server']['disable_pgp_encryption']) unset($actions['pgp']); if (!empty($GLOBALS['egw_info']['server']['disable_pgp_encryption'])) unset($actions['pgp']);
// remove vfs actions if the user has no run access to filemanager // remove vfs actions if the user has no run access to filemanager
if (!$GLOBALS['egw_info']['user']['apps']['filemanager']) if (empty($GLOBALS['egw_info']['user']['apps']['filemanager']))
{ {
unset($actions['save2vfs']); unset($actions['save2vfs']);
unset($actions['selectFromVFSForCompose']); unset($actions['selectFromVFSForCompose']);
@ -1242,16 +1242,16 @@ class mail_compose
// address stuff like from, to, cc, replyto // address stuff like from, to, cc, replyto
$destinationRows = 0; $destinationRows = 0;
foreach(self::$destinations as $destination) { foreach(self::$destinations as $destination) {
if (!is_array($content[$destination])) if (!empty($content[$destination]) && !is_array($content[$destination]))
{ {
if (!empty($content[$destination])) $content[$destination] = (array)$content[$destination]; $content[$destination] = (array)$content[$destination];
} }
$addr_content = $content[strtolower($destination)]; $addr_content = $content[strtolower($destination)] ?? [];
// we clear the given address array and rebuild it // we clear the given address array and rebuild it
unset($content[strtolower($destination)]); unset($content[strtolower($destination)]);
foreach((array)$addr_content as $key => $value) { foreach($addr_content as $value) {
if ($value=="NIL@NIL") continue; if ($value === "NIL@NIL") continue;
if ($destination=='replyto' && str_replace('"','',$value) == if ($destination === 'replyto' && str_replace('"','',$value) ===
str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()])) str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()]))
{ {
// preserve/restore the value to content. // preserve/restore the value to content.
@ -1261,7 +1261,7 @@ class mail_compose
//error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value))); //error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value)));
$value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT)); $value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT));
foreach(Mail::parseAddressList($value) as $addressObject) { foreach(Mail::parseAddressList($value) as $addressObject) {
if ($addressObject->host == '.SYNTAX-ERROR.') continue; if ($addressObject->host === '.SYNTAX-ERROR.') continue;
$address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal); $address = imap_rfc822_write_address($addressObject->mailbox,$addressObject->host,$addressObject->personal);
//$address = Mail::htmlentities($address, $this->displayCharset); //$address = Mail::htmlentities($address, $this->displayCharset);
$content[strtolower($destination)][]=$address; $content[strtolower($destination)][]=$address;
@ -1289,7 +1289,7 @@ class mail_compose
$content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body']; $content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body'];
$content['showtempname']=0; $content['showtempname']=0;
//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.'before merging content with uploadforCompose:'.array2string($content['attachments'])); //if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.'before merging content with uploadforCompose:'.array2string($content['attachments']));
$content['attachments']=(is_array($content['attachments'])&&is_array($content['uploadForCompose'])?array_merge($content['attachments'],(!empty($content['uploadForCompose'])?$content['uploadForCompose']:array())):(is_array($content['uploadForCompose'])?$content['uploadForCompose']:(is_array($content['attachments'])?$content['attachments']:null))); $content['attachments'] = array_merge($content['attachments'] ?? [], $content['uploadForCompose'] ?? []);
//if (is_array($content['attachments'])) foreach($content['attachments'] as $k => &$file) $file['delete['.$file['tmp_name'].']']=0; //if (is_array($content['attachments'])) foreach($content['attachments'] as $k => &$file) $file['delete['.$file['tmp_name'].']']=0;
$content['no_griddata'] = empty($content['attachments']); $content['no_griddata'] = empty($content['attachments']);
$preserv['attachments'] = $content['attachments']; $preserv['attachments'] = $content['attachments'];
@ -1297,21 +1297,21 @@ class mail_compose
//if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.' Attachments:'.array2string($content['attachments'])); //if (is_array($content['attachments']))error_log(__METHOD__.__LINE__.' Attachments:'.array2string($content['attachments']));
// if no filemanager -> no vfsFileSelector // if no filemanager -> no vfsFileSelector
if (!$GLOBALS['egw_info']['user']['apps']['filemanager']) if (empty($GLOBALS['egw_info']['user']['apps']['filemanager']))
{ {
$content['vfsNotAvailable'] = "mail_DisplayNone"; $content['vfsNotAvailable'] = "mail_DisplayNone";
} }
// if no infolog -> no save as infolog // if no infolog -> no save as infolog
if (!$GLOBALS['egw_info']['user']['apps']['infolog']) if (empty($GLOBALS['egw_info']['user']['apps']['infolog']))
{ {
$content['noInfologAvailable'] = "mail_DisplayNone"; $content['noInfologAvailable'] = "mail_DisplayNone";
} }
// if no tracker -> no save as tracker // if no tracker -> no save as tracker
if (!$GLOBALS['egw_info']['user']['apps']['tracker']) if (empty($GLOBALS['egw_info']['user']['apps']['tracker']))
{ {
$content['noTrackerAvailable'] = "mail_DisplayNone"; $content['noTrackerAvailable'] = "mail_DisplayNone";
} }
if (!$GLOBALS['egw_info']['user']['apps']['infolog'] && !$GLOBALS['egw_info']['user']['apps']['tracker']) if (empty($GLOBALS['egw_info']['user']['apps']['infolog']) && empty($GLOBALS['egw_info']['user']['apps']['tracker']))
{ {
$content['noSaveAsAvailable'] = "mail_DisplayNone"; $content['noSaveAsAvailable'] = "mail_DisplayNone";
} }
@ -1324,12 +1324,12 @@ class mail_compose
$sel_options['mimeType'] = self::$mimeTypes; $sel_options['mimeType'] = self::$mimeTypes;
$sel_options['priority'] = self::$priorities; $sel_options['priority'] = self::$priorities;
$sel_options['filemode'] = Vfs\Sharing::$modes; $sel_options['filemode'] = Vfs\Sharing::$modes;
if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3; if (empty($content['priority'])) $content['priority']=3;
//$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed //$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed
$etpl = new Etemplate('mail.compose'); $etpl = new Etemplate('mail.compose');
$etpl->setElementAttribute('composeToolbar', 'actions', self::getToolbarActions($content)); $etpl->setElementAttribute('composeToolbar', 'actions', self::getToolbarActions($content));
if ($content['mimeType']=='html') if ($content['mimeType'] == 'html')
{ {
//mode="$cont[rtfEditorFeatures]" validation_rules="$cont[validation_rules]" base_href="$cont[upload_dir]" //mode="$cont[rtfEditorFeatures]" validation_rules="$cont[validation_rules]" base_href="$cont[upload_dir]"
$_htmlConfig = Mail::$htmLawed_config; $_htmlConfig = Mail::$htmLawed_config;
@ -1340,7 +1340,7 @@ class mail_compose
Mail::$htmLawed_config = $_htmlConfig; Mail::$htmLawed_config = $_htmlConfig;
} }
if (isset($content['composeID'])&&!empty($content['composeID'])) if (!empty($content['composeID']))
{ {
$composeCache = $content; $composeCache = $content;
unset($composeCache['body']); unset($composeCache['body']);
@ -1348,21 +1348,21 @@ class mail_compose
unset($composeCache['mail_plaintext']); unset($composeCache['mail_plaintext']);
Api\Cache::setCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$this->composeID,$composeCache,$expiration=60*60*2); Api\Cache::setCache(Api\Cache::SESSION,'mail','composeCache'.trim($GLOBALS['egw_info']['user']['account_id']).'_'.$this->composeID,$composeCache,$expiration=60*60*2);
} }
if (!isset($_content['serverID'])||empty($_content['serverID'])) if (empty($_content['serverID']))
{ {
$content['serverID'] = $this->mail_bo->profileID; $content['serverID'] = $this->mail_bo->profileID;
} }
$preserv['serverID'] = $content['serverID']; $preserv['serverID'] = $content['serverID'];
$preserv['lastDrafted'] = $content['lastDrafted']; $preserv['lastDrafted'] = $content['lastDrafted'] ?? null;
$preserv['processedmail_id'] = $content['processedmail_id']; $preserv['processedmail_id'] = $content['processedmail_id'] ?? null;
$preserv['references'] = $content['references']; $preserv['references'] = $content['references'] ?? null;
$preserv['in-reply-to'] = $content['in-reply-to']; $preserv['in-reply-to'] = $content['in-reply-to'] ?? null;
// thread-topic is a proprietary microsoft header and deprecated with the current version // thread-topic is a proprietary microsoft header and deprecated with the current version
// horde does not support the encoding of thread-topic, and probably will not no so in the future // horde does not support the encoding of thread-topic, and probably will not no so in the future
//$preserv['thread-topic'] = $content['thread-topic']; //$preserv['thread-topic'] = $content['thread-topic'];
$preserv['thread-index'] = $content['thread-index']; $preserv['thread-index'] = $content['thread-index'] ?? null;
$preserv['list-id'] = $content['list-id']; $preserv['list-id'] = $content['list-id'] ?? null;
$preserv['mode'] = $content['mode']; $preserv['mode'] = $content['mode'] ?? null;
// convert it back to checkbox expectations // convert it back to checkbox expectations
if($content['mimeType'] == 'html') { if($content['mimeType'] == 'html') {
$content['mimeType']=1; $content['mimeType']=1;
@ -1391,11 +1391,11 @@ class mail_compose
// Resolve distribution list before send content to client // Resolve distribution list before send content to client
foreach(array('to', 'cc', 'bcc', 'replyto') as $f) foreach(array('to', 'cc', 'bcc', 'replyto') as $f)
{ {
if (is_array($content[$f])) $content[$f]= self::resolveEmailAddressList ($content[$f]); if (isset($content[$f]) && is_array($content[$f])) $content[$f]= self::resolveEmailAddressList ($content[$f]);
} }
// set filemode icons for all attachments // set filemode icons for all attachments
if($content['attachments'] && is_array($content['attachments'])) if(!empty($content['attachments']))
{ {
foreach($content['attachments'] as &$attach) foreach($content['attachments'] as &$attach)
{ {
@ -1407,9 +1407,9 @@ class mail_compose
} }
} }
$content['to'] = self::resolveEmailAddressList($content['to']); if (isset($content['to'])) $content['to'] = self::resolveEmailAddressList($content['to']);
$content['html_toolbar'] = empty(Mail::$mailConfig['html_toolbar']) ? $content['html_toolbar'] = empty(Mail::$mailConfig['html_toolbar']) ?
join(',', Etemplate\Widget\HtmlArea::$toolbar_default_list) : join(',', Mail::$mailConfig['html_toolbar']); implode(',', Etemplate\Widget\HtmlArea::$toolbar_default_list) : implode(',', Mail::$mailConfig['html_toolbar']);
//error_log(__METHOD__.__LINE__.array2string($content)); //error_log(__METHOD__.__LINE__.array2string($content));
$etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2); $etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2);
} }
@ -2022,7 +2022,7 @@ class mail_compose
'size' => $_size, 'size' => $_size,
'folder' => $_folder, 'folder' => $_folder,
'winmailFlag' => $_is_winmail, 'winmailFlag' => $_is_winmail,
'tmp_name' => mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid).'_'.(!empty($_partID)?$_partID:count($this->sessionData['attachments'])+1), 'tmp_name' => mail_ui::generateRowID($this->mail_bo->profileID, $_folder, $_uid).'_'.(!empty($_partID)?$_partID:count($this->sessionData['attachments'] ?? [])+1),
); );
} }
@ -2485,7 +2485,7 @@ class mail_compose
if(!empty($_formData['list-id'])) { if(!empty($_formData['list-id'])) {
$_mailObject->addHeader('List-Id', $_formData['list-id']); $_mailObject->addHeader('List-Id', $_formData['list-id']);
} }
if($_formData['disposition']=='on') { if(isset($_formData['disposition']) && $_formData['disposition'] === 'on') {
$_mailObject->addHeader('Disposition-Notification-To', $_identity['ident_email']); $_mailObject->addHeader('Disposition-Notification-To', $_identity['ident_email']);
} }
@ -2522,6 +2522,8 @@ class mail_compose
if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving) if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving)
{ {
$attachment_links = $this->_getAttachmentLinks($_formData['attachments'], $_formData['filemode'], $attachment_links = $this->_getAttachmentLinks($_formData['attachments'], $_formData['filemode'],
// @TODO: $content['mimeType'] could type string/boolean. At the moment we can't strictly check them :(.
// @TODO: This needs to be fixed in compose function to get the right type from the content.
$_formData['mimeType'] == 'html', $_formData['mimeType'] == 'html',
array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])), array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])),
$_formData['expiration'], $_formData['password']); $_formData['expiration'], $_formData['password']);
@ -2530,7 +2532,7 @@ class mail_compose
{ {
case 'html': case 'html':
$body = $_formData['body']; $body = $_formData['body'];
if ($attachment_links) if (!empty($attachment_links))
{ {
if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false) if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false)
{ {
@ -2567,7 +2569,7 @@ class mail_compose
default: default:
$body = $this->convertHTMLToText($_formData['body'],false, false, true, true); $body = $this->convertHTMLToText($_formData['body'],false, false, true, true);
if ($attachment_links) $body .= $attachment_links; if (!empty($attachment_links)) $body .= $attachment_links;
#$_mailObject->Body = $_formData['body']; #$_mailObject->Body = $_formData['body'];
if(!empty($signature)) { if(!empty($signature)) {
@ -2653,7 +2655,7 @@ class mail_compose
} }
if ($connection_opened) $mail_bo->closeConnection(); if ($connection_opened) $mail_bo->closeConnection();
} }
return is_array($inline_images)?$inline_images:array(); return $inline_images ?? [];
} }
/** /**
@ -2761,7 +2763,7 @@ class mail_compose
$dmailbox = $dhA['folder']; $dmailbox = $dhA['folder'];
// beware: do not delete the original mail as found in processedmail_id // beware: do not delete the original mail as found in processedmail_id
$pMuid=''; $pMuid='';
if ($content['processedmail_id']) if (!empty($content['processedmail_id']))
{ {
$pMhA = mail_ui::splitRowID($content['processedmail_id']); $pMhA = mail_ui::splitRowID($content['processedmail_id']);
$pMuid = $pMhA['msgUID']; $pMuid = $pMhA['msgUID'];
@ -3021,7 +3023,7 @@ class mail_compose
// create the messages and store inline images // create the messages and store inline images
$inline_images = $this->createMessage($mail, $_formData, $identity); $inline_images = $this->createMessage($mail, $_formData, $identity);
// remember the identity // remember the identity
if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') $fromAddress = $mail->From;//$mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':''); if (!empty($mail->From) && ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on')) $fromAddress = $mail->From;//$mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':'');
#print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>"; #print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>";
#print "<pre>". $mail->getMessageBody() ."</pre><hr><br>"; #print "<pre>". $mail->getMessageBody() ."</pre><hr><br>";
#exit; #exit;
@ -3317,14 +3319,14 @@ class mail_compose
if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']); if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']);
// manually drafted, do not delete // manually drafted, do not delete
// will be handled later on IF mode was $_formData['mode']=='composefromdraft' // will be handled later on IF mode was $_formData['mode']=='composefromdraft'
if (isset($lastDrafted['uid']) && (empty($lastDrafted['uid']) || $lastDrafted['uid'] == $this->sessionData['uid'])) $lastDrafted=false; if (isset($lastDrafted['uid']) && (empty($lastDrafted['uid']) || $lastDrafted['uid'] == ($this->sessionData['uid']??null))) $lastDrafted=false;
//error_log(__METHOD__.__LINE__.array2string($lastDrafted)); //error_log(__METHOD__.__LINE__.array2string($lastDrafted));
} }
if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder'])) if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder']))
{ {
try try
{ {
if ($this->sessionData['lastDrafted'] != $this->sessionData['uid'] || !($_formData['mode']=='composefromdraft' && if ($this->sessionData['lastDrafted'] != ($this->sessionData['uid']??null) || !($_formData['mode']=='composefromdraft' &&
($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )&&$this->sessionData['attachments'])) ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )&&$this->sessionData['attachments']))
{ {
//error_log(__METHOD__.__LINE__."#".$lastDrafted['uid'].'#'.$lastDrafted['folder'].array2string($_formData)); //error_log(__METHOD__.__LINE__."#".$lastDrafted['uid'].'#'.$lastDrafted['folder'].array2string($_formData));
@ -3399,7 +3401,7 @@ class mail_compose
} }
if (is_array($this->sessionData['cc'])) $mailaddresses['cc'] = $this->sessionData['cc']; if (is_array($this->sessionData['cc'])) $mailaddresses['cc'] = $this->sessionData['cc'];
if (is_array($this->sessionData['bcc'])) $mailaddresses['bcc'] = $this->sessionData['bcc']; if (is_array($this->sessionData['bcc'])) $mailaddresses['bcc'] = $this->sessionData['bcc'];
if (!empty($mailaddresses)) $mailaddresses['from'] = Mail\Html::decodeMailHeader($fromAddress); if (!empty($mailaddresses) && !empty($fromAddress)) $mailaddresses['from'] = Mail\Html::decodeMailHeader($fromAddress);
if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' ) if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on' || $_formData['to_calendar'] == 'on' )
{ {
@ -3407,7 +3409,7 @@ class mail_compose
foreach(array('to_infolog','to_tracker','to_calendar') as $app_key) foreach(array('to_infolog','to_tracker','to_calendar') as $app_key)
{ {
$entryid = $_formData['to_integrate_ids'][0][$app_key]; $entryid = $_formData['to_integrate_ids'][0][$app_key] ?? null;
if ($_formData[$app_key] == 'on') if ($_formData[$app_key] == 'on')
{ {
$app_name = substr($app_key,3); $app_name = substr($app_key,3);

View File

@ -252,6 +252,10 @@ class mail_sieve
break; break;
case 'reject': case 'reject':
$content['action_reject_text'] = $rules['action_arg']; $content['action_reject_text'] = $rules['action_arg'];
break;
case 'flags':
$content['action_flags_list'] = explode(' ', $rules['action_arg']);
break;
} }
$content['anyof'] = $rules['anyof'] != 0?1:0; $content['anyof'] = $rules['anyof'] != 0?1:0;
} }
@ -302,10 +306,15 @@ class mail_sieve
break; break;
case 'reject': case 'reject':
$newRule['action_arg'] = $content['action_reject_text']; $newRule['action_arg'] = $content['action_reject_text'];
break;
case 'flags':
$newRule['action_arg'] = implode(' ', $content['action_flags_list']);
break;
} }
unset($newRule['action_folder_text']); unset($newRule['action_folder_text']);
unset($newRule['action_address_text']); unset($newRule['action_address_text']);
unset($newRule['action_reject_text']); unset($newRule['action_reject_text']);
unset($newRule['action_flags_list']);
$newRule['flg'] = 0 ; $newRule['flg'] = 0 ;
if( $newRule['continue'] ) { $newRule['flg'] += 1; } if( $newRule['continue'] ) { $newRule['flg'] += 1; }
@ -550,7 +559,7 @@ class mail_sieve
} }
else else
{ {
if ($icServer->acc_imap_administration) if ($icServer->acc_imap_administration || (!empty($icServer->getExtensions()) && in_array('DATE', $icServer->getExtensions())))
{ {
$ByDate = array('by_date' => lang('By date')); $ByDate = array('by_date' => lang('By date'));
} }
@ -949,11 +958,11 @@ class mail_sieve
break; break;
case 'enable': case 'enable':
$msg = lang('rule with priority ') . $checked . lang(' enabled!'); $msg = lang('rule with priority ') . $checked . lang(' enabled!');
$this->rules[$checked][status] = 'ENABLED'; $this->rules[$checked]['status'] = 'ENABLED';
break; break;
case 'disable': case 'disable':
$msg = lang('rule with priority ') . $checked . lang(' disabled!'); $msg = lang('rule with priority ') . $checked . lang(' disabled!');
$this->rules[$checked][status] = 'DISABLED'; $this->rules[$checked]['status'] = 'DISABLED';
break; break;
case 'move': case 'move':
break; break;

View File

@ -425,7 +425,7 @@ class mail_ui
protected static function image_proxy() protected static function image_proxy()
{ {
$configs = Api\Config::read('mail'); $configs = Api\Config::read('mail');
$image_proxy = $configs[self::IMAGE_PROXY_CONFIG] ?: self::DEFAULT_IMAGE_PROXY; $image_proxy = $configs[self::IMAGE_PROXY_CONFIG] ?? self::DEFAULT_IMAGE_PROXY;
if (strpos(self::EGROUPWARE_IMAGE_PROXY, parse_url($image_proxy, PHP_URL_HOST))) if (strpos(self::EGROUPWARE_IMAGE_PROXY, parse_url($image_proxy, PHP_URL_HOST)))
{ {
$image_proxy = self::EGROUPWARE_IMAGE_PROXY; $image_proxy = self::EGROUPWARE_IMAGE_PROXY;
@ -565,7 +565,7 @@ class mail_ui
$etpl->setElementAttribute(self::$nm_index.'[foldertree]','actions', $this->get_tree_actions()); $etpl->setElementAttribute(self::$nm_index.'[foldertree]','actions', $this->get_tree_actions());
// sending preview toolbar actions // sending preview toolbar actions
if ($content['mailSplitter']) $etpl->setElementAttribute('mailPreview[toolbar]', 'actions', $this->get_toolbar_actions()); if (!empty($content['mailSplitter'])) $etpl->setElementAttribute('mailPreview[toolbar]', 'actions', $this->get_toolbar_actions());
// We need to send toolbar actions to client-side because view template needs them // We need to send toolbar actions to client-side because view template needs them
if (Api\Header\UserAgent::mobile()) $sel_options['toolbar'] = $this->get_toolbar_actions(); if (Api\Header\UserAgent::mobile()) $sel_options['toolbar'] = $this->get_toolbar_actions();
@ -1827,7 +1827,7 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
// we have an own created rowID; prepend app=mail // we have an own created rowID; prepend app=mail
array_unshift($res,'mail'); array_unshift($res,'mail');
} }
return array('app'=>$res[0], 'accountID'=>$res[1], 'profileID'=>$res[2], 'folder'=>base64_decode($res[3]), 'msgUID'=>$res[4]); return array('app'=>$res[0], 'accountID'=>$res[1]??null, 'profileID'=>$res[2]??null, 'folder'=>base64_decode($res[3]??null), 'msgUID'=>$res[4]??null);
} }
/** /**

View File

@ -98,6 +98,16 @@
<radio label="Discard message" id="action" options="discard"/> <radio label="Discard message" id="action" options="discard"/>
<description/> <description/>
</row> </row>
<row>
<radio label="set flags" id="action" options="flags"/>
<taglist id="action_flags_list">
<option value="\\Flagged">Flagged</option>
<option value="\\Deleted">Deleted</option>
<option value="\\Seen">Read</option>
<option value="\\Answered">Answered</option>
<option value="\\Draft">Draft</option>
</taglist>
</row>
<row> <row>
<description value="(*) Please consider, forward to multiple addresses will not work if number of addresses exceeds the Limit. For most mail Servers the limit is 4 by default, please contact your mail server administrator for further info."/> <description value="(*) Please consider, forward to multiple addresses will not work if number of addresses exceeds the Limit. For most mail Servers the limit is 4 by default, please contact your mail server administrator for further info."/>
</row> </row>

View File

@ -160,7 +160,7 @@ class notifications_popup implements notifications_iface {
foreach ($rs as $notification) { foreach ($rs as $notification) {
$actions = null; $actions = null;
$data = json_decode($notification['notify_data'], true); $data = json_decode($notification['notify_data'], true);
if ($data['appname'] && $data['data']) if (!empty($data['appname']) && !empty($data['data']))
{ {
$_actions = Api\Hooks::process (array( $_actions = Api\Hooks::process (array(
'location' => 'notifications_actions', 'location' => 'notifications_actions',
@ -175,7 +175,7 @@ class notifications_popup implements notifications_iface {
'created' => Api\DateTime::server2user($notification['notify_created']), 'created' => Api\DateTime::server2user($notification['notify_created']),
'current' => new Api\DateTime('now'), 'current' => new Api\DateTime('now'),
'actions' => is_array($actions)?$actions:NULL, 'actions' => is_array($actions)?$actions:NULL,
'extra_data' => ($data['data'] ? $data['data'] : array()) 'extra_data' => $data['data'] ?? [],
); );
} }

View File

@ -85,13 +85,13 @@ class pixelegg_framework extends Api\Framework\Ajax
{ {
$ret = parent::_get_css(); $ret = parent::_get_css();
// color to use // color to use
$color = str_replace('custom',$GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'], $color = str_replace('custom', $GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'] ?? null,
$GLOBALS['egw_info']['user']['preferences']['common']['template_color']); $GLOBALS['egw_info']['user']['preferences']['common']['template_color'] ?? null);
// Create a dark variant of the color // Create a dark variant of the color
$color_darker = empty($color) ? '' :$this->_color_shader($color, -30); $color_darker = empty($color) ? '' :$this->_color_shader($color, -30);
if (preg_match('/^(#[0-9A-F]+|[A-Z]+)$/i', $GLOBALS['egw_info']['user']['preferences']['common']['sidebox_custom_color'])) if (!empty($GLOBALS['egw_info']['user']['preferences']['common']['sidebox_custom_color']) && preg_match('/^(#[0-9A-F]+|[A-Z]+)$/i', $GLOBALS['egw_info']['user']['preferences']['common']['sidebox_custom_color']))
{ {
$sidebox_color_hover = $GLOBALS['egw_info']['user']['preferences']['common']['sidebox_custom_color']; $sidebox_color_hover = $GLOBALS['egw_info']['user']['preferences']['common']['sidebox_custom_color'];
$sidebox_color = $this->_color_shader($sidebox_color_hover, -30); $sidebox_color = $this->_color_shader($sidebox_color_hover, -30);
@ -101,7 +101,7 @@ class pixelegg_framework extends Api\Framework\Ajax
$sidebox_color_hover = $color; $sidebox_color_hover = $color;
$sidebox_color = $color_darker; $sidebox_color = $color_darker;
} }
if (preg_match('/^(#[0-9A-F]+|[A-Z]+)$/i', $GLOBALS['egw_info']['user']['preferences']['common']['loginbox_custom_color'])) if (!empty($GLOBALS['egw_info']['user']['preferences']['common']['loginbox_custom_color']) && preg_match('/^(#[0-9A-F]+|[A-Z]+)$/i', $GLOBALS['egw_info']['user']['preferences']['common']['loginbox_custom_color']))
{ {
$loginbox_color = $GLOBALS['egw_info']['user']['preferences']['common']['loginbox_custom_color']; $loginbox_color = $GLOBALS['egw_info']['user']['preferences']['common']['loginbox_custom_color'];
} }
@ -109,8 +109,8 @@ class pixelegg_framework extends Api\Framework\Ajax
{ {
$loginbox_color = $color_darker; $loginbox_color = $color_darker;
} }
//alway set header logo used in sharing regardless of custom color being set //always set header logo used in sharing regardless of custom color being set
$header = $GLOBALS['egw_info']['server']['login_logo_header'] ? Api\Framework::get_login_logo_or_bg_url('login_logo_header', 'logo') $header = !empty($GLOBALS['egw_info']['server']['login_logo_header']) ? Api\Framework::get_login_logo_or_bg_url('login_logo_header', 'logo')
: Api\Framework::get_login_logo_or_bg_url('login_logo_file', 'logo'); : Api\Framework::get_login_logo_or_bg_url('login_logo_file', 'logo');
$ret['app_css'] .= " $ret['app_css'] .= "
/* /*

View File

@ -451,7 +451,7 @@ class timesheet_bo extends Api\Storage
{ {
$extra_cols[] = $total_sql.' AS ts_total'; $extra_cols[] = $total_sql.' AS ts_total';
} }
if (!isset($filter['ts_owner']) || !count($filter['ts_owner'])) if (!isset($filter['ts_owner']) || !count((array)$filter['ts_owner']))
{ {
$filter['ts_owner'] = array_keys($this->grants); $filter['ts_owner'] = array_keys($this->grants);
} }

View File

@ -178,41 +178,8 @@ class timesheet_hooks
// Merge print // Merge print
if ($GLOBALS['egw_info']['user']['apps']['filemanager']) if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{ {
$settings['default_document'] = array( $merge = new timesheet_merge();
'type' => 'vfs_file', $settings += $merge->merge_preferences();
'size' => 60,
'label' => 'Default document to insert entries',
'name' => 'default_document',
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.',lang('timesheet')).' '.
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'ts_title').' '.
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
);
$settings['document_dir'] = array(
'type' => 'vfs_dirs',
'size' => 60,
'label' => 'Directory with documents to insert entries',
'name' => 'document_dir',
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the %1 data inserted.', lang('timesheet')) . ' ' .
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'ts_title') . ' ' .
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
'run_lang' => false,
'xmlrpc' => True,
'admin' => False,
'default' => '/templates/timesheet',
);
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
'type' => 'taglist',
'label' => 'Document download filename',
'name' => 'document_download_name',
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
'help' => 'Choose the default filename for downloaded documents.',
'xmlrpc' => True,
'admin' => False,
'default' => 'document',
);
} }
return $settings; return $settings;

View File

@ -156,68 +156,6 @@ class timesheet_merge extends Api\Storage\Merge
return $info; return $info;
} }
/**
* Generate table with replacements for the Api\Preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('timesheet').' - '.lang('Replacements for inserting entries into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Timesheet fields:')."</h3></td></tr>";
$n = 0;
$fields = array('ts_id' => lang('Timesheet ID')) + $this->bo->field2label + array(
'ts_total' => lang('total'),
'ts_created' => lang('Created'),
'ts_modified' => lang('Modified'),
);
foreach($fields as $name => $label)
{
if (in_array($name,array('pl_id','customfields'))) continue; // dont show them
if (in_array($name,array('ts_title', 'ts_description')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->bo->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('Project fields').':</h3></td></tr>';
$pm_merge = new projectmanager_merge();
$i = 0;
foreach($pm_merge->projectmanager_fields as $name => $label)
{
if (!($i&1)) echo '<tr>';
echo '<td>{{ts_project/'.$name.'}}</td><td>'.$label.'</td>';
if ($i&1) echo "</tr>\n";
$i++;
}
echo '<tr><td colspan="4"><h3>' . lang('General fields:') . "</h3></td></tr>";
foreach($this->get_common_replacements() as $name => $label)
{
echo '<tr><td>{{' . $name . '}}</td><td colspan="3">' . $label . "</td></tr>\n";
}
echo "</table>\n";
echo $GLOBALS['egw']->framework->footer();
}
/** /**
* Get a list of placeholders provided. * Get a list of placeholders provided.
* *
@ -256,10 +194,14 @@ class timesheet_merge extends Api\Storage\Merge
} }
} }
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// Add project placeholders // Add project placeholders
$pm_merge = new projectmanager_merge(); $pm_merge = new projectmanager_merge();
$this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project')); $this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project'));
}
return $placeholders; return $placeholders;
} }
} }

Some files were not shown because too many files have changed in this diff Show More