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);
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))
{
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'])
{
$settings['default_document'] = array(
'type' => 'vfs_file',
'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',
);
$merge = new Api\Contacts\Merge();
$settings += $merge->merge_preferences();
}
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(
'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) {

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']);
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($old[$key]);

View File

@ -128,10 +128,10 @@ class admin_customfields
public function index($content = array())
{
// 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'));
$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
$this->fields = Api\Storage\Customfields::get($this->appname,true);
@ -323,10 +323,10 @@ class admin_customfields
*/
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
$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($cf_id && $this->so)
@ -339,7 +339,7 @@ class admin_customfields
{
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
$this->fields = Api\Storage\Customfields::get($this->appname,true);
@ -347,7 +347,7 @@ class admin_customfields
// Update based on info returned from template
if (is_array($content))
{
$action = @key($content['button']);
$action = key($content['button'] ?? []);
switch($action)
{
case 'delete':
@ -422,7 +422,7 @@ class admin_customfields
}
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
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);
}
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
$this->content_types = (array)Api\Link::get_registry($this->appname, 'default_types');
@ -592,7 +592,7 @@ class admin_customfields
*/
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]))
{
$content['error_msg'] .= empty($new_name) ?
@ -601,7 +601,7 @@ class admin_customfields
}
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'];
$this->save_repository();
}

View File

@ -1258,7 +1258,7 @@ class admin_mail
if ($content['ident_id'] != $content['old_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']);
unset($identity['account_id']);
@ -1285,7 +1285,7 @@ class admin_mail
{
$sel_options['ident_email_alias'] = array_merge(
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 ($content['ident_email'] !== $content['mailLocalAddress'] && !isset($sel_options['ident_email_alias'][$content['ident_email']]))
{

View File

@ -44,7 +44,7 @@
</row>
<row>
<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>
</rows>

View File

@ -227,8 +227,9 @@ export class et2_placeholder_select extends et2_inputWidget
app.onchange = (node, widget) =>
{
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.app_select.val('user');
entry.set_value({app: 'user', id: '', query: ''});
@ -338,7 +339,7 @@ export class et2_placeholder_select extends et2_inputWidget
{
continue;
}
options[key].push({
options[this.egw().lang(key)].push({
value: key + '-' + sub,
label: this.egw().lang(sub)
});

View File

@ -50,6 +50,22 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
"type": "string",
"default": "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
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'))
.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){
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 = [];
for (let p in _prefs)
{
@ -242,7 +265,8 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
}).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;
//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)
{
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"
@ -525,7 +549,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
{
this.preference[_action] = _state;
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);
}
/**
@ -537,7 +561,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
{
let button_options = {
};
let button = jQuery(document.createElement('button'))
let button = jQuery(document.createElement('button'))
.addClass("et2_button et2_button_text et2_button_with_image")
.attr('id', this.id+'-'+action.id)
.attr('type', 'button')
@ -773,7 +797,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
id:child,
value: child,
label: _actions[key]['children'][child]['caption'],
app: egw.app_name(),
app: self.options.preference_app,
icon: _actions[key]['children'][child]['iconUrl']
});
}
@ -784,7 +808,7 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
id:key,
value: key,
label: _actions[key]['caption'],
app: egw.app_name(),
app: self.options.preference_app,
icon: _actions[key]['iconUrl']
});
}
@ -808,12 +832,12 @@ export class et2_toolbar extends et2_DOMWidget implements et2_IInput
_value.actions = pref;
}
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);
}).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,
minWidth: 600,
minHeight: 300,

View File

@ -744,20 +744,24 @@ export abstract class EgwApp
// Find what we need
let nm = null;
let action = _action;
let as_pdf = false;
let as_pdf = null;
// Find Select all
while(nm == null && action != null)
while(nm == null && action.parent != null)
{
if(action.data != null && 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;
}
let all = nm?.getSelection().all || false;
as_pdf = action.getActionById('as_pdf')?.checked || false;
as_pdf = as_pdf || false;
// Get list of entry 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 file... common de Dateien wä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 time common de Uhrzeit auswählen
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)
default common de Vorgabe
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 visible actions common de standardmäßig sichtbare Aktionen
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
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 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 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.
@ -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 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?
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.
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

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 file... common en Choose file...
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 time common en Choose Time
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)
default common en Default
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 visible actions common en Default visible actions
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
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 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 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.
@ -857,6 +861,7 @@ maybe common en Maybe
mayotte common en MAYOTTE
medium common en Medium
menu common en Menu
merged document filename preferences en Merged document filename
message common en Message
message ... common en Message ...
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 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
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.
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

View File

@ -468,7 +468,7 @@ class Accounts
$data = self::cache_read($id);
// 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);
}
@ -595,11 +595,15 @@ class Accounts
/**
* 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
*/
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)))
{
return '#'.$account_id;
@ -985,7 +989,7 @@ class Accounts
$ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : $data['memberships'];
}
//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;
// 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']]) &&
$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']['server']['auth_type_'.$GLOBALS['egw_info']['flags']['currentapp']];
}
@ -223,11 +222,11 @@ class Auth
{
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));
}
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
$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
$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
$GLOBALS['egw_info']['server']['change_pwd_every_x_days'] &&
$GLOBALS['egw_info']['user']['apps']['preferences'] &&
$GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change'] &&
!empty($GLOBALS['egw_info']['server']['change_pwd_every_x_days']) &&
!empty($GLOBALS['egw_info']['user']['apps']['preferences']) &&
!empty($GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change']) &&
$GLOBALS['egw_info']['server']['warn_about_upcoming_pwd_change'] > $daysLeftUntilChangeReq &&
$UserKnowsAboutPwdChange !== true)
{
@ -255,8 +254,8 @@ class Auth
else
{
// login page does not inform user about passwords about to expire
if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' &&
($GLOBALS['egw_info']['flags']['currentapp'] != 'home' ||
if ($GLOBALS['egw_info']['flags']['currentapp'] !== 'login' &&
($GLOBALS['egw_info']['flags']['currentapp'] !== 'home' ||
strpos($_SERVER['SCRIPT_NAME'], '/home/') !== false))
{
$UserKnowsAboutPwdChange = true;

View File

@ -853,7 +853,7 @@ class Categories
if (is_null(self::$cache)) self::init_cache();
$cat = self::$cache[$cat_id];
$cat = self::$cache[$cat_id] ?? null;
if ($item == 'path')
{
if ($cat['parent'])
@ -864,7 +864,7 @@ class Categories
}
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])
{

View File

@ -210,7 +210,7 @@ class Config
{
self::init_static();
}
return (array)self::$configs[$app];
return self::$configs[$app] ?? [];
}
/**
@ -238,7 +238,7 @@ class Config
{
$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';
}
// 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;
$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 &&
($this->default_private ||
$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').')',
);
//_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);
// 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))
{
$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'];
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->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;
}
/**
* 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.
*
@ -352,10 +239,58 @@ class Merge extends Api\Storage\Merge
'label' => "Formatted private address"
];
$placeholders['EPL only'][] = [
'value' => $this->prefix($prefix, 'share', '{'),
'label' => 'Public sharing URL'
];
$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;
}
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
*

View File

@ -77,15 +77,15 @@ class Sql extends Api\Storage
// Get custom fields from addressbook instead of api
$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'];
}
elseif ($GLOBALS['egw_info']['server']['auth_type'])
elseif (!empty($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'];
}
@ -742,7 +742,7 @@ class Sql extends Api\Storage
$cat_filter = array();
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;
}

View File

@ -256,7 +256,7 @@ class Storage
}
$this->customfields = Api\Storage\Customfields::get('addressbook');
// 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->somain = new Ldap();
@ -264,7 +264,7 @@ class Storage
}
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';
}
@ -347,9 +347,9 @@ class Storage
if ($user)
{
// 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);
foreach($GLOBALS['egw']->accounts->memberships($user,true) as $gid)
{
@ -415,9 +415,9 @@ class Storage
*/
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'],
$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->Type = 'mysql';
}
if ($new_connection)
if (!empty($new_connection))
{
foreach(get_included_files() as $file)
{
@ -1599,7 +1599,7 @@ class Db
{
return $array;
}
if (!$column_definitions)
if (empty($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
self::$pdo = new \PDO($dsn,$egw_db->User,'$egw_db->Password');
}
if ($query)
if (!empty($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)
{
if ($extra['data'] && is_array($extra['data']))
if (!empty($extra['data']) && is_array($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']);
}
if ($extra['readonlys'] && is_array($extra['readonlys']))
if (!empty($extra['readonlys']) && is_array($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']);
}
@ -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
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
$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];
@ -209,7 +209,7 @@ class Etemplate extends Etemplate\Widget\Template
'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
// (some content by reference)
@ -420,7 +420,7 @@ class Etemplate extends Etemplate\Widget\Template
}
$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(
array(

View File

@ -428,9 +428,9 @@ class Request
* @param string $var
* @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;
//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
*
* @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);

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

View File

@ -553,12 +553,21 @@ class Widget
$method = new ReflectionMethod($this, $method_name);
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()}");
$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()}");
$params[$index] = (array)$params[$index];
@ -1044,6 +1053,10 @@ class Widget
*/
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());
$ref =& self::$request->modifications[$name][$attr];
if(self::$request && self::$response)

View File

@ -50,7 +50,7 @@ class Box extends Etemplate\Widget
$old_expand = $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['cname'] = $cname;

View File

@ -98,7 +98,7 @@ class Customfields extends Transformer
$form_name = self::form_name($cname, $this->id, $expand);
// Store properties at top level, so all customfield widgets can share
if($this->attrs['app'])
if (!empty($this->attrs['app']))
{
$app = $this->attrs['app'];
}
@ -141,12 +141,12 @@ class Customfields extends Transformer
// app changed
$customfields = Api\Storage\Customfields::get($app);
}
if($this->attrs['customfields'])
if (!empty($this->attrs['customfields']))
{
$customfields = $this->attrs['customfields'];
}
// Filter fields
if($this->attrs['field-names'])
if (!empty($this->attrs['field-names']))
{
$fields_name = explode(',', $this->attrs['field-names']);
foreach($fields_name as &$f)
@ -162,8 +162,8 @@ class Customfields extends Transformer
$fields = $customfields;
$use_private = self::expand_name($this->attrs['use-private'],0,0,'','',self::$cont);
$this->attrs['sub-type'] = self::expand_name($this->attrs['sub-type'],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'] ?? null,0,0,'','',self::$cont);
foreach((array)$fields as $key => $field)
{
@ -174,7 +174,7 @@ class Customfields extends Transformer
}
// 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]);
}
@ -284,7 +284,7 @@ class Customfields extends Transformer
$type = $field['type'];
// Link-tos needs to change from appname to link-to
if($link_types[$field['type']])
if (!empty($link_types[$field['type']]))
{
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';
}
if($field['values']['min']) $widget->attrs['min'] = $field['values']['min'];
if($field['values']['max']) $widget->attrs['min'] = $field['values']['max'];
if (isset($field['values']['min'])) $widget->attrs['min'] = $field['values']['min'];
if (isset($field['values']['max'])) $widget->attrs['min'] = $field['values']['max'];
break;
case 'vfs-upload':
@ -355,7 +355,7 @@ class Customfields extends Transformer
$field['values'] = Api\Storage\Customfields::get_options_from_file($field['values']['@']);
}
// 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'];
}

View File

@ -42,7 +42,7 @@ class Description extends Etemplate\Widget
*/
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);
$value =& self::get_array(self::$request->content, $form_name);

View File

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

View File

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

View File

@ -130,13 +130,13 @@ class Nextmatch extends Etemplate\Widget
$send_value = $value;
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']);
}
// 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']));
$pref_name = "favorite_" .$safe_name;
@ -210,7 +210,7 @@ class Nextmatch extends Etemplate\Widget
}
// 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')) +
Select::typeOptions('select-account',',groups');
@ -894,7 +894,7 @@ class Nextmatch extends Etemplate\Widget
if ($default_attrs) $action += $default_attrs;
// 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(

