diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 56146fc509..95a7d5ad86 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -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; } } } diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php index 9a514656a6..9a59821007 100644 --- a/addressbook/inc/class.addressbook_hooks.inc.php +++ b/addressbook/inc/class.addressbook_hooks.inc.php @@ -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']) diff --git a/admin/inc/class.admin_acl.inc.php b/admin/inc/class.admin_acl.inc.php index ba1652f06f..b66175563b 100644 --- a/admin/inc/class.admin_acl.inc.php +++ b/admin/inc/class.admin_acl.inc.php @@ -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) { diff --git a/admin/inc/class.admin_cmd_category.inc.php b/admin/inc/class.admin_cmd_category.inc.php index 7059cde539..a5b88fb0e8 100644 --- a/admin/inc/class.admin_cmd_category.inc.php +++ b/admin/inc/class.admin_cmd_category.inc.php @@ -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]); diff --git a/admin/inc/class.admin_customfields.inc.php b/admin/inc/class.admin_customfields.inc.php index c285bbf1d5..dc86bd89de 100644 --- a/admin/inc/class.admin_customfields.inc.php +++ b/admin/inc/class.admin_customfields.inc.php @@ -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(); } diff --git a/admin/inc/class.admin_mail.inc.php b/admin/inc/class.admin_mail.inc.php index f8539e40f5..db74bc11db 100644 --- a/admin/inc/class.admin_mail.inc.php +++ b/admin/inc/class.admin_mail.inc.php @@ -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']])) { diff --git a/admin/templates/default/categories.edit.xet b/admin/templates/default/categories.edit.xet index a1ed1c4538..96120b6b2d 100644 --- a/admin/templates/default/categories.edit.xet +++ b/admin/templates/default/categories.edit.xet @@ -44,7 +44,7 @@ - + diff --git a/api/js/etemplate/et2_widget_placeholder.ts b/api/js/etemplate/et2_widget_placeholder.ts index bdbef7b34a..e82b54b1c1 100644 --- a/api/js/etemplate/et2_widget_placeholder.ts +++ b/api/js/etemplate/et2_widget_placeholder.ts @@ -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) }); diff --git a/api/js/etemplate/et2_widget_toolbar.ts b/api/js/etemplate/et2_widget_toolbar.ts index e0cedc3031..dd1dec1dc5 100644 --- a/api/js/etemplate/et2_widget_toolbar.ts +++ b/api/js/etemplate/et2_widget_toolbar.ts @@ -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, diff --git a/api/js/jsapi/egw_app.ts b/api/js/jsapi/egw_app.ts index a4e5a459b7..eadedb8631 100644 --- a/api/js/jsapi/egw_app.ts +++ b/api/js/jsapi/egw_app.ts @@ -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 = []; diff --git a/api/lang/egw_de.lang b/api/lang/egw_de.lang index ad9510f170..ae3ec4d874 100644 --- a/api/lang/egw_de.lang +++ b/api/lang/egw_de.lang @@ -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 diff --git a/api/lang/egw_en.lang b/api/lang/egw_en.lang index 09abb18ec0..722e90c99b 100644 --- a/api/lang/egw_en.lang +++ b/api/lang/egw_en.lang @@ -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 diff --git a/api/src/Accounts.php b/api/src/Accounts.php index 889f6c1907..69c9def084 100644 --- a/api/src/Accounts.php +++ b/api/src/Accounts.php @@ -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 ?? []; } /** diff --git a/api/src/Auth.php b/api/src/Auth.php index 5fdfa1c5a6..74a086de23 100644 --- a/api/src/Auth.php +++ b/api/src/Auth.php @@ -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; diff --git a/api/src/Categories.php b/api/src/Categories.php index 9cbbb46e24..c0d4ea494d 100644 --- a/api/src/Categories.php +++ b/api/src/Categories.php @@ -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]) { diff --git a/api/src/Config.php b/api/src/Config.php index bc90e47eb6..cfe545e13c 100755 --- a/api/src/Config.php +++ b/api/src/Config.php @@ -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'] : []; } /** diff --git a/api/src/Contacts.php b/api/src/Contacts.php index 2409357a24..d3a5a1b2b1 100755 --- a/api/src/Contacts.php +++ b/api/src/Contacts.php @@ -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; } /** diff --git a/api/src/Contacts/Merge.php b/api/src/Contacts/Merge.php index 4dc6d5a47c..169e233abc 100644 --- a/api/src/Contacts/Merge.php +++ b/api/src/Contacts/Merge.php @@ -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 "\n"; - echo '"; - - $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 "\n"; - $n++; - } - if (!($n&1)) echo ''; - echo ''; - if($name == 'cat_id') - { - if ($n&1) echo "\n"; - echo ''; - $n++; - } - if ($n&1) echo "\n"; - $n++; - } - - echo '"; - foreach($this->contacts->customfields as $name => $field) - { - echo '\n"; - } - - echo '"; - 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 '\n"; - } - - echo '"; - echo '\n"; - - Api\Translation::add_app('calendar'); - echo '"; - 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 "\n"; - $n++; - } - if(!($n & 1)) - { - echo ''; - } - echo ''; - if($n & 1) - { - echo "\n"; - } - $n++; - } - echo "

'.lang('Contact fields:')."

{{'.$name.'}}'.$label.'
{{categories}}'.lang('Category path').'

'.lang('Custom fields').":

{{#'.$name.'}}'.$field['label']."

'.lang('General fields:')."

{{'.$name.'}}'.$label."

'.lang('EPL Only').":

{{share}}'.lang('Public sharing URL')."

'.lang('Calendar fields:')." # = 1, 2, ..., 20, -1

{{calendar/#/' . $name . '}}' . $label . '
\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 * diff --git a/api/src/Contacts/Sql.php b/api/src/Contacts/Sql.php index 481d2b9fb0..a6748459fc 100644 --- a/api/src/Contacts/Sql.php +++ b/api/src/Contacts/Sql.php @@ -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; } diff --git a/api/src/Contacts/Storage.php b/api/src/Contacts/Storage.php index 0cb66f4b6d..fe2b3b4531 100755 --- a/api/src/Contacts/Storage.php +++ b/api/src/Contacts/Storage.php @@ -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)); } /** diff --git a/api/src/Db.php b/api/src/Db.php index cb42718df4..c2160cc0a8 100644 --- a/api/src/Db.php +++ b/api/src/Db.php @@ -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; } diff --git a/api/src/Db/Pdo.php b/api/src/Db/Pdo.php index cd038c6f15..353409d523 100644 --- a/api/src/Db/Pdo.php +++ b/api/src/Db/Pdo.php @@ -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); } diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php index 0631d9e3b7..45e88ae271 100644 --- a/api/src/Etemplate.php +++ b/api/src/Etemplate.php @@ -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( diff --git a/api/src/Etemplate/Request.php b/api/src/Etemplate/Request.php index f4823645b9..4843c9503d 100644 --- a/api/src/Etemplate/Request.php +++ b/api/src/Etemplate/Request.php @@ -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 ..."); diff --git a/api/src/Etemplate/Request/Files.php b/api/src/Etemplate/Request/Files.php index b15054d320..613bd5be30 100644 --- a/api/src/Etemplate/Request/Files.php +++ b/api/src/Etemplate/Request/Files.php @@ -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); diff --git a/api/src/Etemplate/Request/Session.php b/api/src/Etemplate/Request/Session.php index 7bad1420fe..339c09307d 100644 --- a/api/src/Etemplate/Request/Session.php +++ b/api/src/Etemplate/Request/Session.php @@ -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); diff --git a/api/src/Etemplate/Widget.php b/api/src/Etemplate/Widget.php index 00a591f000..a3f41e0f23 100644 --- a/api/src/Etemplate/Widget.php +++ b/api/src/Etemplate/Widget.php @@ -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) diff --git a/api/src/Etemplate/Widget/Box.php b/api/src/Etemplate/Widget/Box.php index de3a379091..74a198d239 100644 --- a/api/src/Etemplate/Widget/Box.php +++ b/api/src/Etemplate/Widget/Box.php @@ -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; diff --git a/api/src/Etemplate/Widget/Customfields.php b/api/src/Etemplate/Widget/Customfields.php index 0964a7fd9b..466ff5f5bc 100644 --- a/api/src/Etemplate/Widget/Customfields.php +++ b/api/src/Etemplate/Widget/Customfields.php @@ -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']; } diff --git a/api/src/Etemplate/Widget/Description.php b/api/src/Etemplate/Widget/Description.php index 338e80f15d..e69bcdfd19 100644 --- a/api/src/Etemplate/Widget/Description.php +++ b/api/src/Etemplate/Widget/Description.php @@ -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); diff --git a/api/src/Etemplate/Widget/Grid.php b/api/src/Etemplate/Widget/Grid.php index 17433ba3e1..a26d76a73b 100644 --- a/api/src/Etemplate/Widget/Grid.php +++ b/api/src/Etemplate/Widget/Grid.php @@ -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; diff --git a/api/src/Etemplate/Widget/Image.php b/api/src/Etemplate/Widget/Image.php index 7969ab83dc..f77067f082 100644 --- a/api/src/Etemplate/Widget/Image.php +++ b/api/src/Etemplate/Widget/Image.php @@ -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); } } diff --git a/api/src/Etemplate/Widget/Nextmatch.php b/api/src/Etemplate/Widget/Nextmatch.php index d6ccd128c7..10004d276e 100644 --- a/api/src/Etemplate/Widget/Nextmatch.php +++ b/api/src/Etemplate/Widget/Nextmatch.php @@ -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( diff --git a/api/src/Etemplate/Widget/Password.php b/api/src/Etemplate/Widget/Password.php index ae4d5ef7f3..3e7cbe9997 100644 --- a/api/src/Etemplate/Widget/Password.php +++ b/api/src/Etemplate/Widget/Password.php @@ -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)) diff --git a/api/src/Etemplate/Widget/Placeholder.php b/api/src/Etemplate/Widget/Placeholder.php index a31d88c49d..c2f933c6c8 100644 --- a/api/src/Etemplate/Widget/Placeholder.php +++ b/api/src/Etemplate/Widget/Placeholder.php @@ -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; } diff --git a/api/src/Etemplate/Widget/Select.php b/api/src/Etemplate/Widget/Select.php index 84817c9aa5..e8c8844cff 100644 --- a/api/src/Etemplate/Widget/Select.php +++ b/api/src/Etemplate/Widget/Select.php @@ -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; } diff --git a/api/src/Etemplate/Widget/Template.php b/api/src/Etemplate/Widget/Template.php index b83255e7e9..f256e78272 100644 --- a/api/src/Etemplate/Widget/Template.php +++ b/api/src/Etemplate/Widget/Template.php @@ -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); } diff --git a/api/src/Etemplate/Widget/Textbox.php b/api/src/Etemplate/Widget/Textbox.php index 74717982ac..26a32a3897 100644 --- a/api/src/Etemplate/Widget/Textbox.php +++ b/api/src/Etemplate/Widget/Textbox.php @@ -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; } diff --git a/api/src/Etemplate/Widget/Tree.php b/api/src/Etemplate/Widget/Tree.php index 1fa12c56d0..b05deaac1c 100644 --- a/api/src/Etemplate/Widget/Tree.php +++ b/api/src/Etemplate/Widget/Tree.php @@ -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(); diff --git a/api/src/Etemplate/Widget/Vfs.php b/api/src/Etemplate/Widget/Vfs.php index 9781da9ad7..d04e8b96cc 100644 --- a/api/src/Etemplate/Widget/Vfs.php +++ b/api/src/Etemplate/Widget/Vfs.php @@ -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); diff --git a/api/src/Framework/Ajax.php b/api/src/Framework/Ajax.php index 77f0e67530..e10618fa2c 100755 --- a/api/src/Framework/Ajax.php +++ b/api/src/Framework/Ajax.php @@ -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 - * @author Ralf Becker - * @author Nathan Gray + * @author Andreas Stöckel + * @author Ralf Becker + * @author Nathan Gray * @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 "\n\n"; } diff --git a/api/src/Framework/Bundle.php b/api/src/Framework/Bundle.php index 09d5cfe23e..28d9bc421d 100644 --- a/api/src/Framework/Bundle.php +++ b/api/src/Framework/Bundle.php @@ -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; diff --git a/api/src/Framework/CssIncludes.php b/api/src/Framework/CssIncludes.php index 539af4d32d..bdc4c30d80 100644 --- a/api/src/Framework/CssIncludes.php +++ b/api/src/Framework/CssIncludes.php @@ -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 diff --git a/api/src/Html.php b/api/src/Html.php index 4acfa409c6..3d0dc5baf0 100644 --- a/api/src/Html.php +++ b/api/src/Html.php @@ -100,6 +100,7 @@ class Html // use preg_replace_callback as we experienced problems with links such as $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],'>',strlen($match[3])-4)!==false) { $match[3] = substr($match[3],0,strpos($match[3],'>',strlen($match[3])-4)); @@ -111,7 +112,7 @@ class Html $match[4] = ">"; } //error_log(__METHOD__.__LINE__.array2string($match)); - return $match[1].""."www".$match[2].$match[3]."".$match[4]; + return $match[1].""."www".$match[2].$match[3]."".$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); diff --git a/api/src/Html/HtmLawed.php b/api/src/Html/HtmLawed.php index 770c96dc84..31ec440987 100644 --- a/api/src/Html/HtmLawed.php +++ b/api/src/Html/HtmLawed.php @@ -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']; diff --git a/api/src/Link.php b/api/src/Link.php index 68298cfb91..fe65f6e722 100644 --- a/api/src/Link.php +++ b/api/src/Link.php @@ -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 "

Link::unlink('$link_id','$app',".array2string($id).",'$owner','$app2','$id2', $hold_for_purge)

\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); } diff --git a/api/src/Link/Storage.php b/api/src/Link/Storage.php index d55a8f323b..bdac0f380e 100644 --- a/api/src/Link/Storage.php +++ b/api/src/Link/Storage.php @@ -127,7 +127,7 @@ class Storage { echo "

solink.get_links($app,".print_r($id,true).",$only_app,$order,$deleted)

\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) diff --git a/api/src/Mail.php b/api/src/Mail.php index 1731ecd4a9..295b524efe 100644 --- a/api/src/Mail.php +++ b/api/src/Mail.php @@ -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; } diff --git a/api/src/Mail/Account.php b/api/src/Mail/Account.php index c535b39026..3fcc988a9a 100644 --- a/api/src/Mail/Account.php +++ b/api/src/Mail/Account.php @@ -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! diff --git a/api/src/Mail/Credentials.php b/api/src/Mail/Credentials.php index 091506be30..a5482d24b9 100644 --- a/api/src/Mail/Credentials.php +++ b/api/src/Mail/Credentials.php @@ -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, diff --git a/api/src/Mail/Html.php b/api/src/Mail/Html.php index 9f134cdd89..667b845e4f 100644 --- a/api/src/Mail/Html.php +++ b/api/src/Mail/Html.php @@ -169,7 +169,7 @@ class Html if ($addbracesforendtag === true ) { if (stripos($_body,'<'.$tag)!==false) $ct = preg_match_all('#<'.$tag.'(?:\s.*)?>(.+)#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]+;)/', '&', $html); $dom = new \DOMDocument('1.0','UTF-8'); - if(!$dom->loadHTML( + if (!@$dom->loadHTML( ''. Api\Translation::convert($html,preg_match('/]+content="[^>"]+charset=([^;"]+)/i', $html, $matches) ? $matches[1] : false, 'utf8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS )) diff --git a/api/src/Mail/Imap.php b/api/src/Mail/Imap.php index 8a9ddd8780..ecd2bcdcbc 100644 --- a/api/src/Mail/Imap.php +++ b/api/src/Mail/Imap.php @@ -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); diff --git a/api/src/Mail/Notifications.php b/api/src/Mail/Notifications.php index d1a1d02301..54b8343967 100644 --- a/api/src/Mail/Notifications.php +++ b/api/src/Mail/Notifications.php @@ -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; } diff --git a/api/src/Mail/Script.php b/api/src/Mail/Script.php index 28fdf9823c..a70d00e9d4 100644 --- a/api/src/Mail/Script.php +++ b/api/src/Mail/Script.php @@ -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']); diff --git a/api/src/Mail/Smtp/Sql.php b/api/src/Mail/Smtp/Sql.php index 83a443cf83..7e20377cf7 100644 --- a/api/src/Mail/Smtp/Sql.php +++ b/api/src/Mail/Smtp/Sql.php @@ -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) { diff --git a/api/src/Mailer.php b/api/src/Mailer.php index d002c5d1cd..75fe433c37 100644 --- a/api/src/Mailer.php +++ b/api/src/Mailer.php @@ -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); } diff --git a/api/src/Session.php b/api/src/Session.php index 29d6692975..f9f3a4bec1 100644 --- a/api/src/Session.php +++ b/api/src/Session.php @@ -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; diff --git a/api/src/Storage.php b/api/src/Storage.php index 6695b9b95b..42b31fb1a4 100644 --- a/api/src/Storage.php +++ b/api/src/Storage.php @@ -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, diff --git a/api/src/Storage/Base.php b/api/src/Storage/Base.php index 2bfb54d884..b75e0ff8f4 100644 --- a/api/src/Storage/Base.php +++ b/api/src/Storage/Base.php @@ -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 "

sql='{$this->db->Query_ID->sql}'

\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; } /** diff --git a/api/src/Storage/Customfields.php b/api/src/Storage/Customfields.php index 82d3b61d9f..fafa460512 100755 --- a/api/src/Storage/Customfields.php +++ b/api/src/Storage/Customfields.php @@ -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); } diff --git a/api/src/Storage/Merge.php b/api/src/Storage/Merge.php index ca01c49612..a83c1fc5e1 100644 --- a/api/src/Storage/Merge.php +++ b/api/src/Storage/Merge.php @@ -32,6 +32,16 @@ use ZipArchive; */ abstract class Merge { + /** + * Preference, path where we look for merge templates + */ + public const PREF_TEMPLATE_DIR = 'document_dir'; + + /** + * Preference, path to special documents that are listed first + */ + public const PREF_DEFAULT_TEMPLATE = 'default_document'; + /** * Preference, path where we will put the generated document */ @@ -104,21 +114,21 @@ abstract class Merge public $export_limit; public $public_functions = array( - "merge_entries" => true + "merge_entries" => true ); /** * Configuration for HTML Tidy to clean up any HTML content that is kept */ public static $tidy_config = array( - 'output-xml' => true, // Entity encoding - 'show-body-only' => true, - 'output-encoding' => 'utf-8', - 'input-encoding' => 'utf-8', - 'quote-ampersand' => false, // Prevent double encoding - 'quote-nbsp' => true, // XSLT can handle spaces easier - 'preserve-entities' => true, - 'wrap' => 0, // Wrapping can break output + 'output-xml' => true, // Entity encoding + 'show-body-only' => true, + 'output-encoding' => 'utf-8', + 'input-encoding' => 'utf-8', + 'quote-ampersand' => false, // Prevent double encoding + 'quote-nbsp' => true, // XSLT can handle spaces easier + 'preserve-entities' => true, + 'wrap' => 0, // Wrapping can break output ); /** @@ -156,8 +166,8 @@ abstract class Merge $this->contacts = new Api\Contacts(); - $this->datetime_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'].' '. - ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']==12 ? 'h:i a' : 'H:i'); + $this->datetime_format = $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'] . ' ' . + ($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == 12 ? 'h:i a' : 'H:i'); $this->export_limit = self::getExportLimit(); } @@ -171,8 +181,8 @@ abstract class Merge { $accountsel = new uiaccountsel(); - return ''. - $accountsel->selection('newsettings[export_limit_excepted]','export_limit_excepted',$config['export_limit_excepted'],'both',4); + return '' . + $accountsel->selection('newsettings[export_limit_excepted]', 'export_limit_excepted', $config['export_limit_excepted'], 'both', 4); } /** @@ -183,10 +193,10 @@ abstract class Merge * - format_datetime($time,$format=null) * * @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 array with replacements or false if entry not found */ - abstract protected function get_replacements($id,&$content=null); + abstract protected function get_replacements($id, &$content = null); /** * Return if merge-print is implemented for given mime-type (and/or extension) @@ -194,47 +204,56 @@ abstract class Merge * @param string $mimetype eg. text/plain * @param string $extension only checked for applications/msword and .rtf */ - static public function is_implemented($mimetype,$extension=null) + static public function is_implemented($mimetype, $extension = null) { - static $zip_available=null; - if (is_null($zip_available)) + static $zip_available = null; + if(is_null($zip_available)) { $zip_available = check_load_extension('zip') && - class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!) + class_exists('ZipArchive'); // some PHP has zip extension, but no ZipArchive (eg. RHEL5!) } - switch ($mimetype) + switch($mimetype) { case 'application/msword': - if (strtolower($extension) != '.rtf') break; + if(strtolower($extension) != '.rtf') + { + break; + } case 'application/rtf': case 'text/rtf': - return true; // rtf files - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + return true; // rtf files + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': - if (!$zip_available) break; - return true; // open office write xml files - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms word 2007 xml format - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars + if(!$zip_available) + { + break; + } + return true; // open office write xml files + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms word 2007 xml format + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.d': // mimetypes in vfs are limited to 64 chars case 'application/vnd.ms-word.document.macroenabled.12': - case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': // ms excel 2007 xml format + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': // ms excel 2007 xml format case 'application/vnd.openxmlformats-officedocument.spreadsheetml.shee': case 'application/vnd.ms-excel.sheet.macroenabled.12': - if (!$zip_available) break; - return true; // ms word xml format + if(!$zip_available) + { + break; + } + return true; // ms word xml format case 'application/xml': - return true; // alias for text/xml, eg. ms office 2003 word format + return true; // alias for text/xml, eg. ms office 2003 word format case 'message/rfc822': return true; // ToDo: check if you are theoretical able to send mail case 'application/x-yaml': - return true; // yaml file, plain text with marginal syntax support for multiline replacements + return true; // yaml file, plain text with marginal syntax support for multiline replacements default: - if (substr($mimetype,0,5) == 'text/') + if(substr($mimetype, 0, 5) == 'text/') { - return true; // text files + return true; // text files } break; } @@ -252,13 +271,16 @@ abstract class Merge * @param boolean $ignore_acl =false true: no acl check * @return array */ - public function contact_replacements($contact,$prefix='',$ignore_acl=false, &$content = '') + public function contact_replacements($contact, $prefix = '', $ignore_acl = false, &$content = '') { - if (!is_array($contact)) + if(!is_array($contact)) { $contact = $this->contacts->read($contact, $ignore_acl); } - if (!is_array($contact)) return array(); + if(!is_array($contact)) + { + return array(); + } $replacements = array(); foreach(array_keys($this->contacts->contact_fields) as $name) @@ -362,15 +384,15 @@ abstract class Merge } // Format date cfs per user Api\Preferences if($this->mimetype !== 'application/x-yaml' && $contact[$name] && - ($field['type'] == 'date' || $field['type'] == 'date-time')) + ($field['type'] == 'date' || $field['type'] == 'date-time')) { - $this->date_fields[] = '#'.$name; - $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = Api\DateTime::to($contact[$name], $field['type'] == 'date' ? true : ''); + $this->date_fields[] = '#' . $name; + $replacements['$$' . ($prefix ? $prefix . '/' : '') . $name . '$$'] = Api\DateTime::to($contact[$name], $field['type'] == 'date' ? true : ''); } - $replacements['$$'.($prefix ? $prefix.'/':'').$name.'$$'] = + $replacements['$$' . ($prefix ? $prefix . '/' : '') . $name . '$$'] = // use raw data for yaml, no user-preference specific formatting $this->mimetype == 'application/x-yaml' || $field['type'] == 'htmlarea' ? (string)$contact[$name] : - Customfields::format($field, (string)$contact[$name]); + Customfields::format($field, (string)$contact[$name]); } if($content && strpos($content, '$$#') !== FALSE) @@ -380,22 +402,27 @@ abstract class Merge // Add in extra cat field $cats = array(); - foreach(is_array($contact['cat_id']) ? $contact['cat_id'] : explode(',',$contact['cat_id']) as $cat_id) + foreach(is_array($contact['cat_id']) ? $contact['cat_id'] : explode(',', $contact['cat_id']) as $cat_id) { - if(!$cat_id) continue; - if($GLOBALS['egw']->categories->id2name($cat_id,'main') != $cat_id) + if(!$cat_id) + { + continue; + } + if($GLOBALS['egw']->categories->id2name($cat_id, 'main') != $cat_id) { $path = explode(' / ', $GLOBALS['egw']->categories->id2name($cat_id, 'path')); unset($path[0]); // Drop main - $cats[$GLOBALS['egw']->categories->id2name($cat_id,'main')][] = implode(' / ', $path); - } elseif($cat_id) { + $cats[$GLOBALS['egw']->categories->id2name($cat_id, 'main')][] = implode(' / ', $path); + } + elseif($cat_id) + { $cats[$cat_id] = array(); } } - $replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] = ''; + $replacements['$$' . ($prefix ? $prefix . '/' : '') . 'categories$$'] = ''; foreach($cats as $main => $cat) { - $replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] .= $GLOBALS['egw']->categories->id2name($main,'name') + $replacements['$$' . ($prefix ? $prefix . '/' : '') . 'categories$$'] .= $GLOBALS['egw']->categories->id2name($main, 'name') . (count($cat) > 0 ? ': ' : '') . implode(', ', $cats[$main]) . "\n"; } return $replacements; @@ -406,16 +433,16 @@ abstract class Merge * * Uses egw_link system to get link titles * - * @param app Name of current app - * @param id ID of current entry - * @param only_app Restrict links to only given application - * @param exclude Exclude links to these applications - * @param style String One of: - * 'title' - plain text, just the title of the link - * 'link' - URL to the entry - * 'href' - HREF tag wrapped around the title + * @param string app Name of current app + * @param string id ID of current entry + * @param string only_app Restrict links to only given application + * @param string[] exclude Exclude links to these applications + * @param string style One of: + * 'title' - plain text, just the title of the link + * 'link' - URL to the entry + * 'href' - HREF tag wrapped around the title */ - protected function get_links($app, $id, $only_app='', $exclude = array(), $style = 'title') + protected function get_links($app, $id, $only_app = '', $exclude = array(), $style = 'title') { $links = Api\Link::get_links($app, $id, $only_app); $link_titles = array(); @@ -425,34 +452,40 @@ abstract class Merge if(!is_array($link_info) && $only_app && $only_app[0] !== '!') { $link_info = array( - 'app' => $only_app, - 'id' => $link_info + 'app' => $only_app, + 'id' => $link_info ); } - if($exclude && in_array($link_info['id'], $exclude)) continue; + if($exclude && in_array($link_info['id'], $exclude)) + { + continue; + } $title = Api\Link::title($link_info['app'], $link_info['id']); - + if($style == 'href' || $style == 'link') { $link = Api\Link::view($link_info['app'], $link_info['id'], $link_info); if($link_info['app'] != Api\Link::VFS_APPNAME) { // Set app to false so we always get an external link - $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php',$link, false)); + $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php', $link, false)); } else { $link = Api\Framework::link($link, array()); } // Prepend site - if ($link[0] == '/') $link = Api\Framework::getUrl($link); + if($link[0] == '/') + { + $link = Api\Framework::getUrl($link); + } $title = $style == 'href' ? Api\Html::a_href(Api\Html::htmlspecialchars($title), $link) : $link; } $link_titles[] = $title; } - return implode("\n",$link_titles); + return implode("\n", $link_titles); } /** @@ -469,7 +502,7 @@ abstract class Merge { $array = array(); $pattern = '@\$\$(links_attachments|links|attachments|link)\/?(title|href|link)?\/?([a-z]*)\$\$@'; - static $link_cache=null; + static $link_cache = null; $matches = null; if(preg_match_all($pattern, $content, $matches)) { @@ -495,38 +528,44 @@ abstract class Merge if($app != Api\Link::VFS_APPNAME) { // Set app to false so we always get an external link - $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php',$link, false)); + $link = str_replace(',', '%2C', $GLOBALS['egw']->framework->link('/index.php', $link, false)); } else { $link = Api\Framework::link($link, array()); } // Prepend site - if ($link[0] == '/') $link = Api\Framework::getUrl($link); + if($link[0] == '/') + { + $link = Api\Framework::getUrl($link); + } // Formatting if($matches[2][$i] == 'title') { $link = $title; } - else if($matches[2][$i] == 'href') + else { - // Turn on HTML style parsing or the link will be escaped - $this->parse_html_styles = true; - $link = Api\Html::a_href(Api\Html::htmlspecialchars($title), $link); + if($matches[2][$i] == 'href') + { + // Turn on HTML style parsing or the link will be escaped + $this->parse_html_styles = true; + $link = Api\Html::a_href(Api\Html::htmlspecialchars($title), $link); + } } - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $link; + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $link; break; case 'links': - $link_app = $matches[3][$i] ? $matches[3][$i] : '!'.Api\Link::VFS_APPNAME; - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, $link_app, array(),$matches[2][$i]); + $link_app = $matches[3][$i] ? $matches[3][$i] : '!' . Api\Link::VFS_APPNAME; + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, $link_app, array(), $matches[2][$i]); break; case 'attachments': - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, Api\Link::VFS_APPNAME,array(),$matches[2][$i]); + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, Api\Link::VFS_APPNAME, array(), $matches[2][$i]); break; default: - $array['$$'.($prefix?$prefix.'/':'').$placeholder.'$$'] = $this->get_links($app, $id, $matches[3][$i], array(), $matches[2][$i]); + $array['$$' . ($prefix ? $prefix . '/' : '') . $placeholder . '$$'] = $this->get_links($app, $id, $matches[3][$i], array(), $matches[2][$i]); break; } $link_cache[$id][$placeholder] = $array[$placeholder]; @@ -553,7 +592,7 @@ abstract class Merge if(!$GLOBALS['egw_info']['user']['apps']['stylite']) { - $replacements['$$'.$prefix.'share$$'] = lang('EPL Only'); + $replacements['$$' . $prefix . 'share$$'] = lang('EPL Only'); return $replacements; } @@ -562,7 +601,7 @@ abstract class Merge if($share) { - $replacements['$$'.$prefix.'share$$'] = $link = Api\Sharing::share2link($share); + $replacements['$$' . $prefix . 'share$$'] = $link = Api\Sharing::share2link($share); } return $replacements; @@ -589,7 +628,8 @@ abstract class Merge // 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(); @@ -603,15 +643,18 @@ abstract class Merge * * @param int|string|DateTime $time unix timestamp or Y-m-d H:i:s string (in user time!) * @param string $format =null format string, default $this->datetime_format - * @deprecated use Api\DateTime::to($time='now',$format='') * @return string + * @deprecated use Api\DateTime::to($time='now',$format='') */ - protected function format_datetime($time,$format=null) + protected function format_datetime($time, $format = null) { trigger_error(__METHOD__ . ' is deprecated, use Api\DateTime::to($time, $format)', E_USER_DEPRECATED); - if (is_null($format)) $format = $this->datetime_format; + if(is_null($format)) + { + $format = $this->datetime_format; + } - return Api\DateTime::to($time,$format); + return Api\DateTime::to($time, $format); } /** @@ -623,19 +666,19 @@ abstract class Merge */ public static function is_export_limit_excepted() { - static $is_excepted=null; + static $is_excepted = null; - if (is_null($is_excepted)) + if(is_null($is_excepted)) { $is_excepted = isset($GLOBALS['egw_info']['user']['apps']['admin']); // check export-limit and fail if user tries to export more entries then allowed - if (!$is_excepted && (is_array($export_limit_excepted = $GLOBALS['egw_info']['server']['export_limit_excepted']) || - is_array($export_limit_excepted = unserialize($export_limit_excepted)))) + if(!$is_excepted && (is_array($export_limit_excepted = $GLOBALS['egw_info']['server']['export_limit_excepted']) || + is_array($export_limit_excepted = unserialize($export_limit_excepted)))) { - $id_and_memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true); + $id_and_memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true); $id_and_memberships[] = $GLOBALS['egw_info']['user']['account_id']; - $is_excepted = (bool) array_intersect($id_and_memberships, $export_limit_excepted); + $is_excepted = (bool)array_intersect($id_and_memberships, $export_limit_excepted); } } return $is_excepted; @@ -646,30 +689,36 @@ abstract class Merge * * @param string $app ='common' checks and validates app_limit, if not set returns the global limit * @return mixed - no if no export is allowed, false if there is no restriction and int as there is a valid restriction - * you may have to cast the returned value to int, if you want to use it as number + * you may have to cast the returned value to int, if you want to use it as number */ - public static function getExportLimit($app='common') + public static function getExportLimit($app = 'common') { - static $exportLimitStore=array(); - if (empty($app)) $app='common'; + static $exportLimitStore = array(); + if(empty($app)) + { + $app = 'common'; + } //error_log(__METHOD__.__LINE__.' called with app:'.$app); - if (!array_key_exists($app,$exportLimitStore)) + if(!array_key_exists($app, $exportLimitStore)) { //error_log(__METHOD__.__LINE__.' -> '.$app_limit.' '.function_backtrace()); $exportLimitStore[$app] = $GLOBALS['egw_info']['server']['export_limit']; - if ($app !='common') + if($app != 'common') { - $app_limit = Api\Hooks::single('export_limit',$app); - if ($app_limit) $exportLimitStore[$app] = $app_limit; + $app_limit = Api\Hooks::single('export_limit', $app); + if($app_limit) + { + $exportLimitStore[$app] = $app_limit; + } } //error_log(__METHOD__.__LINE__.' building cache for app:'.$app.' -> '.$exportLimitStore[$app]); - if (empty($exportLimitStore[$app])) + if(empty($exportLimitStore[$app])) { $exportLimitStore[$app] = false; return false; } - if (is_numeric($exportLimitStore[$app])) + if(is_numeric($exportLimitStore[$app])) { $exportLimitStore[$app] = (int)$exportLimitStore[$app]; } @@ -691,12 +740,24 @@ abstract class Merge * * @return bool - true if no export is allowed or a limit is set, false if there is no restriction */ - public static function hasExportLimit($app_limit,$checkas='AND') + public static function hasExportLimit($app_limit, $checkas = 'AND') { - if (strtoupper($checkas) == 'ISALLOWED') return (empty($app_limit) || ($app_limit !='no' && $app_limit > 0) ); - if (empty($app_limit)) return false; - if ($app_limit == 'no') return true; - if ($app_limit > 0) return true; + if(strtoupper($checkas) == 'ISALLOWED') + { + return (empty($app_limit) || ($app_limit != 'no' && $app_limit > 0)); + } + if(empty($app_limit)) + { + return false; + } + if($app_limit == 'no') + { + return true; + } + if($app_limit > 0) + { + return true; + } } /** @@ -709,31 +770,34 @@ abstract class Merge * @param array $fix =null regular expression => replacement pairs eg. to fix garbled placeholders * @return string|boolean merged document or false on error */ - public function &merge($document,$ids,&$err,$mimetype,array $fix=null) + public function &merge($document, $ids, &$err, $mimetype, array $fix = null) { - if (!($content = file_get_contents($document))) + if(!($content = file_get_contents($document))) { - $err = lang("Document '%1' does not exist or is not readable for you!",$document); + $err = lang("Document '%1' does not exist or is not readable for you!", $document); $ret = false; return $ret; } - if (self::hasExportLimit($this->export_limit) && !self::is_export_limit_excepted() && count($ids) > (int)$this->export_limit) + if(self::hasExportLimit($this->export_limit) && !self::is_export_limit_excepted() && count($ids) > (int)$this->export_limit) { - $err = lang('No rights to export more than %1 entries!',(int)$this->export_limit); + $err = lang('No rights to export more than %1 entries!', (int)$this->export_limit); $ret = false; return $ret; } // fix application/msword mimetype for rtf files - if ($mimetype == 'application/msword' && strtolower(substr($document,-4)) == '.rtf') + if($mimetype == 'application/msword' && strtolower(substr($document, -4)) == '.rtf') { $mimetype = 'application/rtf'; } - try { - $content = $this->merge_string($content,$ids,$err,$mimetype,$fix); - } catch (\Exception $e) { + try + { + $content = $this->merge_string($content, $ids, $err, $mimetype, $fix); + } + catch (\Exception $e) + { _egw_log_exception($e); $err = $e->getMessage(); $ret = false; @@ -742,51 +806,51 @@ abstract class Merge return $content; } - protected function apply_styles (&$content, $mimetype, $mso_application_progid=null) + protected function apply_styles(&$content, $mimetype, $mso_application_progid = null) { - if (!isset($mso_application_progid)) + if(!isset($mso_application_progid)) { $matches = null; $mso_application_progid = $mimetype == 'application/xml' && - preg_match('/'.preg_quote('', '/').'/',substr($content,0,200),$matches) ? - $matches[1] : ''; + preg_match('/' . preg_quote('', '/') . '/', substr($content, 0, 200), $matches) ? + $matches[1] : ''; } // Tags we can replace with the target document's version $replace_tags = array(); - switch($mimetype.$mso_application_progid) + switch($mimetype . $mso_application_progid) { - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': $doc = new DOMDocument(); $xslt = new XSLTProcessor(); - $doc->load(EGW_INCLUDE_ROOT.'/api/templates/default/Merge/openoffice.xslt'); + $doc->load(EGW_INCLUDE_ROOT . '/api/templates/default/Merge/openoffice.xslt'); $xslt->importStyleSheet($doc); //echo $content;die(); break; - case 'application/xmlWord.Document': // Word 2003*/ - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 + case 'application/xmlWord.Document': // Word 2003*/ + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': // It seems easier to split the parent tags here $replace_tags = array( // Tables, lists don't go inside - '/<(ol|ul|table)( [^>]*)?>/' => '<$1$2>', - '/<\/(ol|ul|table)>/' => '', + '/<(ol|ul|table)( [^>]*)?>/' => '<$1$2>', + '/<\/(ol|ul|table)>/' => '', // Fix for things other than text (newlines) inside table row '/<(td)( [^>]*)?>((?!))(.*?)<\/td>[\s]*?/' => '<$1$2>$4', // Remove extra whitespace - '/]*?)>[^:print:]*?(.*?)<\/li>/' => '$2', // This doesn't get it all - '/[\s]+(.*?)<\/w:t>/' => '$1', + '/]*?)>[^:print:]*?(.*?)<\/li>/' => '$2', // This doesn't get it all + '/[\s]+(.*?)<\/w:t>/' => '$1', // Remove spans with no attributes, linebreaks inside them cause problems - '/(.*?)<\/span>/' => '$1' + '/(.*?)<\/span>/' => '$1' ); - $content = preg_replace(array_keys($replace_tags),array_values($replace_tags),$content); + $content = preg_replace(array_keys($replace_tags), array_values($replace_tags), $content); /* In the case where you have something like (invalid - mismatched tags), @@ -796,14 +860,15 @@ abstract class Merge $count = $i = 0; do { - $content = preg_replace('/(.*?)<\/span>/','$1',$content, -1, $count); + $content = preg_replace('/(.*?)<\/span>/', '$1', $content, -1, $count); $i++; - } while($count > 0 && $i < 10); + } + while($count > 0 && $i < 10); $doc = new DOMDocument(); $xslt = new XSLTProcessor(); $xslt_file = $mimetype == 'application/xml' ? 'wordml.xslt' : 'msoffice.xslt'; - $doc->load(EGW_INCLUDE_ROOT.'/api/templates/default/Merge/'.$xslt_file); + $doc->load(EGW_INCLUDE_ROOT . '/api/templates/default/Merge/' . $xslt_file); $xslt->importStyleSheet($doc); break; } @@ -822,8 +887,9 @@ abstract class Merge $content = $xslt->transformToXml($element); //echo $content;die(); // Word 2003 needs two declarations, add extra declaration back in - if($mimetype == 'application/xml' && $mso_application_progid == 'Word.Document' && strpos($content, ''.$content; + if($mimetype == 'application/xml' && $mso_application_progid == 'Word.Document' && strpos($content, '' . $content; } // Validate /* @@ -845,12 +911,12 @@ abstract class Merge * @param string $charset =null charset to override default set by mimetype or export charset * @return string|boolean merged document or false on error */ - public function &merge_string($_content,$ids,&$err,$mimetype,array $fix=null,$charset=null) + public function &merge_string($_content, $ids, &$err, $mimetype, array $fix = null, $charset = null) { $ids = empty($ids) ? [] : (array)$ids; $matches = null; - if ($mimetype == 'application/xml' && - preg_match('/'.preg_quote('', '/').'/',substr($_content,0,200),$matches)) + if($mimetype == 'application/xml' && + preg_match('/' . preg_quote('', '/') . '/', substr($_content, 0, 200), $matches)) { $mso_application_progid = $matches[1]; } @@ -860,33 +926,38 @@ abstract class Merge } // alternative syntax using double curly brackets (eg. {{cat_id}} instead $$cat_id$$), // agressivly removing all xml-tags eg. Word adds within placeholders - $content = preg_replace_callback('/{{[^}]+}}/i', function($matches) + $content = preg_replace_callback('/{{[^}]+}}/i', function ($matches) { - return '$$'.strip_tags(substr($matches[0], 2, -2)).'$$'; - }, $_content); + return '$$' . strip_tags(substr($matches[0], 2, -2)) . '$$'; + }, $_content); // Handle escaped placeholder markers in RTF, they won't match when escaped if($mimetype == 'application/rtf') { - $content = preg_replace('/\\\{\\\{([^\\}]+)\\\}\\\}/i','$$\1$$',$content); + $content = preg_replace('/\\\{\\\{([^\\}]+)\\\}\\\}/i', '$$\1$$', $content); } // make currently processed mimetype available to class methods; $this->mimetype = $mimetype; // fix garbled placeholders - if ($fix && is_array($fix)) + if($fix && is_array($fix)) { - $content = preg_replace(array_keys($fix),array_values($fix),$content); + $content = preg_replace(array_keys($fix), array_values($fix), $content); //die("
".htmlspecialchars($content)."
\n"); } - list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get differt parts of document, seperatet by Pagerepeat - if ($mimetype == 'text/plain' && $ids && count($ids) > 1) + list($contentstart, $contentrepeat, $contentend) = preg_split('/\$\$pagerepeat\$\$/', $content, -1, PREG_SPLIT_NO_EMPTY) + [null, + null, + null]; //get differt parts of document, seperatet by Pagerepeat + if($mimetype == 'text/plain' && $ids && count($ids) > 1) { // textdocuments are simple, they do not hold start and end, but they may have content before and after the $$pagerepeat$$ tag // header and footer should not hold any $$ tags; if we find $$ tags with the header, we assume it is the pagerepeatcontent $nohead = false; - if (stripos($contentstart,'$$') !== false) $nohead = true; - if ($nohead) + if(stripos($contentstart, '$$') !== false) + { + $nohead = true; + } + if($nohead) { $contentend = $contentrepeat; $contentrepeat = $contentstart; @@ -894,51 +965,58 @@ abstract class Merge } } - if (in_array($mimetype, array('application/vnd.oasis.opendocument.text','application/vnd.oasis.opendocument.text-template')) && count($ids) > 1) + if(in_array($mimetype, array('application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template')) && count($ids) > 1) { if(strpos($content, '$$pagerepeat') === false) { //for odt files we have to split the content and add a style for page break to the style area - list($contentstart,$contentrepeat,$contentend) = preg_split('/office:body>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat - $contentstart = substr($contentstart,0,strlen($contentstart)-1); //remove "<" - $contentrepeat = substr($contentrepeat,0,strlen($contentrepeat)-2); //remove "/', $content, -1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat + $contentstart = substr($contentstart, 0, strlen($contentstart) - 1); //remove "<" + $contentrepeat = substr($contentrepeat, 0, strlen($contentrepeat) - 2); //remove "/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets - $contentstart = $stylestart.''; + list($stylestart, $stylerepeat, $styleend) = preg_split('/<\/office:automatic-styles>/', $content, -1, PREG_SPLIT_NO_EMPTY); //get differt parts of document style sheets + $contentstart = $stylestart . ''; $contentstart .= ''; $contentend = ''; } else { // Template specifies where to repeat - list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get different parts of document, seperated by pagerepeat + list($contentstart, $contentrepeat, $contentend) = preg_split('/\$\$pagerepeat\$\$/', $content, -1, PREG_SPLIT_NO_EMPTY); //get different parts of document, seperated by pagerepeat } } - if (in_array($mimetype, array('application/vnd.ms-word.document.macroenabled.12', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) && count($ids) > 1) + if(in_array($mimetype, array('application/vnd.ms-word.document.macroenabled.12', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')) && count($ids) > 1) { //for Word 2007 XML files we have to split the content and add a style for page break to the style area - list($contentstart,$contentrepeat,$contentend) = preg_split('/w:body>/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat - $contentstart = substr($contentstart,0,strlen($contentstart)-1); //remove "/', $content, -1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat + $contentstart = substr($contentstart, 0, strlen($contentstart) - 1); //remove "'; $contentend = ''; } - list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get the label content - preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY); + list($Labelstart, $Labelrepeat, $Labeltend) = preg_split('/\$\$label\$\$/', $contentrepeat, -1, PREG_SPLIT_NO_EMPTY) + [null, + null, + null]; //get the label content + preg_match_all('/\$\$labelplacement\$\$/', $contentrepeat, $countlables, PREG_SPLIT_NO_EMPTY); $countlables = count($countlables[0]); - preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1); + preg_replace('/\$\$labelplacement\$\$/', '', $Labelrepeat, 1); $lableprint = $countlables > 1; - if (count($ids) > 1 && !$contentrepeat) + if(count($ids) > 1 && !$contentrepeat) { $err = lang('for more than one contact in a document use the tag pagerepeat!'); $ret = false; return $ret; } - if ($this->report_memory_usage) error_log(__METHOD__."(count(ids)=".count($ids).") strlen(contentrepeat)=".strlen($contentrepeat).', strlen(labelrepeat)='.strlen($Labelrepeat)); - - if ($contentrepeat) + if($this->report_memory_usage) { - $content_stream = fopen('php://temp','r+'); + error_log(__METHOD__ . "(count(ids)=" . count($ids) . ") strlen(contentrepeat)=" . strlen($contentrepeat) . ', strlen(labelrepeat)=' . strlen($Labelrepeat)); + } + + if($contentrepeat) + { + $content_stream = fopen('php://temp', 'r+'); fwrite($content_stream, $contentstart); $joiner = ''; switch($mimetype) @@ -947,8 +1025,8 @@ abstract class Merge case 'text/rtf': $joiner = '\\par \\page\\pard\\plain'; break; - case 'application/vnd.oasis.opendocument.text': // oo text - case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet + case 'application/vnd.oasis.opendocument.text': // oo text + case 'application/vnd.oasis.opendocument.spreadsheet': // oo spreadsheet case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': @@ -968,20 +1046,26 @@ abstract class Merge $joiner = "\r\n"; break; default: - $err = lang('%1 not implemented for %2!','$$pagerepeat$$',$mimetype); + $err = lang('%1 not implemented for %2!', '$$pagerepeat$$', $mimetype); $ret = false; return $ret; } } - foreach ((array)$ids as $n => $id) + foreach((array)$ids as $n => $id) { - if ($contentrepeat) $content = $contentrepeat; //content to repeat - if ($lableprint) $content = $Labelrepeat; + if($contentrepeat) + { + $content = $contentrepeat; + } //content to repeat + if($lableprint) + { + $content = $Labelrepeat; + } // generate replacements; if exception is thrown, catch it set error message and return false try { - if(!($replacements = $this->get_replacements($id,$content))) + if(!($replacements = $this->get_replacements($id, $content))) { $err = lang('Entry not found!'); $ret = false; @@ -995,114 +1079,120 @@ abstract class Merge $ret = false; return $ret; } - if ($this->report_memory_usage) error_log(__METHOD__."() $n: $id ".Api\Vfs::hsize(memory_get_usage(true))); + if($this->report_memory_usage) + { + error_log(__METHOD__ . "() $n: $id " . Api\Vfs::hsize(memory_get_usage(true))); + } // some general replacements: current user, date and time if(strpos($content, '$$user/') !== false && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'person_id'))) { $replacements += $this->contact_replacements($user, 'user', false, $content); $replacements['$$user/primary_group$$'] = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'account_primary_group')); } - $replacements['$$date$$'] = Api\DateTime::to('now',true); + $replacements['$$date$$'] = Api\DateTime::to('now', true); $replacements['$$datetime$$'] = Api\DateTime::to('now'); - $replacements['$$time$$'] = Api\DateTime::to('now',false); + $replacements['$$time$$'] = Api\DateTime::to('now', false); $app = $this->get_app(); $replacements += $this->share_placeholder($app, $id, '', $content); // does our extending class registered table-plugins AND document contains table tags - if ($this->table_plugins && preg_match_all('/\\$\\$table\\/([A-Za-z0-9_]+)\\$\\$(.*?)\\$\\$endtable\\$\\$/s',$content,$matches,PREG_SET_ORDER)) + if($this->table_plugins && preg_match_all('/\\$\\$table\\/([A-Za-z0-9_]+)\\$\\$(.*?)\\$\\$endtable\\$\\$/s', $content, $matches, PREG_SET_ORDER)) { // process each table foreach($matches as $match) { - $plugin = $match[1]; // plugin name + $plugin = $match[1]; // plugin name $callback = $this->table_plugins[$plugin]; - $repeat = $match[2]; // line to repeat + $repeat = $match[2]; // line to repeat $repeats = ''; - if (isset($callback)) + if(isset($callback)) { - for($n = 0; ($row_replacements = $this->$callback($plugin,$id,$n,$repeat)); ++$n) + for($n = 0; ($row_replacements = $this->$callback($plugin, $id, $n, $repeat)); ++$n) { $_repeat = $this->process_commands($repeat, $row_replacements); - $repeats .= $this->replace($_repeat,$row_replacements,$mimetype,$mso_application_progid); + $repeats .= $this->replace($_repeat, $row_replacements, $mimetype, $mso_application_progid); } } - $content = str_replace($match[0],$repeats,$content); + $content = str_replace($match[0], $repeats, $content); } } - $content = $this->process_commands($this->replace($content,$replacements,$mimetype,$mso_application_progid,$charset), $replacements); + $content = $this->process_commands($this->replace($content, $replacements, $mimetype, $mso_application_progid, $charset), $replacements); // remove not existing replacements (eg. from calendar array) - if (strpos($content,'$$') !== null) + if(strpos($content, '$$') !== null) { - $content = preg_replace('/\$\$[a-z0-9_\/]+\$\$/i','',$content); + $content = preg_replace('/\$\$[a-z0-9_\/]+\$\$/i', '', $content); } - if ($contentrepeat) + if($contentrepeat) { fwrite($content_stream, ($n == 0 ? '' : $joiner) . $content); } if($lableprint) { - $contentrep[is_array($id) ? implode(':',$id) : $id] = $content; + $contentrep[is_array($id) ? implode(':', $id) : $id] = $content; } } - if ($Labelrepeat) + if($Labelrepeat) { - $countpage=0; - $count=0; - $contentrepeatpages[$countpage] = $Labelstart.$Labeltend; + $countpage = 0; + $count = 0; + $contentrepeatpages[$countpage] = $Labelstart . $Labeltend; - foreach ($contentrep as $Label) + foreach($contentrep as $Label) { - $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/',$Label,$contentrepeatpages[$countpage],1); - $count=$count+1; - if (($count % $countlables) == 0 && count($contentrep)>$count) //new page + $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/', $Label, $contentrepeatpages[$countpage], 1); + $count = $count + 1; + if(($count % $countlables) == 0 && count($contentrep) > $count) //new page { - $countpage = $countpage+1; - $contentrepeatpages[$countpage] = $Labelstart.$Labeltend; + $countpage = $countpage + 1; + $contentrepeatpages[$countpage] = $Labelstart . $Labeltend; } } - $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/','',$contentrepeatpages[$countpage],-1); //clean empty fields + $contentrepeatpages[$countpage] = preg_replace('/\$\$labelplacement\$\$/', '', $contentrepeatpages[$countpage], -1); //clean empty fields switch($mimetype) { case 'application/rtf': case 'text/rtf': - $ret = $contentstart.implode('\\par \\page\\pard\\plain',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('\\par \\page\\pard\\plain', $contentrepeatpages) . $contentend; break; case 'application/vnd.oasis.opendocument.text': case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.presentation-template': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'application/vnd.oasis.opendocument.spreadsheet': case 'application/vnd.oasis.opendocument.spreadsheet-template': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': - $ret = $contentstart.implode('',$contentrepeatpages).$contentend; + $ret = $contentstart . implode('', $contentrepeatpages) . $contentend; break; case 'text/plain': - $ret = $contentstart.implode("\r\n",$contentrep).$contentend; + $ret = $contentstart . implode("\r\n", $contentrep) . $contentend; break; default: - $err = lang('%1 not implemented for %2!','$$labelplacement$$',$mimetype); + $err = lang('%1 not implemented for %2!', '$$labelplacement$$', $mimetype); $ret = false; } return $ret; } - if ($contentrepeat) + if($contentrepeat) { fwrite($content_stream, $contentend); rewind($content_stream); $content = stream_get_contents($content_stream); } - if ($this->report_memory_usage) error_log(__METHOD__."() returning ".Api\Vfs::hsize(memory_get_peak_usage(true))); + if($this->report_memory_usage) + { + error_log(__METHOD__ . "() returning " . Api\Vfs::hsize(memory_get_peak_usage(true))); + } return $content; } @@ -1117,54 +1207,57 @@ abstract class Merge * @param string $charset =null charset to override default set by mimetype or export charset * @return string */ - protected function replace($content,array $replacements,$mimetype,$mso_application_progid='',$charset=null) + protected function replace($content, array $replacements, $mimetype, $mso_application_progid = '', $charset = null) { switch($mimetype) { - case 'application/vnd.oasis.opendocument.text': // open office + case 'application/vnd.oasis.opendocument.text': // open office case 'application/vnd.oasis.opendocument.spreadsheet': case 'application/vnd.oasis.opendocument.presentation': case 'application/vnd.oasis.opendocument.text-template': case 'application/vnd.oasis.opendocument.spreadsheet-template': case 'application/vnd.oasis.opendocument.presentation-template': - case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': // ms office 2007 case 'application/vnd.ms-word.document.macroenabled.12': case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': case 'application/vnd.ms-excel.sheet.macroenabled.12': case 'application/xml': case 'text/xml': $is_xml = true; - $charset = 'utf-8'; // xml files --> always use utf-8 + $charset = 'utf-8'; // xml files --> always use utf-8 break; case 'application/rtf': case 'text/rtf': - $charset = 'iso-8859-1'; // rtf seems to user iso-8859-1 or equivalent windows charset, not utf-8 + $charset = 'iso-8859-1'; // rtf seems to user iso-8859-1 or equivalent windows charset, not utf-8 break; case 'text/html': $is_xml = true; $matches = null; - if (preg_match('/ use our export-charset, defined in addressbook prefs - if (empty($charset)) $charset = $this->contacts->prefs['csv_charset']; + default: // div. text files --> use our export-charset, defined in addressbook prefs + if(empty($charset)) + { + $charset = $this->contacts->prefs['csv_charset']; + } break; } //error_log(__METHOD__."('$document', ... ,$mimetype) --> $charset (egw=".Api\Translation::charset().', export='.$this->contacts->prefs['csv_charset'].')'); // do we need to convert charset - if ($charset && $charset != Api\Translation::charset()) + if($charset && $charset != Api\Translation::charset()) { - $replacements = Api\Translation::convert($replacements,Api\Translation::charset(),$charset); + $replacements = Api\Translation::convert($replacements, Api\Translation::charset(), $charset); } // Date only placeholders for timestamps @@ -1172,14 +1265,14 @@ abstract class Merge { foreach($this->date_fields as $field) { - if(($value = $replacements['$$'.$field.'$$'] ?? null)) + if(($value = $replacements['$$' . $field . '$$'] ?? null)) { - $time = Api\DateTime::createFromFormat('+'.Api\DateTime::$user_dateformat.' '.Api\DateTime::$user_timeformat.'*', $value); - $replacements['$$'.$field.'/date$$'] = $time ? $time->format(Api\DateTime::$user_dateformat) : ''; + $time = Api\DateTime::createFromFormat('+' . Api\DateTime::$user_dateformat . ' ' . Api\DateTime::$user_timeformat . '*', $value); + $replacements['$$' . $field . '/date$$'] = $time ? $time->format(Api\DateTime::$user_dateformat) : ''; } } } - if ($is_xml) // zip'ed xml document (eg. OO) + if($is_xml) // zip'ed xml document (eg. OO) { // Numeric fields $names = array(); @@ -1187,35 +1280,35 @@ abstract class Merge // Tags we can replace with the target document's version $replace_tags = array(); // only keep tags, if we have xsl extension available - if (class_exists('XSLTProcessor') && class_exists('DOMDocument') && $this->parse_html_styles) + if(class_exists('XSLTProcessor') && class_exists('DOMDocument') && $this->parse_html_styles) { - switch($mimetype.$mso_application_progid) + switch($mimetype . $mso_application_progid) { case 'text/html': $replace_tags = array( - '','','','','','','
    ','