View File

@ -49,7 +49,8 @@ class Password extends Etemplate\Widget\Textbox
{
$form_name = self::form_name($cname, $this->id, $expand);
$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']);
if (!empty($value))

View File

@ -64,9 +64,13 @@ class Placeholder extends Etemplate\Widget
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
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)
@ -86,6 +90,8 @@ class Placeholder extends Etemplate\Widget
// Looks like app doesn't support merging
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() : [];
break;
}

View File

@ -109,14 +109,14 @@ class Select extends Etemplate\Widget
{
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'] || $this->attrs['multiple'] === 'false');
}
// 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'];
if ((string)$this->attrs['multiple'] == $this->attrs['options'])
@ -124,7 +124,7 @@ class Select extends Etemplate\Widget
unset($this->attrs['options']);
}
}
elseif($this->attrs['rows'] > 1)
elseif(isset($this->attrs['rows']) && $this->attrs['rows'] > 1)
{
$this->attrs['multiple'] = true;
}
@ -311,8 +311,8 @@ class Select extends Etemplate\Widget
{
$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();
$type = $this->attrs['type'] ? $this->attrs['type'] : $this->type;
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->type;
if ($type != 'select' && $type != 'menupopup')
{
// 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]) &&
($type_options = self::typeOptions($this,
// 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']),
$no_lang, $this->attrs['readonly'], self::get_array(self::$request->content, $form_name), $form_name)))
(!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'] ?? false, self::get_array(self::$request->content, $form_name), $form_name)))
{
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);
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
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);
}
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;
$options = array();
switch ($widget_type)
@ -644,7 +644,7 @@ class Select extends Etemplate\Widget
// These are extra info for easy dealing with categories
// client side, without extra loading
'main' => (int)$cat['main'],
'children' => $cat['children'],
'children' => $cat['children'] ?? null,
//add different class per level to allow different styling for each category level:
'class' => "cat_level". $cat['level']
);
@ -839,7 +839,7 @@ class Select extends Etemplate\Widget
}
foreach((array)$options as $right => $name)
{
if(!!($value & $right))
if (!!((int)$value & (int)$right))
{
$new_value[] = $right;
}

View File

@ -68,7 +68,7 @@ class Template extends Etemplate\Widget
list($name) = explode('?', $_name); // remove optional cache-buster
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");
return self::$cache[$name];
@ -146,7 +146,7 @@ class Template extends Etemplate\Widget
{
static $prefixes = null;
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))
{
@ -184,7 +184,7 @@ class Template extends Etemplate\Widget
$path = $prefix.$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];
$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
// 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'])
{
$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);
}

View File

@ -62,14 +62,14 @@ class Textbox extends Etemplate\Widget
parent::set_attrs($xml, $cloned);
// 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.
if ($this->attrs['size'] < 0)
if (isset($this->attrs['size']) && $this->attrs['size'] < 0)
{
self::setElementAttribute($this->id, 'size', abs($this->attrs['size']));
self::$request->readonlys[$this->id] = false;
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;
}

View File

@ -143,7 +143,7 @@ class Tree extends Etemplate\Widget
parent::set_attrs($xml, $cloned);
// 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);
}
@ -297,21 +297,21 @@ class Tree extends Etemplate\Widget
{
$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);
//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 ($this->attrs['type'])
if (empty(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = [];
if (!empty($this->attrs['type']))
{
// += to keep further options set by app code
self::$request->sel_options[$form_name] += self::typeOptions($this->attrs['type'], $this->attrs['options'],
$no_lang, $this->attrs['readonly'], self::get_array(self::$request->content, $form_name), $form_name);
self::$request->sel_options[$form_name] += self::typeOptions($this->attrs['type'], $this->attrs['options'] ?? null,
$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 != $this->attr['no_lang'])
if (!isset($this->attr['no_lang']) || $no_lang != $this->attr['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)
{
list($rows,$type,$type2,$type3) = explode(',',$legacy_options);
list($rows,$type,$type2,$type3) = explode(',', $legacy_options)+[null,null,null,null];
$no_lang = false;
$options = array();

View File

@ -37,10 +37,10 @@ class Vfs extends File
*/
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));
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']);
}
@ -226,7 +226,7 @@ class Vfs extends File
foreach($links as $link)
{
$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))] =
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
*
* @link http://www.stylite.de
* @link https://www.egroupware.org
* @package api
* @subpackage framework
* @author Andreas Stöckel <as@stylite.de>
* @author Ralf Becker <rb@stylite.de>
* @author Nathan Gray <ng@stylite.de>
* @author Andreas Stöckel
* @author Ralf Becker <rb@egroupware.org>
* @author Nathan Gray <ng@egroupware.org>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
@ -16,7 +16,7 @@ namespace EGroupware\Api\Framework;
use EGroupware\Api;
/**
* Stylite jdots template
* Framework for Ajax based templates
*/
abstract class Ajax extends Api\Framework
{
@ -88,13 +88,13 @@ abstract class Ajax extends Api\Framework
$width = self::DEFAULT_SIDEBAR_WIDTH;
//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'];
// error_log(__METHOD__.__LINE__."($app):$width --> reading jdotssideboxwidth");
}
//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'];
// 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']='';
// 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
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();
$footer = "\n".$vars['page_generation_time']."\n";
}
}
return $footer.
$GLOBALS['egw_info']['flags']['need_footer']."\n". // eg. javascript, which need to be at the end of the page
return ($footer??'').
($GLOBALS['egw_info']['flags']['need_footer']??'')."\n". // eg. javascript, which need to be at the end of the page
"</body>\n</html>\n";
}

View File

@ -49,12 +49,12 @@ class Bundle
unset($GLOBALS['egw_info']['server']['debug_minify']);
$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
//$bundles = self::all(); Cache::setTree(__CLASS__, 'bundles', $bundles, 7200);
$bundles = Cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'all'), array(), 7200);
$bundles_ts = $bundles['.ts'];
$bundles_ts = $bundles['.ts'] ?? null;
unset($bundles['.ts']);
foreach($bundles as $name => $files)
{
@ -83,13 +83,13 @@ class Bundle
if (!isset($to_include[$file]))
{
if (($bundle = $file2bundle[$file]))
if (($bundle = $file2bundle[$file] ?? false))
{
//error_log(__METHOD__."() requiring bundle $bundle for $file");
if (!in_array($bundle, $included_bundles))
{
$included_bundles[] = $bundle;
$minurl = self::$bundle2minurl[$bundle];
$minurl = self::$bundle2minurl[$bundle] ?? null;
if (!isset($minurl) && isset($GLOBALS['egw_info']['apps'][$bundle]))
{
$minurl = '/'.$bundle.'/js/app.min.js';
@ -108,10 +108,10 @@ class Bundle
else
{
unset($query);
list($path, $query) = explode('?', $file, 2);
list($path, $query) = explode('?', $file, 2)+[null,null];
$mod = filemtime(EGW_SERVER_ROOT.$path);
// 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')) &&
(($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)
{
$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
if (!empty($minurl) && !file_exists(EGW_SERVER_ROOT.$minurl)) $minurl = null;
$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
unset($query);
list($path,$query) = explode('?',$path,2);
list($path,$query) = explode('?',$path,2)+[null,null];
$mod = filemtime(EGW_SERVER_ROOT.$path);
if ($mod > $max_modified) $max_modified = $mod;

View File

@ -113,7 +113,7 @@ class CssIncludes
{
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;
// 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>
$result4 = preg_replace_callback( $Expr, function ($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)
{
$match[3] = substr($match[3],0,strpos($match[3],'&gt',strlen($match[3])-4));
@ -111,7 +112,7 @@ class Html
$match[4] = "&gt;";
}
//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 );
}
return $result4;
@ -755,7 +756,7 @@ tinymce.init({
{
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)
{
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:.*/'),
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'].']';
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)
{
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";
}
if ($link_id < 0) // vfs-link?
if ((int)$link_id < 0) // vfs-link?
{
return self::delete_attached(-$link_id);
}
elseif ($app == self::VFS_APPNAME)
elseif ($app === self::VFS_APPNAME)
{
return self::delete_attached($app2,$id2,$id);
}
elseif ($app2 == self::VFS_APPNAME)
elseif ($app2 === self::VFS_APPNAME)
{
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";
}
if (($not_only = $only_app[0] == '!'))
if (!empty($only_app) && ($not_only = $only_app[0] == '!'))
{
$only_app = substr($only_app,1);
}
@ -173,7 +173,7 @@ class Storage
catch(Api\Db\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)

View File

@ -414,14 +414,10 @@ class Mail
{
//error_log(__METHOD__." Session restore ".function_backtrace());
$this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
}
else
{
$this->restoreSessionData();
$lv_mailbox = $this->sessionData['mailbox'];
$firstMessage = $this->sessionData['previewMessage'];
$this->sessionData = array();
}
if (!$_reuseCache) $this->forcePrefReload($_profileID,!$_reuseCache);
@ -1000,9 +996,7 @@ class Mail
*/
static function getTimeOut($_use='IMAP')
{
$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
if (empty($timeout)) $timeout = ($_use=='SIEVE'?10:20); // this is the default value
return $timeout;
return $_use=='SIEVE' ? 10 : 20; // this is the default value
}
/**
@ -3211,7 +3205,7 @@ class Mail
}
//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
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 {
// avoid calling sortByAutoFolder as it is not regarding subfolders
$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
@ -5589,10 +5583,10 @@ class Mail
if (empty($_folder)) $_folder = $this->sessionData['mailbox']?: $this->icServer->getCurrentMailbox();
$_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");
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();
@ -5629,7 +5623,7 @@ class Mail
if (!$_stream)
{
//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;
}
@ -6775,6 +6769,8 @@ class Mail
//error_log(__METHOD__."()");
$imageC = 0;
$images = null;
$attachments = null;
if (preg_match_all("/(src|background)=\"(.*)\"/Ui", $_html2parse, $images) && isset($images[2]))
{
foreach($images[2] as $i => $url)
@ -6782,18 +6778,18 @@ class Mail
//$isData = false;
$basedir = $data = '';
$needTempFile = true;
$attachmentData = ['name' => '', 'type' => '', 'file' => '', 'tmp_name' => ''];
try
{
// 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 = '';
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$mimeType = MimeMagic::ext2mime($ext);
if ( strlen($directory) > 1 && substr($directory,-1) != '/') { $directory .= '/'; }
$myUrl = $directory.$filename;
$ext = pathinfo($attachmentData['name'], PATHINFO_EXTENSION);
$attachmentData['type'] = MimeMagic::ext2mime($ext);
if ( strlen($directory) > 1 && !str_ends_with($directory, '/')) { $directory .= '/'; }
$myUrl = $directory.$attachmentData['name'];
if ($myUrl[0]=='/') // local path -> we only allow path's that are available via http/https (or vfs)
{
$basedir = Framework::getUrl('/');
@ -6801,7 +6797,7 @@ class Mail
// 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
// 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');
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
// 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();
// Strips the url and store it into a temp for further procss
@ -6826,50 +6822,50 @@ class Mail
if ($attachment)
{
$data = $attachment->getContents();
$mimeType = $attachment->getType();
$filename = $attachment->getDispositionParameter('filename');
$attachmentData['type'] = $attachment->getType();
$attachmentData['name'] = $attachment->getDispositionParameter('filename');
}
}
}
if ( strlen($basedir) > 1 && substr($basedir,-1) != '/' && $myUrl[0]!='/') { $basedir .= '/'; }
if ($needTempFile && !$attachment && substr($myUrl,0,4) !== "http") $data = file_get_contents($basedir.urldecode($myUrl));
if ( $myUrl[0]!='/' && strlen($basedir) > 1 && !str_ends_with($basedir, '/')) { $basedir .= '/'; }
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]));
// we only support base64 encoded 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);
// 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;
$filename = ($what?$what:'data').$imageC++.'.'.$exactly;
$attachmentData['name'] = ($what ?: 'data').$imageC++.'.'.$exactly;
}
if ($data || $needTempFile === false)
{
if ($needTempFile)
{
$attachment_file =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
$tmpfile = fopen($attachment_file,'w');
$attachmentData['file'] =tempnam($GLOBALS['egw_info']['server']['temp_dir'],$GLOBALS['egw_info']['flags']['currentapp']."_");
$tmpfile = fopen($attachmentData['file'],'w');
fwrite($tmpfile,$data);
fclose($tmpfile);
}
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
// 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
$cid = 'cid:' . md5($attachment_file);
if ($_mailObject->AddEmbeddedImage($attachment_file, substr($cid, 4), urldecode($filename), $mimeType) !== null)
$cid = 'cid:' . md5($attachmentData['file']);
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 = 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($e->getTraceAsString());
}
$attachments [] = array(
'name' => $filename,
'type' => $mimeType,
'file' => $attachment_file,
'tmp_name' => $attachment_file
);
$attachmentData['tmp_name'] = $attachmentData['file'];
$attachments [] = $attachmentData;
}
return is_array($attachments) ? $attachments : null;
}

View File

@ -549,7 +549,7 @@ class Account implements \ArrayAccess
$row = array_merge($row, Credentials::from_session($row));
}
// 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_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)
{
$data['ident_email'] = $GLOBALS['egw_info']['user']['account_email'];
$data['ident_email'] = $GLOBALS['egw_info']['user']['account_email'] ?? null;
}
}
if (empty($data['ident_realname']))
{
$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
// (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']);
// 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']).'!');
}
$password = base64_decode(Api\Cache::getSession('phpgwapi', 'password'));
$realname = !$set_identity || $data['ident_realname'] ? $data['ident_realname'] :
$GLOBALS['egw_info']['user']['account_fullname'];
$email = !$set_identity || $data['ident_email'] ? $data['ident_email'] :
$GLOBALS['egw_info']['user']['account_email'];
$realname = !$set_identity || !empty($data['ident_realname']) ? $data['ident_realname'] :
($GLOBALS['egw_info']['user']['account_fullname'] ?? null);
$email = !$set_identity || !empty($data['ident_email']) ? $data['ident_email'] :
($GLOBALS['egw_info']['user']['account_email'] ?? null);
return array(
'ident_realname' => $realname,

View File

@ -169,7 +169,7 @@ class Html
if ($addbracesforendtag === true )
{
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]));
// only replace what we have found
@ -495,7 +495,7 @@ class Html
$html = preg_replace('/&(?!#?[a-zA-Z0-9]+;)/', '&amp;', $html);
$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'),
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')
{
$timeout = $GLOBALS['egw_info']['user']['preferences']['mail']['connectionTimeout'];
if (empty($timeout) || !($timeout > 0)) $timeout = $_use == 'SIEVE' ? 10 : 20; // this is the default value
return $timeout;
return $_use == 'SIEVE' ? 10 : 20; // this is the default value
}
/**
@ -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
* 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)
{
@ -794,10 +792,10 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
}
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 'getVacation':
case 'setVacation':
case 'getExtensions':
if (is_null($this->sieve))
{
$this->sieve = new Sieve($this);

View File

@ -73,7 +73,7 @@ class Notifications
$account_specific = 0;
foreach($rows as $row)
{
if ($row['account_id'])
if (!empty($row['account_id']))
{
$account_specific = $row['account_id'];
}
@ -82,7 +82,7 @@ class Notifications
{
self::$cache[$acc_id][$row['account_id']][] = $row['notif_folder'];
} // 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;
}

View File

@ -34,6 +34,7 @@ class Script
var $emailNotification; /* email notification settings. */
var $pcount; /* highest priority value in ruleset. */
var $errstr; /* error text. */
var $extensions; /* contains extensions status*/
/**
* Body transform content types
*
@ -69,6 +70,21 @@ class Script
$this->emailNotification = array(); // Added email notifications
$this->pcount = 0;
$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
@ -86,6 +102,7 @@ class Script
$anyofbit = 4;
$keepbit = 8;
$regexbit = 128;
$this->_setExtensionsStatus($connection);
if (!isset($this->name)){
$this->errstr = 'retrieveRules: no script name specified';
@ -150,10 +167,10 @@ class Script
$rule['anyof'] = ($bits[8] & $anyofbit);
$rule['keep'] = ($bits[8] & $keepbit);
$rule['regexp'] = ($bits[8] & $regexbit);
$rule['bodytransform'] = ($bits[12]);
$rule['field_bodytransform'] = ($bits[13]);
$rule['ctype'] = ($bits[14]);
$rule['field_ctype_val'] = ($bits[15]);
$rule['bodytransform'] = ($bits[12]??null);
$rule['field_bodytransform'] = ($bits[13]??null);
$rule['ctype'] = ($bits[14]??null);
$rule['field_ctype_val'] = ($bits[15]??null);
$rule['unconditional'] = 0;
if (!$rule['from'] && !$rule['to'] && !$rule['subject'] &&
!$rule['field'] && !$rule['size'] && $rule['action']) {
@ -188,7 +205,7 @@ class Script
}
$vacation['addresses'] = &$vaddresses;
$vacation['forwards'] = $bits[5];
$vacation['forwards'] = $bits[5]??null;
}
break;
case "notify":
@ -236,7 +253,6 @@ class Script
$activerules = 0;
$regexused = 0;
$regexsupported = true;
$rejectused = 0;
$vacation_active = false;
@ -245,13 +261,10 @@ class Script
//include "$default->lib_dir/version.php";
// lets generate the main body of the script from our rules
$enotify = $variables= $supportsbody = false;
if ($connection->hasExtension('enotify')) $enotify = true;
if ($connection->hasExtension('variables')) $variables = true;
if ($connection->hasExtension('body')) $supportsbody = true;
if (!$connection->hasExtension('vacation')) $this->vacation = false;
if (!$connection->hasExtension('regex')) $regexsupported = false;
// set extensions status
$this->_setExtensionsStatus($connection);
if (!$this->extensions['vacation']) $this->vacation = false;
$newscriptbody = "";
$continue = 1;
@ -334,7 +347,7 @@ class Script
$newruletext .= "size " . $xthan . $rule['size'] . "K";
$started = 1;
}
if ($supportsbody){
if ($this->extensions['body']){
if (!empty($rule['field_bodytransform'])){
if ($started) $newruletext .= ", ";
$btransform = " :raw ";
@ -379,6 +392,9 @@ class Script
if (preg_match("/discard/i",$rule['action'])) {
$newruletext .= "discard;";
}
if (preg_match("/flags/i",$rule['action'])) {
$newruletext .= "addflag \"".$rule['action_arg']."\";";
}
if ($rule['keep']) $newruletext .= "\n\tkeep;";
if (!$rule['unconditional']) $newruletext .= "\n}";
@ -417,7 +433,7 @@ class Script
$vacation_active = true;
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
$regexused = 1;
@ -441,17 +457,17 @@ class Script
}
$newscriptbody .= "\tkeep;\n}\n";
}
$newscriptbody .= "vacation :days " . $vacation['days'];
$vac_rule = "vacation :days " . $vacation['days'];
$first = 1;
if (!empty($vacation['addresses'][0]))
{
$newscriptbody .= " :addresses [";
$vac_rule .= " :addresses [";
foreach ($vacation['addresses'] as $vaddress) {
if (!$first) $newscriptbody .= ", ";
$newscriptbody .= "\"" . trim($vaddress) . "\"";
if (!$first) $vac_rule .= ", ";
$vac_rule .= "\"" . trim($vaddress) . "\"";
$first = 0;
}
$newscriptbody .= "] ";
$vac_rule .= "] ";
}
$message = $vacation['text'];
if ($vacation['start_date'] || $vacation['end_date'])
@ -463,7 +479,20 @@ class Script
date($format_date,$vacation['end_date']),
),$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.
@ -476,10 +505,10 @@ class Script
// format notification body
$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}";
if ($variables)
if ($this->extensions['variables'])
{
$notification_body .= ", ";
$notification_body .= 'From: ${from}';
@ -522,13 +551,18 @@ class Script
if ($activerules) {
$newscripthead .= "require [\"fileinto\"";
if ($regexsupported && $regexused) $newscripthead .= ",\"regex\"";
if ($this->extensions['regex'] && $regexused) $newscripthead .= ",\"regex\"";
if ($rejectused) $newscripthead .= ",\"reject\"";
if ($this->vacation && $vacation_active) {
$newscripthead .= ",\"vacation\"";
}
if ($supportsbody) $newscripthead .= ",\"body\"";
if ($this->emailNotification && $this->emailNotification['status'] == 'on') $newscripthead .= ',"'.($enotify?'e':'').'notify"'.($variables?',"variables"':''); // Added email notifications
if ($this->extensions['body']) $newscripthead .= ",\"body\"";
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";
} else {
// no active rules, but might still have an active vacation rule
@ -536,18 +570,21 @@ class Script
if ($this->vacation && $vacation_active)
{
$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;
}
if ($this->emailNotification && $this->emailNotification['status'] == 'on')
{
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
{
$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";
@ -570,7 +607,7 @@ class Script
$newscriptfoot .= "#rule&&" . $rule['priority'] . "&&" . $rule['status'] . "&&" .
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'];
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";
$pcount = $pcount+2;
//error_log(__CLASS__."::".__METHOD__.__LINE__.array2string($newscriptfoot));
@ -616,7 +653,7 @@ class Script
}
catch (\Exception $e) {
$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__.' # ScriptName:'.$this->name.' Script:'.$newscript);
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;
}
@ -191,7 +191,7 @@ class Sql extends Mail\Smtp
case self::TYPE_MAILBOX:
$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']]));
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['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;
}
@ -240,7 +240,7 @@ class Sql extends Mail\Smtp
function setUserData($_uidnumber, array $_mailAlternateAddress, array $_mailForwardingAddress, $_deliveryMode,
$_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)
{

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'));
// 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;
}
@ -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
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 .= (!isset($e) ? 'Mail send' : 'Mail NOT send').
@ -732,7 +732,7 @@ class Mailer extends Horde_Mime_Mail
$recipients->add($h->getAddressList());
}
}
if ($this->_bcc) {
if (!empty($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
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))
{
$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))
{
$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(
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();
}
}
$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);
if ($this->debug) error_log(__METHOD__."() ".$this->db->Query_ID->sql);
$cols = $this->_get_columns($only_keys,$extra_cols);
}
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();
}
@ -1157,8 +1157,8 @@ class Base
}
}
}
if (is_array($query) && $op != 'AND') $query = $this->db->column_data_implode(' '.$op.' ',$query);
return $query;
if (!empty($query) && is_array($query) && $op != 'AND') $query = $this->db->column_data_implode(' '.$op.' ',$query);
return $query ?? null;
}
/**

View File

@ -190,7 +190,7 @@ class Customfields implements \IteratorAggregate
/**
* 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
* @return string formatted value
*/
@ -204,7 +204,7 @@ class Customfields implements \IteratorAggregate
$values = array();
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);
}

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']));
$details['#'.$name] = array(
'label' => $field['label'],
'value' => Customfields::format($field, $data['#'.$name]),
'value' => Customfields::format($field, $data['#'.$name] ?? null),
);
//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));
$old_assignees = array();
$assignees = $assigned ? $assigned : array();
if ($data[$this->assigned_field]) // current assignments
$assignees = $assigned ?? array();
if (!empty($data[$this->assigned_field])) // current assignments
{
$assignees = is_array($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[$this->assigned_field] : explode(',',$old[$this->assigned_field]);
@ -1050,7 +1050,7 @@ abstract class Tracking
// remove the session-id in the notification mail!
$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));
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 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 (empty($detail['value']) && !$modified) continue; // skip unchanged, empty values
$body .= $this->format_line($html_email,$detail['type'],$modified,
$detail['label'] ? $detail['label'] : '', $detail['value']);
$body .= $this->format_line($html_email, $detail['type'] ?? null, $modified,
$detail['label'] ?? '', $detail['value']);
}
if ($html_email)
{
$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";
}
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);
}
@ -1271,7 +1271,7 @@ abstract class Tracking
$merge_class = $this->app.'_merge';
$merge = new $merge_class();
$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)
{
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);
}
if($replace_user_pass_host)
if ($replace_user_pass_host)
{
$url = str_replace(array('$user',
'$pass',
'$host',
'$home'), array($parts['user'],
$parts['pass'],
$parts['host'],
$parts['home']), $url);
$url = strtr($url, [
'$user' => $parts['user'],
'$pass' => $parts['pass'],
'$host' => $parts['host'],
'$home' => $parts['home'],
]);
}
if($parts['query'])
if (isset($parts['query']))
{
$url .= '?' . $parts['query'];
}
if($parts['fragment'])
if (isset($parts['fragment']))
{
$url .= '#' . $parts['fragment'];
}
@ -657,7 +656,7 @@ class Base
return false;
}
$k = (string)Vfs::parse_url($url, PHP_URL_SCHEME);
if(!(is_array($scheme2urls[$k])))
if (!isset($scheme2urls[$k]))
{
$scheme2urls[$k] = array();
}

View File

@ -798,11 +798,11 @@ class StreamWrapper extends Base implements StreamWrapperIface
{
$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;
}
if($stat['url'] && $query && strpos($stat['url'],'?'.$query)===false)
if ($stat && $stat['url'] && $query && strpos($stat['url'],'?'.$query) === false)
{
$stat['url'] .= '?'.$query;
}
@ -998,7 +998,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
}
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))
{

View File

@ -71,6 +71,7 @@ function _egw_log_exception($e,&$headline=null)
{
error_log($headline.($e instanceof egw_exception_warning ? ': ' : ' ('.get_class($e).'): ').
$e->getMessage().(!empty($e->details) ? ': '.$e->details : ''));
error_log('File: '.str_replace(EGW_SERVER_ROOT, '', $e->getFile()).', Line: '.$e->getLine());
foreach($trace as $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')
{
echo ($headline ? $headline.': ' : '').$e->getMessage()."\n";
echo $e->getFile().' ('.$e->getLine().")\n";
if ($GLOBALS['egw_info']['server']['exception_show_trace'])
{
echo $e->getTraceAsString()."\n";
@ -116,6 +118,8 @@ function egw_exception_handler($e)
$message = '<h3>'.Api\Html::htmlspecialchars($headline)."</h3>\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
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_USER_ERROR:
error_log(__METHOD__."($errno, '$errstr', '$errfile', $errline)");
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
case E_WARNING:

View File

@ -175,6 +175,8 @@ function php_safe_unserialize($str)
*/
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;') &&
($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[] = $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
if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{
$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('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',
);
$merge = new calendar_merge();
$settings += $merge->merge_preferences();
}
$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
if (($attributes['params'] || count($attributes['values']) > 1) &&
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)
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 (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']]);
}

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";
$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
$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']);
if ($end) $where[] = (int)$end.' > range_start';
}

View File

@ -642,16 +642,20 @@ class calendar_ui
*
* @param int $event_id
* @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
* 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(is_string($event_id) && strpos($event_id,':') !== FALSE)
if(!$event_id)
{
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);
}
@ -698,6 +702,21 @@ class calendar_ui
else if($event['recur_type'] )
{
$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->rewind();
do
@ -705,10 +724,17 @@ class calendar_ui
$occurrence = $rrule->current();
$converted = $this->bo->read($event['id'], $occurrence);
$this->to_client($converted);
$response->generic('data', array('uid' => 'calendar::'.$converted['row_id'], 'data' => $converted));
$data['calendar::' . $converted['row_id']] = $converted;
$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;
}

View File

@ -1007,7 +1007,7 @@ class calendar_uiforms extends calendar_ui
$response = Api\Json\Response::get();
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 : '');
@ -1267,8 +1267,6 @@ class calendar_uiforms extends calendar_ui
Api\DateTime::to($as_of_date,'ts') < time()
)
{
unset($orig_event);
// copy event by unsetting the id(s)
unset($event['id']);
unset($event['uid']);
@ -1325,7 +1323,8 @@ class calendar_uiforms extends calendar_ui
}
$last->setTime(0, 0, 0);
$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 />'.
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);
}
}
$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'])
{
$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[] = $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!');
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
let existing = egw.dataGetUIDdata('calendar::'+pushData.id);
let existing = egw.dataGetUIDdata('calendar::' + pushData.id);
if(existing && Math.abs(existing.timestamp - new Date().valueOf()) < 1000)
{
// Update directly
this._update_events(this.state, ['calendar::'+pushData.id]);
this._update_events(this.state, ['calendar::' + pushData.id]);
return;
};
}
;
// 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
egw.dataStoreUID(data.uid, data.data);
// Any existing events were updated. Run this to catch new events or events moved into view
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')
{
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.
*

View File

@ -46,6 +46,14 @@
"type": "pear",
"url": "https://pear.horde.org"
},
{
"type": "vcs",
"url": "https://github.com/egroupware/Crypt"
},
{
"type": "vcs",
"url": "https://github.com/egroupware/Compress"
},
{
"type": "vcs",
"url": "https://github.com/IMSGlobal/lti-1-3-php-library"
@ -81,6 +89,8 @@
"egroupware/adodb-php": "self.version",
"egroupware/bookmarks": "self.version",
"egroupware/collabora": "self.version",
"egroupware/compress": "^2.2.3",
"egroupware/crypt": "^2.7.13",
"egroupware/guzzlestream": "dev-master",
"egroupware/icalendar": "^2.1.9",
"egroupware/magicsuggest": "^2.1",
@ -99,8 +109,6 @@
"npm-asset/as-jqplot": "1.0.*",
"npm-asset/gridster": "0.5.*",
"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_mail": "^2.1.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(
'type' => 'vfs_dir',
'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',
);
$merge = new filemanager_merge();
$settings += $merge->merge_preferences();
$editorLink = self::getEditorLink();
$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

@ -26,15 +26,14 @@ class filemanager_merge extends Api\Storage\Merge
* @var array
*/
var $public_functions = array(
'show_replacements' => true,
'merge_entries' => true
'show_replacements' => true,
'merge_entries' => true
);
/**
* Fields that are numeric, for special numeric handling
*/
protected $numeric_fields = array(
);
protected $numeric_fields = array();
/**
* Fields that are dates or timestamps
@ -74,12 +73,12 @@ class filemanager_merge extends Api\Storage\Merge
* Get replacements
*
* @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
*/
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;
}
@ -90,58 +89,58 @@ class filemanager_merge extends Api\Storage\Merge
* Get filemanager replacements
*
* @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
*/
public function filemanager_replacements($id,$prefix='', &$content = null)
public function filemanager_replacements($id, $prefix = '', &$content = null)
{
$info = array();
$file = Vfs::lstat($id,true);
$file = Vfs::lstat($id, true);
$file['mtime'] = Api\DateTime::to($file['mtime']);
$file['ctime'] = Api\DateTime::to($file['ctime']);
$file['name'] = Vfs::basename($id);
$file['dir'] = ($dir = Vfs::dirname($id)) ? Vfs::decodePath($dir) : '';
$dirlist = explode('/',$file['dir']);
$dirlist = explode('/', $file['dir']);
$file['folder'] = array_pop($dirlist);
$file['folder_file'] = $file['folder'] . '/'.$file['name'];
$file['folder_file'] = $file['folder'] . '/' . $file['name'];
$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['mime'] = Vfs::mime_content_type($id);
$file['gid'] *= -1; // our widgets use negative gid's
if (($props = Vfs::propfind($id)))
if(($props = Vfs::propfind($id)))
{
foreach($props as $prop)
{
$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);
}
// Custom fields
if($content && strpos($content, '#') !== 0)
{
{
// Expand link-to custom fields
$this->cf_link_to_expand($file, $content, $info);
$this->cf_link_to_expand($file, $content, $info);
foreach(Api\Storage\Customfields::get('filemanager') as $name => $field)
{
// Set any missing custom fields, or the marker will stay
if(!$file['#'.$name])
if(!$file['#' . $name])
{
$file['#'.$name] = '';
$file['#' . $name] = '';
continue;
}
// Format date cfs per user Api\Preferences
if($field['type'] == 'date' || $field['type'] == 'date-time')
{
$this->date_fields[] = '#'.$name;
$file['#'.$name] = Api\DateTime::to($file['#'.$name], $field['type'] == 'date' ? true : '');
$this->date_fields[] = '#' . $name;
$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)
{
// 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?
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
$resolved = Vfs::resolve_url_symlinks(implode('/',array_slice(explode('/',$file['dir']),0,4)));
list($app, $app_id) = explode('/', substr($resolved, strpos($resolved, 'apps/')+5));
$resolved = Vfs::resolve_url_symlinks(implode('/', array_slice(explode('/', $file['dir']), 0, 4)));
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
$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)
@ -170,7 +171,7 @@ class filemanager_merge extends Api\Storage\Merge
$app_merge = null;
try
{
$classname = $app .'_merge';
$classname = $app . '_merge';
if(class_exists($classname))
{
$app_merge = new $classname();
@ -180,9 +181,10 @@ class filemanager_merge extends Api\Storage\Merge
}
}
}
// Silently discard & continue
catch(Exception $e) {
unset($e); // not used
// Silently discard & continue
catch (Exception $e)
{
unset($e); // not used
}
}
}
@ -211,7 +213,7 @@ class filemanager_merge extends Api\Storage\Merge
foreach($file as $key => &$value)
{
if(!$value) $value = '';
$info['$$'.($prefix ? $prefix.'/':'').$key.'$$'] = $value;
$info['$$' . ($prefix ? $prefix . '/' : '') . $key . '$$'] = $value;
}
if($app_placeholders)
{
@ -239,14 +241,18 @@ class filemanager_merge extends Api\Storage\Merge
{
return $session;
}
else if (($session = \EGroupware\Api\Cache::getSession(Api\Sharing::class, "$app::$id")) &&
substr($session['share_path'], -strlen($path)) === $path)
else
{
return $session;
if(($session = \EGroupware\Api\Cache::getSession(Api\Sharing::class, "$app::$id")) &&
substr($session['share_path'], -strlen($path)) === $path)
{
return $session;
}
}
// Need to create the share here.
// 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();
$extra = array();
@ -254,72 +260,59 @@ 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');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
$content['extra_template'] = 'filemanager.replacements';
}
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(
'name' => 'name',
'path' => 'Absolute path',
'rel_path' => 'Path relative to current directory',
'folder' => 'Containing folder',
'name' => 'name',
'path' => 'Absolute path',
'rel_path' => 'Path relative to current directory',
'folder' => 'Containing folder',
'folder_file' => 'Containing folder and file name',
'url' => 'url',
'webdav_url' => 'External path using webdav',
'link' => 'Clickable link to file',
'comment' => 'comment',
'mtime' => 'modified',
'ctime' => 'created',
'mime' => 'Type',
'hsize' => 'Size',
'size' => 'Size (in bytes)',
'url' => 'url',
'webdav_url' => 'External path using webdav',
'link' => 'Clickable link to file',
'comment' => 'comment',
'mtime' => 'modified',
'ctime' => 'created',
'mime' => 'Type',
'hsize' => 'Size',
'size' => 'Size (in bytes)',
);
$group = 'placeholders';
foreach($fields as $name => $label)
{
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
$marker = $this->prefix($prefix, $name, '{');
if(!array_filter($placeholders, function ($a) use ($marker)
{
return array_key_exists($marker, $a);
}))
{
$placeholders[$group][] = [
'value' => $marker,
'label' => $label
];
}
}
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";
}
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";
}
echo "</table>\n";
echo $GLOBALS['egw']->framework->footer();
return $placeholders;
}
}

View File

@ -684,7 +684,7 @@ class filemanager_ui
*/
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!');
}

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);
if($definition['owner'] && $definition['owner'] == $GLOBALS['egw_info']['user']['account_id'] || $GLOBALS['egw_info']['user']['apps']['admin']) {
// 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 {
unset($keys[$index]);
}

View File

@ -279,7 +279,7 @@ class importexport_export_csv implements importexport_iface_export_record
}
// Fall through for other settings
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']['@']);
}

View File

@ -183,28 +183,37 @@ class importexport_wizard_basic_export_csv
$content['step'] = 'wizard_step40';
// 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'];
} elseif (!$content['delimiter']) {
}
elseif(!$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';
}
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'];
}
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'];
}
$sel_options['begin_with_fieldnames'] = array(
0 => lang('No'),
1 => lang('Field names'),
'label' => lang('Field labels')
0 => lang('No'),
1 => lang('Field names'),
'label' => lang('Field labels')
);
$sel_options['charset'] = Api\Translation::get_installed_charsets()+
$sel_options['charset'] = Api\Translation::get_installed_charsets() +
array(
'user' => lang('User preference'),
);
@ -273,12 +282,19 @@ class importexport_wizard_basic_export_csv
unset ($preserv['button']);
$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
if(!array_key_exists('filter', $content) || !is_array($content['filter']))
{
$content['filter'] = [];
}
foreach($content['set_filter']['fields'] as $field => $settings)
{
$content['set_filter'][$field] = $content['filter'][$field];
if(array_key_exists($field, $content['filter']))
{
$content['set_filter'][$field] = $content['filter'][$field];
}
}
if(!$content['set_filter']['fields'])

View File

@ -228,7 +228,7 @@ class infolog_bo
{
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();
}
@ -262,17 +262,17 @@ class infolog_bo
$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']);
}
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']);
}
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']);
}
@ -286,7 +286,7 @@ class infolog_bo
}
$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
foreach($this->enums['type'] as $key => $val)
@ -629,12 +629,14 @@ class infolog_bo
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
{
return False;
$false = False;
return $false;
}
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
* 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'];
$from = $values['info_from'];
@ -1104,7 +1110,7 @@ class infolog_bo
) || (
is_array($values['info_contact']) && $values['info_contact']['id'] == 'none' &&
array_key_exists('search', $values['info_contact'])
))
))
{
if(is_array($values['info_contact']))
{
@ -1113,7 +1119,7 @@ class infolog_bo
$id = (int)$values['info_contact']['id'];
$from = $values['info_contact']['search'];
}
else if ($values['info_contact'])
else if($values['info_contact'])
{
list($app, $id) = explode(':', $values['info_contact'], 2);
}

View File

@ -1,6 +1,6 @@
<?php
/**
* eGroupWare - Infolog - importexport
* EGroupware - InfoLog - importexport
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package infolog
@ -8,13 +8,12 @@
* @link http://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray
* @version $Id$
*/
/**
* 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
{
@ -53,7 +52,7 @@ class infolog_egw_record implements importexport_iface_egw_record
* @param string $_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
if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{
$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('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',
);
$merge = new infolog_merge();
$settings += $merge->merge_preferences();
}
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
foreach($this->bo->customfields as $name => $field)
{
if(!$array['#'.$name])
if (empty($array['#'.$name]))
{
$array['#'.$name] = '';
}
@ -183,9 +183,9 @@ class infolog_merge extends Api\Storage\Merge
$info += $this->get_all_links('infolog', $id, $prefix, $content);
// 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
$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;
}
/**
* 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 = '')
{
$placeholders = parent::get_placeholder_list($prefix);
$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'),
'project' => lang('Project name')) + $tracking->field2label + array('info_sum_timesheets' => lang('Used time'));
Api\Translation::add_app('projectmanager');
$group = 'placeholders';
$group = 'infolog';
foreach($fields as $name => $label)
{
if(in_array($name, array('custom')))
@ -310,15 +232,27 @@ class infolog_merge extends Api\Storage\Merge
}
}
// 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 = $contact_merge->get_placeholder_list('info_contact');
$this->add_linked_placeholders($placeholders, lang($tracking->field2label['info_from']), $contact);
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// Add contact placeholders
$contact_merge = new Api\Contacts\Merge();
$contact = $contact_merge->get_placeholder_list($this->prefix($prefix, 'info_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;
}
}

View File

@ -144,7 +144,7 @@ class infolog_tracking extends Api\Storage\Tracking
*/
function get_subject($data, $old, $deleted = null, $receiver = null)
{
if ($data['prefix'])
if (!empty($data['prefix']))
{
$prefix = $data['prefix']; // async notification
}
@ -172,7 +172,7 @@ class infolog_tracking extends Api\Storage\Tracking
*/
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')
{
@ -345,16 +345,16 @@ class infolog_tracking extends Api\Storage\Tracking
return '';
}
// 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~'];
// 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'];
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_id'] = $info_id;
/* $info_link_id is never defined
if ($info_link_id && strpos($info_link_id,':') !== false) // updating info_link_id if necessary
{
list($app,$id) = explode(':',$info_link_id);
@ -1903,7 +1903,7 @@ class infolog_ui
// we need eg. the new modification date, for further updates
$content = array_merge($content,$to_write);
}
}
}*/
// Need to purge description history after encryption?
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)
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'])
{
@ -2172,7 +2172,7 @@ class infolog_ui
$readonlys['action'] = true;
}
// 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']];
$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
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']))
{
@ -2252,7 +2252,7 @@ class infolog_ui
$tracking = new infolog_tracking($this);
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
$history_stati['Mo'] = $tracking->field2label['info_datemodified'];
@ -2276,20 +2276,20 @@ class infolog_ui
'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');
}
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');
}
elseif ($GLOBALS['egw_info']['server']['disable_pgp_encryption'])
elseif (!empty($GLOBALS['egw_info']['server']['disable_pgp_encryption']))
{
$readonlys['encrypt'] = true;
}
$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'));
//error_log(substr($content['info_des'],1793,10));
//$content['info_des'] = substr($content['info_des'],0,1793);

View File

@ -60,6 +60,7 @@ function ajax_exception_handler($e)
$response = Json\Response::get();
$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
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']);
if ($acc_smime['acc_smime_password'])
if ($acc_smime && !empty($acc_smime['acc_smime_password']))
{
$actions = array_merge($actions, array(
'smime_sign' => array (
@ -271,9 +271,9 @@ class mail_compose
}
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
if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
if (empty($GLOBALS['egw_info']['user']['apps']['filemanager']))
{
unset($actions['save2vfs']);
unset($actions['selectFromVFSForCompose']);
@ -1242,16 +1242,16 @@ class mail_compose
// address stuff like from, to, cc, replyto
$destinationRows = 0;
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
unset($content[strtolower($destination)]);
foreach((array)$addr_content as $key => $value) {
if ($value=="NIL@NIL") continue;
if ($destination=='replyto' && str_replace('"','',$value) ==
foreach($addr_content as $value) {
if ($value === "NIL@NIL") continue;
if ($destination === 'replyto' && str_replace('"','',$value) ===
str_replace('"','',$identities[$this->mail_bo->getDefaultIdentity()]))
{
// preserve/restore the value to content.
@ -1261,7 +1261,7 @@ class mail_compose
//error_log(__METHOD__.__LINE__.array2string(array('key'=>$key,'value'=>$value)));
$value = str_replace("\"\"",'"', htmlspecialchars_decode($value, ENT_COMPAT));
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 = Mail::htmlentities($address, $this->displayCharset);
$content[strtolower($destination)][]=$address;
@ -1289,7 +1289,7 @@ class mail_compose
$content['mail_'.($content['mimeType'] == 'html'?'html':'plain').'text'] =$content['body'];
$content['showtempname']=0;
//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;
$content['no_griddata'] = empty($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 no filemanager -> no vfsFileSelector
if (!$GLOBALS['egw_info']['user']['apps']['filemanager'])
if (empty($GLOBALS['egw_info']['user']['apps']['filemanager']))
{
$content['vfsNotAvailable'] = "mail_DisplayNone";
}
// 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";
}
// 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";
}
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";
}
@ -1324,12 +1324,12 @@ class mail_compose
$sel_options['mimeType'] = self::$mimeTypes;
$sel_options['priority'] = self::$priorities;
$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
$etpl = new Etemplate('mail.compose');
$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]"
$_htmlConfig = Mail::$htmLawed_config;
@ -1340,7 +1340,7 @@ class mail_compose
Mail::$htmLawed_config = $_htmlConfig;
}
if (isset($content['composeID'])&&!empty($content['composeID']))
if (!empty($content['composeID']))
{
$composeCache = $content;
unset($composeCache['body']);
@ -1348,21 +1348,21 @@ class mail_compose
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);
}
if (!isset($_content['serverID'])||empty($_content['serverID']))
if (empty($_content['serverID']))
{
$content['serverID'] = $this->mail_bo->profileID;
}
$preserv['serverID'] = $content['serverID'];
$preserv['lastDrafted'] = $content['lastDrafted'];
$preserv['processedmail_id'] = $content['processedmail_id'];
$preserv['references'] = $content['references'];
$preserv['in-reply-to'] = $content['in-reply-to'];
$preserv['lastDrafted'] = $content['lastDrafted'] ?? null;
$preserv['processedmail_id'] = $content['processedmail_id'] ?? null;
$preserv['references'] = $content['references'] ?? null;
$preserv['in-reply-to'] = $content['in-reply-to'] ?? null;
// 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
//$preserv['thread-topic'] = $content['thread-topic'];
$preserv['thread-index'] = $content['thread-index'];
$preserv['list-id'] = $content['list-id'];
$preserv['mode'] = $content['mode'];
$preserv['thread-index'] = $content['thread-index'] ?? null;
$preserv['list-id'] = $content['list-id'] ?? null;
$preserv['mode'] = $content['mode'] ?? null;
// convert it back to checkbox expectations
if($content['mimeType'] == 'html') {
$content['mimeType']=1;
@ -1391,11 +1391,11 @@ class mail_compose
// Resolve distribution list before send content to client
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
if($content['attachments'] && is_array($content['attachments']))
if(!empty($content['attachments']))
{
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']) ?
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));
$etpl->exec('mail.mail_compose.compose',$content,$sel_options,array(),$preserv,2);
}
@ -2022,7 +2022,7 @@ class mail_compose
'size' => $_size,
'folder' => $_folder,
'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'])) {
$_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']);
}
@ -2522,6 +2522,8 @@ class mail_compose
if ($_formData['attachments'] && $_formData['filemode'] != Vfs\Sharing::ATTACH && !$_autosaving)
{
$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',
array_unique(array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc'])),
$_formData['expiration'], $_formData['password']);
@ -2530,7 +2532,7 @@ class mail_compose
{
case 'html':
$body = $_formData['body'];
if ($attachment_links)
if (!empty($attachment_links))
{
if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false)
{
@ -2567,7 +2569,7 @@ class mail_compose
default:
$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'];
if(!empty($signature)) {
@ -2653,7 +2655,7 @@ class mail_compose
}
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'];
// beware: do not delete the original mail as found in processedmail_id
$pMuid='';
if ($content['processedmail_id'])
if (!empty($content['processedmail_id']))
{
$pMhA = mail_ui::splitRowID($content['processedmail_id']);
$pMuid = $pMhA['msgUID'];
@ -3021,7 +3023,7 @@ class mail_compose
// create the messages and store inline images
$inline_images = $this->createMessage($mail, $_formData, $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->getMessageBody() ."</pre><hr><br>";
#exit;
@ -3317,14 +3319,14 @@ class mail_compose
if (isset($lastDrafted['uid']) && !empty($lastDrafted['uid'])) $lastDrafted['uid']=trim($lastDrafted['uid']);
// manually drafted, do not delete
// 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));
}
if ($lastDrafted && is_array($lastDrafted) && $mail_bo->isDraftFolder($lastDrafted['folder']))
{
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']))
{
//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['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' )
{
@ -3407,7 +3409,7 @@ class mail_compose
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')
{
$app_name = substr($app_key,3);

View File

@ -252,6 +252,10 @@ class mail_sieve
break;
case 'reject':
$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;
}
@ -302,10 +306,15 @@ class mail_sieve
break;
case 'reject':
$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_address_text']);
unset($newRule['action_reject_text']);
unset($newRule['action_flags_list']);
$newRule['flg'] = 0 ;
if( $newRule['continue'] ) { $newRule['flg'] += 1; }
@ -550,7 +559,7 @@ class mail_sieve
}
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'));
}
@ -949,11 +958,11 @@ class mail_sieve
break;
case 'enable':
$msg = lang('rule with priority ') . $checked . lang(' enabled!');
$this->rules[$checked][status] = 'ENABLED';
$this->rules[$checked]['status'] = 'ENABLED';
break;
case 'disable':
$msg = lang('rule with priority ') . $checked . lang(' disabled!');
$this->rules[$checked][status] = 'DISABLED';
$this->rules[$checked]['status'] = 'DISABLED';
break;
case 'move':
break;

View File

@ -425,7 +425,7 @@ class mail_ui
protected static function image_proxy()
{
$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)))
{
$image_proxy = self::EGROUPWARE_IMAGE_PROXY;
@ -565,7 +565,7 @@ class mail_ui
$etpl->setElementAttribute(self::$nm_index.'[foldertree]','actions', $this->get_tree_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
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
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"/>
<description/>
</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>
<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>

View File

@ -160,7 +160,7 @@ class notifications_popup implements notifications_iface {
foreach ($rs as $notification) {
$actions = null;
$data = json_decode($notification['notify_data'], true);
if ($data['appname'] && $data['data'])
if (!empty($data['appname']) && !empty($data['data']))
{
$_actions = Api\Hooks::process (array(
'location' => 'notifications_actions',
@ -175,7 +175,7 @@ class notifications_popup implements notifications_iface {
'created' => Api\DateTime::server2user($notification['notify_created']),
'current' => new Api\DateTime('now'),
'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();
// color to use
$color = str_replace('custom',$GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'],
$GLOBALS['egw_info']['user']['preferences']['common']['template_color']);
$color = str_replace('custom', $GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'] ?? null,
$GLOBALS['egw_info']['user']['preferences']['common']['template_color'] ?? null);
// Create a dark variant of the color
$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 = $this->_color_shader($sidebox_color_hover, -30);
@ -101,7 +101,7 @@ class pixelegg_framework extends Api\Framework\Ajax
$sidebox_color_hover = $color;
$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'];
}
@ -109,8 +109,8 @@ class pixelegg_framework extends Api\Framework\Ajax
{
$loginbox_color = $color_darker;
}
//alway 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')
//always set header logo used in sharing regardless of custom color being set
$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');
$ret['app_css'] .= "
/*

View File

@ -451,7 +451,7 @@ class timesheet_bo extends Api\Storage
{
$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);
}

View File

@ -178,41 +178,8 @@ class timesheet_hooks
// Merge print
if ($GLOBALS['egw_info']['user']['apps']['filemanager'])
{
$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('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',
);
$merge = new timesheet_merge();
$settings += $merge->merge_preferences();
}
return $settings;

View File

@ -156,68 +156,6 @@ class timesheet_merge extends Api\Storage\Merge
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.
*
@ -256,10 +194,14 @@ class timesheet_merge extends Api\Storage\Merge
}
}
// Add project placeholders
$pm_merge = new projectmanager_merge();
$this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project'));
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// Add project placeholders
$pm_merge = new projectmanager_merge();
$this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project'));
}
return $placeholders;
}
}

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