From eb6fd7cf3d31158a43f85e87e290f6be03482567 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Tue, 17 Feb 2015 10:42:00 +0000 Subject: [PATCH 01/43] * Mobile theme: Login page style improvement --- pixelegg/css/mobile.css | 101 +++++++++++++++++++ pixelegg/css/mobile.less | 136 +++++++++++++++++++++++++- pixelegg/templates/pixelegg/login.tpl | 6 +- 3 files changed, 239 insertions(+), 4 deletions(-) diff --git a/pixelegg/css/mobile.css b/pixelegg/css/mobile.css index 9c73171882..cde3df2ba2 100644 --- a/pixelegg/css/mobile.css +++ b/pixelegg/css/mobile.css @@ -6248,10 +6248,83 @@ a.textSidebox { /*@import "../less/layout_nextmatch.less";*/ /*@import "../less/layout_footer.less";*/ /*@import "../less/layout_dialog.less";*/ +/********************************/ +/* */ +/* MEDIA DEFINITION */ +/* */ +/********************************/ +/*Tablets Max-Width*/ +/*Smartphones Max-Width*/ +/*Smartphones Min-Width*/ +/*All devices portrait mode*/ +/*All devices landscape mode*/ +/*Tablets landscape mode*/ +/*Tablets Portrait*/ @media all { body { background-color: transparent; } + body div#loginMainDiv #divAppIconBar #divLogo img[src$="svg"] { + width: 40%; + margin-top: 5px; + } + body div#loginMainDiv div#centerBox { + position: absolute; + margin: 0; + width: 100%; + background-color: transparent; + padding: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; + -webkit-border-top-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topleft: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + background-color: none; + background-image: none; + background-repeat: none; + border: none; + border-radius: none; + } + body div#loginMainDiv div#centerBox form { + margin-top: -2em; + margin-right: 3em; + } + body div#loginMainDiv div#centerBox form table.divLoginbox { + width: 100%; + float: left; + } + body div#loginMainDiv div#centerBox form table.divLoginbox tr.hiddenCredential { + display: none; + } + body div#loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { + font-size: xx-large; + } + body div#loginMainDiv div#centerBox form table.divLoginbox input, + body div#loginMainDiv div#centerBox form table.divLoginbox select { + width: 100%; + height: 60px; + } + body div#loginMainDiv div#centerBox form table.divLoginbox td { + font-size: 300%; + padding: 0.8%; + } + body div#loginMainDiv div#centerBox form table.divLoginbox td.registration { + font-size: 180%; + } + body div#loginMainDiv div#centerBox form table.divLoginbox td select { + background-size: 48px auto; + } + body div#loginMainDiv div#centerBox #loginCdMessage { + font-size: large; + padding: 0; + } body div.egw_fw_mobile_iOS_popup_appHeader { padding-top: 15px; } @@ -6966,3 +7039,31 @@ a.textSidebox { background-position: center; } } +@media only screen and (max-device-width : 1024px) and (orientation : portrait) { + body div#loginMainDiv #divAppIconBar #divLogo img[src$="svg"] { + width: 70%; + } + body div#loginMainDiv div#centerBox form table.divLoginbox { + width: 100%; + float: left; + } + body div#loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { + font-size: xx-large; + } + body div#loginMainDiv div#centerBox form table.divLoginbox input, + body div#loginMainDiv div#centerBox form table.divLoginbox select { + width: 100%; + height: 80px; + } + body div#loginMainDiv div#centerBox form table.divLoginbox td { + font-size: 400%; + padding: 0.8%; + } + body div#loginMainDiv div#centerBox form table.divLoginbox td.registration { + font-size: 250%; + } + body div#loginMainDiv div#centerBox #loginCdMessage { + font-size: xx-large; + padding: 0; + } +} diff --git a/pixelegg/css/mobile.less b/pixelegg/css/mobile.less index 6fa0b774aa..63aac28461 100644 --- a/pixelegg/css/mobile.less +++ b/pixelegg/css/mobile.less @@ -12,8 +12,103 @@ @import "pixelegg.less"; +/********************************/ +/* */ +/* MEDIA DEFINITION */ +/* */ +/********************************/ +/*Tablets Max-Width*/ +@tablet-max: 1024px; +/*Smartphones Max-Width*/ +@smartphone-max: 768px; +/*Smartphones Min-Width*/ +@smartphone-min: 321px; +/*All devices portrait mode*/ +@handheld-portrait: ~"only screen and (max-device-width : @{tablet-max}) and (orientation : portrait)"; +/*All devices landscape mode*/ +@handheld-landscape: ~"only screen and (max-device-width : @{tablet-max}) and (orientation : landscape)"; +/*Tablets landscape mode*/ +@tablet-portrait: ~"only screen and (max-device-width : @{tablet-max}) and (min-width: @{smartphone-max}) and (orientation : landscape)"; +/*Tablets Portrait*/ +@tablet-portrait: ~"only screen and (max-device-width : @{tablet-max}) and (min-width: @{smartphone-max}) and (orientation : portrait)"; + + @media all { body{ + + div#loginMainDiv{ + #divAppIconBar { + #divLogo img[src$="svg"] { + width:40%; + margin-top: 5px; + } + } + div#centerBox{ + position:absolute; + margin: 0; + width: 100%; + background-color: transparent; + padding: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius:0; + -webkit-border-bottom-left-radius: 0; + -webkit-border-top-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topleft: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + background-color: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-repeat: none; + border:none; + border-radius: none; + form { + margin-top: -2em; + margin-right: 3em; + + table.divLoginbox { + width:100%; + float:left; + tr.hiddenCredential { + display:none; + } + input[type="submit"] { + font-size: xx-large; + + } + input, select { + width:100%; + height:60px; + } + td { + font-size: 300%; + padding:0.8%; + &.registration{ + font-size: 180%; + } + select { + background-size: 48px auto; + } + } + } + } + #loginCdMessage { + font-size:large; + padding:0; + } + } + } + background-color: transparent; // iOS appHeader class div.egw_fw_mobile_iOS_popup_appHeader{ @@ -733,4 +828,43 @@ background-size: 120px 120px; background-position: center; } -} \ No newline at end of file +} +@media @handheld-portrait +{ + body{ + div#loginMainDiv{ + #divAppIconBar { + #divLogo img[src$="svg"] { + width:70%; + } + } + div#centerBox{ + form { + table.divLoginbox { + width:100%; + float:left; + input[type="submit"] { + font-size: xx-large; + + } + input, select { + width:100%; + height:80px; + } + td { + font-size: 400%; + padding:0.8%; + &.registration { + font-size: 250%; + } + } + } + } + #loginCdMessage { + font-size:xx-large; + padding:0; + } + } + } + } +} diff --git a/pixelegg/templates/pixelegg/login.tpl b/pixelegg/templates/pixelegg/login.tpl index 5d6e81cc07..fafe8b6ac4 100644 --- a/pixelegg/templates/pixelegg/login.tpl +++ b/pixelegg/templates/pixelegg/login.tpl @@ -14,8 +14,8 @@ <tr class="divLoginboxHeader"> <td colspan="3">{website_title}</td> </tr> - <tr> - <td colspan="2" height="20"> + <tr class="hiddenCredential"> + <td colspan="2" height="20" > <input type="hidden" name="passwd_type" value="text" /> <input type="hidden" name="account_type" value="u" /> </td> @@ -57,7 +57,7 @@ </tr> <!-- BEGIN registration --> <tr> - <td colspan="3" height="20" align="center"> + <td colspan="3" height="20" align="center" class="registration"> {lostpassword_link} {lostid_link} {register_link} From 2a5971258b1c1af5c61cc223ba6253805d75a88c Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Tue, 17 Feb 2015 10:52:50 +0000 Subject: [PATCH 02/43] * PostgreSQL: fixed not working new installation due to access to egw_mailaccounts table prior to creating it: gets now checked, to not abort transaction --- phpgwapi/inc/class.accounts_sql.inc.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpgwapi/inc/class.accounts_sql.inc.php b/phpgwapi/inc/class.accounts_sql.inc.php index 75e8ab97ca..5e98a802fb 100644 --- a/phpgwapi/inc/class.accounts_sql.inc.php +++ b/phpgwapi/inc/class.accounts_sql.inc.php @@ -128,7 +128,9 @@ class accounts_sql $this->contacts_table.'.tel_work AS account_phone,'; $join = 'LEFT JOIN '.$this->contacts_table.' ON '.$this->table.'.account_id='.$this->contacts_table.'.account_id'; } - else + // during setup emailadmin might not yet be installed and running below query + // will abort transaction in PostgreSQL + elseif (!isset($GLOBALS['egw_setup']) || in_array(emailadmin_smtp_sql::TABLE, $this->db->table_names(true))) { $extra_cols = emailadmin_smtp_sql::TABLE.'.mail_value AS account_email,'; $join = 'LEFT JOIN '.emailadmin_smtp_sql::TABLE.' ON '.$this->table.'.account_id=-'.emailadmin_smtp_sql::TABLE.'.account_id AND mail_type='.emailadmin_smtp_sql::TYPE_ALIAS; From 3ead887bf7c4eca31012e45d8c917e1464bc0646 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Tue, 17 Feb 2015 12:50:55 +0000 Subject: [PATCH 03/43] Fix splitter widget dock to the fullSize if there is no size preference yet --- etemplate/js/et2_widget_split.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etemplate/js/et2_widget_split.js b/etemplate/js/et2_widget_split.js index 3dc542b324..c0fe5075ad 100644 --- a/etemplate/js/et2_widget_split.js +++ b/etemplate/js/et2_widget_split.js @@ -196,6 +196,12 @@ var et2_split = et2_DOMWidget.extend([et2_IResizeable,et2_IPrint], this.prefSize = pref[this.orientation == "v" ?'sizeLeft' : 'sizeTop']; } } + // If there is no preference yet, set it to half size + // Otherwise the right pane gets the fullsize + else + { + this.prefSize = this.orientation == "v" ? options.sizeLeft: options.sizeTop; + } } // Avoid double init From b18f0ecc76969bd9022b83beb55a5560b84dd879 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Tue, 17 Feb 2015 13:10:21 +0000 Subject: [PATCH 04/43] Fix egw_message does not show newlines --- phpgwapi/templates/idots/css/traditional.css | 1 + pixelegg/css/mobile.css | 1 + pixelegg/css/pixelegg.css | 1 + pixelegg/less/layout_dialog.less | 1 + 4 files changed, 4 insertions(+) diff --git a/phpgwapi/templates/idots/css/traditional.css b/phpgwapi/templates/idots/css/traditional.css index 2ffbf5fd87..327ee1970b 100755 --- a/phpgwapi/templates/idots/css/traditional.css +++ b/phpgwapi/templates/idots/css/traditional.css @@ -974,6 +974,7 @@ body > div#egw_message { border: 2px gray solid; min-width: 100px; z-index: 10; + white-space: pre-wrap; } /** diff --git a/pixelegg/css/mobile.css b/pixelegg/css/mobile.css index cde3df2ba2..1421dda292 100644 --- a/pixelegg/css/mobile.css +++ b/pixelegg/css/mobile.css @@ -3815,6 +3815,7 @@ body > div#egw_message { z-index: 100000; margin: 0px auto; max-width: 90%; + white-space: pre-wrap; } /** * Less-file for egroupware diff --git a/pixelegg/css/pixelegg.css b/pixelegg/css/pixelegg.css index 8ae0b4635e..edb56fb8e1 100644 --- a/pixelegg/css/pixelegg.css +++ b/pixelegg/css/pixelegg.css @@ -3804,6 +3804,7 @@ body > div#egw_message { z-index: 100000; margin: 0px auto; max-width: 90%; + white-space: pre-wrap; } /** * Less-file for egroupware diff --git a/pixelegg/less/layout_dialog.less b/pixelegg/less/layout_dialog.less index 017dda1a0a..35de1905fa 100755 --- a/pixelegg/less/layout_dialog.less +++ b/pixelegg/less/layout_dialog.less @@ -506,4 +506,5 @@ body > div#egw_message { z-index: 100000; margin: 0px auto; max-width: 90%; + white-space: pre-wrap; } From 39115a09851d66bb5949735091d76ecde4bc080b Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Tue, 17 Feb 2015 14:35:40 +0000 Subject: [PATCH 05/43] fixed not shown custom-fields in infolog --- etemplate/inc/class.etemplate_widget_customfields.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etemplate/inc/class.etemplate_widget_customfields.inc.php b/etemplate/inc/class.etemplate_widget_customfields.inc.php index 5897dd8840..cdc34868f5 100644 --- a/etemplate/inc/class.etemplate_widget_customfields.inc.php +++ b/etemplate/inc/class.etemplate_widget_customfields.inc.php @@ -129,7 +129,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer if ($app && $app != 'stylite' && $app != $GLOBALS['egw_info']['flags']['currentapp'] && ( $GLOBALS['egw_info']['flags']['currentapp'] == 'etemplate' || !$this->attrs['customfields'] || etemplate::$hooked - )) + ) || !isset($customfields)) { // app changed $customfields =& egw_customfields::get($app); From 93a514993a3b0eb53488ec225e57eaa77e0e520e Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Tue, 17 Feb 2015 16:21:50 +0000 Subject: [PATCH 06/43] * InfoLog/Addressbook: refresh CRM view if InfoLog was edited without having InfoLog tab open --- infolog/js/app.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/infolog/js/app.js b/infolog/js/app.js index f8c1e04f45..0718da9430 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -100,8 +100,13 @@ app.classes.infolog = AppJS.extend( } } } - //Refresh handler for infologs integrated in calendar - if (_app == 'infolog' && _id && _type !='delete') + // Refresh handler for Addressbook CRM view + if (_app == 'infolog' && this.et2._inst.app == 'addressbook' && this.et2._inst.name == 'infolog.index') + { + this.et2._inst.refresh(_msg, _app, _id, _type); + } + // Refresh handler for infologs integrated in calendar + if (_app == 'infolog' && _id && _type != 'delete') { var info_type = egw.dataGetUIDdata(_app+"::"+_id)?egw.dataGetUIDdata(_app+"::"+_id).data.info_type:false; var cal_show = egw.preference('cal_show','infolog')||false; From 286cca54e10216d9b98bbb0ce6e66313aefa8f22 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Tue, 17 Feb 2015 16:27:29 +0000 Subject: [PATCH 07/43] Fix link to no longer pre-selecting last used app --- etemplate/js/et2_widget_link.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etemplate/js/et2_widget_link.js b/etemplate/js/et2_widget_link.js index ffe7faadb8..60e5cab6ac 100644 --- a/etemplate/js/et2_widget_link.js +++ b/etemplate/js/et2_widget_link.js @@ -582,7 +582,6 @@ var et2_link_entry = et2_inputWidget.extend( // Application selection this.app_select = $j(document.createElement("select")).appendTo(this.div) - .val(this.options.value.app||'') .change(function(e) { // Clear cache when app changes self.cache = {}; @@ -607,6 +606,11 @@ var et2_link_entry = et2_inputWidget.extend( this.app_select.hide(); this.div.addClass("no_app"); } + else + { + // Now that options are in, set to last used app + this.app_select.val(this.options.value.app||''); + } // Search input this.search = $j(document.createElement("input")) From c988b3390748d5f2b17f9ec142d33ad3b5607162 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Tue, 17 Feb 2015 16:38:44 +0000 Subject: [PATCH 08/43] Allow notes to scroll as needed. --- home/templates/default/note.xet | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/home/templates/default/note.xet b/home/templates/default/note.xet index 55764d923f..0ccddac8d0 100644 --- a/home/templates/default/note.xet +++ b/home/templates/default/note.xet @@ -9,5 +9,10 @@ <button statustext="Apply the changes" label="Apply" id="apply" image="apply" background_image="1"/> <button statustext="leave without saveing the entry" label="Cancel" id="cancel" onclick="window.close();" image="cancel" background_image="1"/> </hbox> + <styles> + .home_note_portlet .et2_container > div { + overflow: auto; + } + </styles> </template> </overlay> From ae0c757ea211743cbc396f8c7f8c86d76db13b44 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Tue, 17 Feb 2015 18:02:10 +0000 Subject: [PATCH 09/43] Fix some nextmatch custom field bugs: - no custom fields in nm rows when there were none explicitly selected - Custom field column shown even if none were defined --- etemplate/js/et2_extension_nextmatch.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index bc5c81dbbc..b1df8f0888 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -1247,6 +1247,12 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin // Custom fields get listed separately if(widget.instanceOf(et2_nextmatch_customfields)) { + if(jQuery.isEmptyObject(widget.customfields)) + { + // No customfields defined, don't show column + delete(columns[col.id]); + continue; + } for(var field_name in widget.customfields) { columns[widget.prefix+field_name] = " - "+widget.customfields[field_name].label; @@ -2753,6 +2759,7 @@ var et2_nextmatch_customfields = et2_customfields_list.extend(et2_INextmatchHead } var columnMgr = this.nextmatch.dataview.getColumnMgr(); var nm_column = null; + var set_fields = {}; for(var i = 0; i < this.nextmatch.columns.length; i++) { if(this.nextmatch.columns[i].widget == this) @@ -2816,7 +2823,13 @@ var et2_nextmatch_customfields = et2_customfields_list.extend(et2_INextmatchHead { cf.hide(); } + else if (jQuery.isEmptyObject(this.options.fields)) + { + // If we're showing it make sure it's set, but only after + set_fields[field_name] = true; + } } + jQuery.extend(this.options.fields, set_fields); }, /** From b2d1fa70d2eb8b1758fadac584589b16ac865426 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Tue, 17 Feb 2015 18:45:14 +0000 Subject: [PATCH 10/43] Fix bug where old image directory was re-scanned when changing it, instead of the new image directory --- admin/inc/hook_config_validate.inc.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/admin/inc/hook_config_validate.inc.php b/admin/inc/hook_config_validate.inc.php index 341fb9e387..01c9e60b36 100644 --- a/admin/inc/hook_config_validate.inc.php +++ b/admin/inc/hook_config_validate.inc.php @@ -33,5 +33,8 @@ function vfs_image_dir($vfs_image_dir) if ($vfs_image_dir != (string)$GLOBALS['egw_info']['server']['vfs_image_dir']) { common::delete_image_map(); + + // Set the global now, or the old value will get re-loaded + $GLOBALS['egw_info']['server']['vfs_image_dir'] = $vfs_image_dir; } } \ No newline at end of file From 5bb66358220c30be83e8b0a33afaf4cf4d1441c8 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Tue, 17 Feb 2015 22:25:48 +0000 Subject: [PATCH 11/43] harden ldap auth, by removing \000 bytes, causing passwords to be not empty by php, but empty to c libaries --- phpgwapi/inc/class.auth_ads.inc.php | 10 ++++--- phpgwapi/inc/class.auth_ldap.inc.php | 41 ++++++++++++++++------------ 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/phpgwapi/inc/class.auth_ads.inc.php b/phpgwapi/inc/class.auth_ads.inc.php index 43331d71ec..2e4d265175 100644 --- a/phpgwapi/inc/class.auth_ads.inc.php +++ b/phpgwapi/inc/class.auth_ads.inc.php @@ -30,17 +30,19 @@ class auth_ads implements auth_backend * password authentication * * @param string $username username of account to authenticate - * @param string $passwd corresponding password - * @param string $passwd_type='text' 'text' for cleartext passwords (default) + * @param string $_passwd corresponding password + * @param string $passwd_type ='text' 'text' for cleartext passwords (default) * @return boolean true if successful authenticated, false otherwise */ - function authenticate($username, $passwd, $passwd_type='text') + function authenticate($username, $_passwd, $passwd_type='text') { unset($passwd_type); // not used by required in function signature if (preg_match('/[()|&=*,<>!~]/',$username)) { return False; } + // harden ldap auth, by removing \000 bytes, causing passwords to be not empty by php, but empty to c libaries + $passwd = str_replace("\000", '', $_passwd); $adldap = accounts_ads::get_adldap(); // bind with username@ads_domain, only if a non-empty password given, in case anonymous search is enabled @@ -145,7 +147,7 @@ class auth_ads implements auth_backend * @param int $account_id account id of user whose passwd should be changed * @param string $passwd must be cleartext, usually not used, but may be used to authenticate as user to do the change -> ldap * @param int $lastpwdchange must be a unixtimestamp or 0 (force user to change pw) or -1 for current time - * @param boolean $return_mod=false true return ldap modification instead of executing it + * @param boolean $return_mod =false true return ldap modification instead of executing it * @return boolean|array true if account_lastpwd_change successful changed, false otherwise or array if $return_mod */ static function setLastPwdChange($account_id=0, $passwd=NULL, $lastpwdchange=NULL, $return_mod=false) diff --git a/phpgwapi/inc/class.auth_ldap.inc.php b/phpgwapi/inc/class.auth_ldap.inc.php index 97c074d068..3a236f07e5 100644 --- a/phpgwapi/inc/class.auth_ldap.inc.php +++ b/phpgwapi/inc/class.auth_ldap.inc.php @@ -35,15 +35,18 @@ class auth_ldap implements auth_backend /** * authentication against LDAP * - * @param string $username username of account to authenticate - * @param string $passwd corresponding password + * @param string $_username username of account to authenticate + * @param string $_passwd corresponding password * @return boolean true if successful authenticated, false otherwise */ - function authenticate($username, $passwd, $passwd_type='text') + function authenticate($_username, $_passwd, $passwd_type='text') { + unset($passwd_type); // not used by required by function signature + // allow non-ascii in username & password - $username = translation::convert($username,translation::charset(),'utf-8'); - $passwd = translation::convert($passwd,translation::charset(),'utf-8'); + $username = translation::convert($_username,translation::charset(),'utf-8'); + // harden ldap auth, by removing \000 bytes, causing passwords to be not empty by php, but empty to c libaries + $passwd = str_replace("\000", '', translation::convert($_passwd,translation::charset(),'utf-8')); if(!$ldap = common::ldapConnect()) { @@ -59,8 +62,8 @@ class auth_ldap implements auth_backend /* find the dn for this uid, the uid is not always in the dn */ $attributes = array('uid','dn','givenName','sn','mail','uidNumber','shadowExpire','homeDirectory'); - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']),$filter); + $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']), + $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'); if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap') { @@ -133,6 +136,7 @@ class auth_ldap implements auth_backend elseif ($GLOBALS['egw_info']['server']['pwd_migration_allowed'] && !empty($GLOBALS['egw_info']['server']['pwd_migration_types'])) { + $matches = null; // try to query password from ldap server (might fail because of ACL) and check if we need to migrate the hash if (($sri = ldap_search($ldap, $userDN,"(objectclass=*)", array('userPassword'))) && ($values = ldap_get_entries($ldap, $sri)) && isset($values[0]['userpassword'][0]) && @@ -147,7 +151,7 @@ class auth_ldap implements auth_backend return $ret; } } - if ($this->debug) error_log(__METHOD__."('$username','$password') dn not found or password wrong!"); + if ($this->debug) error_log(__METHOD__."('$_username', '$_passwd') dn not found or password wrong!"); // dn not found or password wrong return False; } @@ -155,13 +159,13 @@ class auth_ldap implements auth_backend /** * fetch the last pwd change for the user * - * @param string $username username of account to authenticate + * @param string $_username username of account to authenticate * @return mixed false or shadowlastchange*24*3600 */ - function getLastPwdChange($username) + function getLastPwdChange($_username) { // allow non-ascii in username & password - $username = translation::convert($username,translation::charset(),'utf-8'); + $username = translation::convert($_username,translation::charset(),'utf-8'); if(!$ldap = common::ldapConnect()) { @@ -177,8 +181,8 @@ class auth_ldap implements auth_backend /* find the dn for this uid, the uid is not always in the dn */ $attributes = array('uid','dn','shadowexpire','shadowlastchange'); - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']),$filter); + $filter = str_replace(array('%user','%domain'),array(ldap::quote($username),$GLOBALS['egw_info']['user']['domain']), + $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'); if ($GLOBALS['egw_info']['server']['account_repository'] == 'ldap') { @@ -237,8 +241,8 @@ class auth_ldap implements auth_backend } //echo "<p>auth_ldap::change_password('$old_passwd','$new_passwd',$account_id) username='$username'</p>\n"; - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']),$filter); + $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']), + $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'); $ds = common::ldapConnect(); $sri = ldap_search($ds, $GLOBALS['egw_info']['server']['ldap_context'], $filter); @@ -270,7 +274,7 @@ class auth_ldap implements auth_backend * @param string $old_passwd must be cleartext or empty to not to be checked * @param string $new_passwd must be cleartext * @param int $account_id account id of user whose passwd should be changed - * @param boolean $update_lastchange=true + * @param boolean $update_lastchange =true * @return boolean true if password successful changed, false otherwise */ function change_password($old_passwd, $new_passwd, $account_id=0, $update_lastchange=true) @@ -286,8 +290,8 @@ class auth_ldap implements auth_backend } if ($this->debug) error_log(__METHOD__."('$old_passwd','$new_passwd',$account_id, $update_lastchange) username='$username'"); - $filter = $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'; - $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']),$filter); + $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']), + $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'); $ds = $ds_admin = common::ldapConnect(); $sri = ldap_search($ds, $GLOBALS['egw_info']['server']['ldap_context'], $filter); @@ -308,6 +312,7 @@ class auth_ldap implements auth_backend $ds = $user_ds->ldapConnect('',$dn,$old_passwd); } catch (egw_exception_no_permission $e) { + unset($e); return false; // wrong old user password } } From 3a06bcb28597b97ba2e51f991014dab7ccf249f6 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 08:15:54 +0000 Subject: [PATCH 12/43] disabling dates_range_view in favor of using dates-table direct, as it appears 1.5-3 times quicker in two big installations I tested with --- calendar/inc/class.calendar_so.inc.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php index 33729f4f91..98645395a6 100644 --- a/calendar/inc/class.calendar_so.inc.php +++ b/calendar/inc/class.calendar_so.inc.php @@ -161,6 +161,9 @@ class calendar_so /** * Return sql to fetch all dates in a given timerange, to be used instead of full dates table in further sql queries * + * Currently NOT used, as using two views joined together appears slower in my tests (probably because no index) then + * joining cal_range_view with real dates table (with index). + * * @param int $start * @param int $end * @param array $_where =null @@ -848,7 +851,8 @@ class calendar_so // dates table join only needed to enum recuring events, we use a time-range limited view here too if ($params['enum_recuring']) { - $join = "JOIN ".$this->dates_range_view($start, $end, null, $filter == 'everything' ? null : $filter == 'deleted'). + $join = "JOIN ".$this->dates_table. // using dates_table direct seems quicker then an other view + //$this->dates_range_view($start, $end, null, $filter == 'everything' ? null : $filter == 'deleted'). " ON $this->cal_table.cal_id=$this->dates_table.cal_id ".$join; } From 7306abcf5479d605be128d43b3d7893f7855cf22 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 08:46:43 +0000 Subject: [PATCH 13/43] * Calendar: fixed week 13 was skiped (due to daylight saving change) when using week navigation, added propper header for multiple week view --- calendar/inc/class.calendar_uiviews.inc.php | 27 +++++---------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php index 4d9dcddc2d..51578d3b20 100644 --- a/calendar/inc/class.calendar_uiviews.inc.php +++ b/calendar/inc/class.calendar_uiviews.inc.php @@ -655,16 +655,18 @@ class calendar_uiviews extends calendar_ui $this->first = $this->datetime->get_weekday_start($this->year,$this->month,$this->day); $this->last = strtotime("+$weeks weeks",$this->first) - 1; $weekNavH = "$weeks weeks"; + $navHeader = lang('Week').' '.$this->week_number($this->first).' - '.$this->week_number($this->last).': '. + $this->bo->long_date($this->first,$this->last); } else { $this->_week_align_month($this->first,$this->last); $weekNavH = "1 month"; + $navHeader = lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; } if ($this->debug > 0) $this->bo->debug_message('uiviews::month(%1) date=%2: first=%3, last=%4',False,$weeks,$this->date,$this->bo->date2string($this->first),$this->bo->date2string($this->last)); - $GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; - $navHeader = lang(adodb_date('F',$this->bo->date2ts($this->date))).' '.$this->year; + $GLOBALS['egw_info']['flags']['app_header'] .= ': '.$navHeader; $days =& $this->bo->search(array( 'start' => $this->first, @@ -842,32 +844,15 @@ class calendar_uiviews extends calendar_ui $navHeader = lang('Week').' '.$this->week_number($this->first).': '.$this->bo->long_date($this->first,$this->last); } - # temporarly disabled, because it collides with the title for the website - # - # // add navigation for previous and next - # // prev. week - # $GLOBALS['egw_info']['flags']['app_header'] = html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array( - # 'menuaction' => $this->view_menuaction, - # 'date' => date('Ymd',$this->first-$days*DAY_s), - # )) . ' <b>'.$GLOBALS['egw_info']['flags']['app_header']; - # // next week - # $GLOBALS['egw_info']['flags']['app_header'] .= '</b> '.html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array( - # 'menuaction' => $this->view_menuaction, - # 'date' => date('Ymd',$this->last+$days*DAY_s), - # )); - # - # $class = $class == 'row_on' ? 'th' : 'row_on'; - //echo "<p>weekdaystarts='".$this->cal_prefs['weekdaystarts']."', get_weekday_start($this->year,$this->month,$this->day)=".date('l Y-m-d',$wd_start).", first=".date('l Y-m-d',$this->first)."</p>\n"; - $navHeader = '<div class="calendar_calWeek calendar_calWeekNavHeader">' .html::a_href(html::image('phpgwapi','left',lang('previous'),$options=' alt="<<"'),array( 'menuaction' => $this->view_menuaction, - 'date' => date('Ymd',$this->first-$days*DAY_s), + 'date' => date('Ymd', strtotime("-$days days",$this->first)), )). '<span>'.$navHeader; $navHeader = $navHeader.'</span>'.html::a_href(html::image('phpgwapi','right',lang('next'),$options=' alt=">>"'),array( 'menuaction' => $this->view_menuaction, - 'date' => date('Ymd',$this->last+$days*DAY_s), + 'date' => date('Ymd', strtotime("+$days days",$this->last)), )).'</div>'; $merge = $this->merge(); From 74c771a2c9a5fd4a11ab68320827af52fffc56a1 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Wed, 18 Feb 2015 09:30:52 +0000 Subject: [PATCH 14/43] Fix calendar print template --- calendar/templates/default/print.xet | 148 +++++++++++++-------------- 1 file changed, 73 insertions(+), 75 deletions(-) diff --git a/calendar/templates/default/print.xet b/calendar/templates/default/print.xet index c14fbc1873..190ccac382 100644 --- a/calendar/templates/default/print.xet +++ b/calendar/templates/default/print.xet @@ -54,82 +54,80 @@ </grid> </template> <template id="calendar.print" template="" lang="" group="0" version="1.6.001"> - <hbox options="0,0"> - <grid width="100%" height="200"> - <columns> - <column width="95"/> - <column/> - </columns> - <rows> - <row> - <hbox> - <image src="print" onclick="window.print();" class="calendar_print_button"/> - <appicon class="calendar_print_appicon"/> - </hbox> - </row> - <row class="th" height="28"> - <description value="Title" class="bold" options="bold"/> - <textbox id="title" size="80" maxlength="255" readonly="true" span="all" class="bold"/> - </row> - <row class="row"> - <description width="95" options=",,,start" value="Start"/> - <date-time id="start" readonly="true"/> - </row> - <row class="row"> - <description width="0" options=",,,whole_day" value="whole day"/> - <checkbox id="whole_day" options=",, ," statustext="Event will occupy the whole day" readonly="true"/> - </row> - <row class="row"> - <description width="0" options=",,,duration" value="Duration"/> - <hbox options="0,0"> - <menulist> - <menupopup no_lang="1" onchange="set_style_by_class('table','end_hide','visibility',this.value == '' ? 'visible' : 'hidden'); if (this.value == '') document.getElementById(form::name('end[str]')).value = document.getElementById(form::name('start[str]')).value;" id="duration" options="Use end date" statustext="Duration of the meeting" readonly="true"/> - </menulist> - <date-time id="end" class="end_hide" readonly="true"/> - </hbox> - </row> - <row class="row"> - <description options=",,,location" value="Location" width="0"/> - <textbox maxlength="255" id="location" class="calendar_inputFullWidth" readonly="true"/> - </row> - <row class="row"> - <description options=",,,priority" value="Priority" width="0"/> + <grid width="100%" height="200"> + <columns> + <column width="95"/> + <column/> + </columns> + <rows> + <row> + <hbox> + <image src="print" onclick="window.print();" class="calendar_print_button"/> + <appicon class="calendar_print_appicon"/> + </hbox> + </row> + <row class="th" height="28"> + <description value="Title" class="bold" options="bold"/> + <textbox id="title" size="80" maxlength="255" readonly="true" span="all" class="bold"/> + </row> + <row class="row"> + <description width="95" options=",,,start" value="Start"/> + <date-time id="start" readonly="true"/> + </row> + <row class="row"> + <description width="0" options=",,,whole_day" value="whole day"/> + <checkbox id="whole_day" options=",, ," statustext="Event will occupy the whole day" readonly="true"/> + </row> + <row class="row"> + <description width="0" options=",,,duration" value="Duration"/> + <hbox options="0,0"> <menulist> - <menupopup type="select-priority" id="priority" readonly="true"/> + <menupopup no_lang="1" onchange="set_style_by_class('table','end_hide','visibility',this.value == '' ? 'visible' : 'hidden'); if (this.value == '') document.getElementById(form::name('end[str]')).value = document.getElementById(form::name('start[str]')).value;" id="duration" options="Use end date" statustext="Duration of the meeting" readonly="true"/> </menulist> - </row> - <row class="row"> - <description value="Non blocking" width="0"/> - <checkbox id="non_blocking" options="1,0, ," statustext="A non blocking event will not conflict with other events" readonly="true"/> - </row> - <row class="row"> - <description value="Private"/> - <checkbox id="public" options="0,1" readonly="true"/> - </row> - <row class="row calendar_print_cat"> - <description value="Categories"/> - <menulist> - <menupopup type="select-cat" id="category" readonly="true"/> - </menulist> - </row> - <row valign="top"> - <description value="Description"/> - <textbox multiline="true" id="description" readonly="true"/> - </row> - <row class="th"> - <description value="custom fields" span="all"/> - </row> - <row> - <customfields span="all" readonly="true"/> - </row> - <row> - <template id="calendar.print.participants" span="all"/> - </row> - <row> - <template span="all" id="calendar.print.links"/> - </row> - </rows> - </grid> - </hbox> + <date-time id="end" class="end_hide" readonly="true"/> + </hbox> + </row> + <row class="row"> + <description options=",,,location" value="Location" width="0"/> + <textbox maxlength="255" id="location" class="calendar_inputFullWidth" readonly="true"/> + </row> + <row class="row"> + <description options=",,,priority" value="Priority" width="0"/> + <menulist> + <menupopup type="select-priority" id="priority" readonly="true"/> + </menulist> + </row> + <row class="row"> + <description value="Non blocking" width="0"/> + <checkbox id="non_blocking" options="1,0, ," statustext="A non blocking event will not conflict with other events" readonly="true"/> + </row> + <row class="row"> + <description value="Private"/> + <checkbox id="public" options="0,1" readonly="true"/> + </row> + <row class="row calendar_print_cat"> + <description value="Categories"/> + <menulist> + <menupopup type="select-cat" id="category" readonly="true"/> + </menulist> + </row> + <row valign="top"> + <description value="Description"/> + <textbox multiline="true" id="description" readonly="true"/> + </row> + <row class="th"> + <description value="custom fields" span="all"/> + </row> + <row> + <customfields span="all" readonly="true"/> + </row> + <row> + <template id="calendar.print.participants" span="all"/> + </row> + <row> + <template span="all" id="calendar.print.links"/> + </row> + </rows> + </grid> </template> </overlay> From 343bffd9024ac52126ec8f6677bec98472018a64 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Wed, 18 Feb 2015 09:44:25 +0000 Subject: [PATCH 15/43] No need to submit after the print is triggerd --- calendar/js/app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/calendar/js/app.js b/calendar/js/app.js index 3be3c0bf85..47272a6235 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -811,7 +811,6 @@ app.classes.calendar = AppJS.extend( { case 'print': this.egw.open_link('calendar.calendar_uiforms.edit&cal_id='+id+'&print=1','_blank','700x700'); - this.et2._inst.submit(); break; case 'mail': this.egw.json('calendar.calendar_uiforms.ajax_custom_mail', [event, !event['id'], false],null,null,null,null).sendRequest(); From 0413898ce403a3ef2e1cac32e67ba274ef36e393 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 10:10:10 +0000 Subject: [PATCH 16/43] * Mail: composed mails saved as draft contains again attachments, drafts created by autosaving every 2 minutes do not for performance reasons --- mail/inc/class.mail_compose.inc.php | 29 +++++++++++++++++------------ mail/js/app.js | 19 ++++++++++--------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index 15cb345903..d51bb3f772 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -2142,10 +2142,11 @@ class mail_compose * @param egw_mailer $_mailObject * @param array $_formData * @param array $_identity - * @param boolean $_send =false true: create to send message: false: create to save as draft + * @param boolean $_autosaving =false true: autosaving, false: save-as-draft or send */ - function createMessage(egw_mailer $_mailObject, array $_formData, array $_identity, $_send=false) + function createMessage(egw_mailer $_mailObject, array $_formData, array $_identity, $_autosaving=false) { + //error_log(__METHOD__."(, formDate[filemode]=$_formData[filemode], _autosaving=".array2string($_autosaving).') '.function_backtrace()); $mail_bo = $this->mail_bo; $activeMailProfile = emailadmin_account::read($this->mail_bo->profileID); @@ -2233,7 +2234,7 @@ class mail_compose $signature = mail_bo::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id'))); } */ - if ($_formData['attachments'] && $_formData['filemode'] != egw_sharing::ATTACH && $_send) + if ($_formData['attachments'] && $_formData['filemode'] != egw_sharing::ATTACH && !$_autosaving) { $attachment_links = $this->getAttachmentLinks($_formData['attachments'], $_formData['filemode'], $_formData['mimeType'] == 'html', @@ -2267,7 +2268,7 @@ class mail_compose $_mailObject->setBody($this->convertHTMLToText($body, true, true)); } // convert URL Images to inline images - if possible - if ($_send) mail_bo::processURL2InlineImages($_mailObject, $body); + if (!$_autosaving) mail_bo::processURL2InlineImages($_mailObject, $body); if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false) { $body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body); @@ -2343,7 +2344,8 @@ class mail_compose break; } } - elseif ($_formData['filemode'] == egw_sharing::ATTACH) + // attach files not for autosaving + elseif ($_formData['filemode'] == egw_sharing::ATTACH && !$_autosaving) { if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs') { @@ -2431,9 +2433,11 @@ class mail_compose * Save compose mail as draft * * @param array $content content sent from client-side + * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]' */ - public function ajax_saveAsDraft ($content) + public function ajax_saveAsDraft ($content, $action='button[saveAsDraft]') { + //error_log(__METHOD__."(, action=$action)"); $response = egw_json_response::get(); $success = true; @@ -2455,7 +2459,7 @@ class mail_compose $folder = $this->mail_bo->getDraftFolder(); $this->mail_bo->reopen($folder); $status = $this->mail_bo->getFolderStatus($folder); - if (($messageUid = $this->saveAsDraft($formData,$folder))) + if (($messageUid = $this->saveAsDraft($formData, $folder, $action))) { // saving as draft, does not mean closing the message $messageUid = ($messageUid===true ? $status['uidnext'] : $messageUid); @@ -2538,13 +2542,14 @@ class mail_compose /** * Save message as draft to specific folder * - * @param type $_formData content - * @param type $savingDestination destination folder + * @param array $_formData content + * @param string &$savingDestination ='' destination folder + * @param string $action ='button[saveAsDraft]' 'autosaving', 'button[saveAsDraft]' or 'button[saveAsDraftAndPrint]' * @return boolean return messageUID| false due to an error */ - function saveAsDraft($_formData, &$savingDestination='') + function saveAsDraft($_formData, &$savingDestination='', $action='button[saveAsDraft]') { - //error_log(__METHOD__.__LINE__); + //error_log(__METHOD__."(..., $savingDestination, action=$action)"); $mail_bo = $this->mail_bo; $mail = new egw_mailer($this->mail_bo->profileID); @@ -2568,7 +2573,7 @@ class mail_compose $flags = '\\Seen \\Draft'; - $this->createMessage($mail, $_formData, $identity); + $this->createMessage($mail, $_formData, $identity, $action === 'autosaving'); // folder list as Customheader if (!empty($this->sessionData['folder'])) diff --git a/mail/js/app.js b/mail/js/app.js index bf590420eb..cf7755be72 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -176,7 +176,7 @@ app.classes.mail = AppJS.extend( // Prepare display dialog for printing // copies iframe content to a DIV, as iframe causes // trouble for multipage printing - jQuery('#mail-display_mailDisplayBodySrc').on('load', function(){self.mail_prepare_print()}); + jQuery('#mail-display_mailDisplayBodySrc').on('load', function(){self.mail_prepare_print();}); this.mail_isMainWindow = false; this.mail_display(); @@ -199,7 +199,7 @@ app.classes.mail = AppJS.extend( // Set autosaving interval to 2 minutes for compose message this.W_INTERVALS.push(window.setInterval(function (){ - that.saveAsDraft(null,that.et2.getWidgetById('button[saveAsDraft]'),'autosaving'); + that.saveAsDraft(null, 'autosaving'); }, 120000)); /* Control focus actions on subject to handle expanders properly.*/ @@ -3157,20 +3157,21 @@ app.classes.mail = AppJS.extend( * Save as Draft (VFS) * -handel both actions save as draft and save as draft and print * - * @param {egw object} _egw - * @param {widget object} _widget - * @param {string} _action autosaving trigger action + * @param {egwAction} _egw_action + * @param {array|string} _action string "autosaving", if that triggered the action */ saveAsDraft: function(_egw_action, _action) { //this.et2_obj.submit(); var content = this.et2.getArrayMgr('content').data; - if (_egw_action ) + var action = _action; + if (_egw_action && _action !== 'autosaving') { - var action = _action == 'autosaving'?_action: _egw_action.id; + action = _egw_action.id; } - var widgets = ['from','to','cc','bcc','subject','folder','replyto','mailaccount', 'mail_htmltext', 'mail_plaintext', 'lastDrafted']; + var widgets = ['from','to','cc','bcc','subject','folder','replyto','mailaccount', + 'mail_htmltext', 'mail_plaintext', 'lastDrafted', 'filemode', 'expiration', 'password']; var widget = {}; for (var index in widgets) { @@ -3183,7 +3184,7 @@ app.classes.mail = AppJS.extend( var self = this; if (content) { - this.egw.json('mail.mail_compose.ajax_saveAsDraft',[content],function(_data){ + this.egw.json('mail.mail_compose.ajax_saveAsDraft',[content, action],function(_data){ self.savingDraft_response(_data,action); }).sendRequest(true); } From 4078a48fb8e24040c798e44798a0c4451c490832 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 11:17:28 +0000 Subject: [PATCH 17/43] fixed send mail does not contain attachments --- mail/inc/class.mail_compose.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index d51bb3f772..db4076ead3 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -2703,7 +2703,7 @@ class mail_compose //error_log($this->sessionData['mailaccount']); //error_log(__METHOD__.__LINE__.':'.array2string($this->sessionData['mailidentity']).'->'.array2string($identity)); // create the messages - $this->createMessage($mail, $_formData, $identity, true); + $this->createMessage($mail, $_formData, $identity); // remember the identity if ($_formData['to_infolog'] == 'on' || $_formData['to_tracker'] == 'on') $fromAddress = $mail->FromName.($mail->FromName?' <':'').$mail->From.($mail->FromName?'>':''); #print "<pre>". $mail->getMessageHeader() ."</pre><hr><br>"; From 426f9e0f8451f6fff5d4cd8d62a2755678a5a0f1 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 11:40:26 +0000 Subject: [PATCH 18/43] * Admin: add a description to stock groups Admins, Default and NoGroup, allow to edit that description for LDAP and ADS --- admin/inc/class.admin_ui.inc.php | 1 + phpgwapi/inc/class.accounts.inc.php | 44 ++++++++++++++++++++++++ phpgwapi/inc/class.accounts_ads.inc.php | 4 --- phpgwapi/inc/class.accounts_ldap.inc.php | 4 ++- phpgwapi/lang/egw_de.lang | 3 ++ phpgwapi/lang/egw_en.lang | 3 ++ 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/admin/inc/class.admin_ui.inc.php b/admin/inc/class.admin_ui.inc.php index c7a2cf84b9..8ce6abb20f 100644 --- a/admin/inc/class.admin_ui.inc.php +++ b/admin/inc/class.admin_ui.inc.php @@ -426,6 +426,7 @@ class admin_ui { $tree['item'][] = self::fix_userdata(array( 'text' => $group['account_lid'], + 'tooltip' => $group['account_description'], 'id' => $root.'/'.$group['account_id'], )); } diff --git a/phpgwapi/inc/class.accounts.inc.php b/phpgwapi/inc/class.accounts.inc.php index b5c6c8e42b..61839c96a4 100644 --- a/phpgwapi/inc/class.accounts.inc.php +++ b/phpgwapi/inc/class.accounts.inc.php @@ -335,6 +335,17 @@ class accounts else { $account_search[$serial]['data'] = $this->backend->search($param); + if ($param['type'] !== 'accounts') + { + foreach($account_search[$serial]['data'] as &$account) + { + // add default description for Admins and Default group + if ($account['account_type'] === 'g' && empty($account['account_description'])) + { + self::add_default_group_description($account); + } + } + } $account_search[$serial]['total'] = $this->total = $this->backend->total; } //echo "<p>accounts::search(".array2string(unserialize($serial)).")= returning ".count($account_search[$serial]['data'])." of $this->total entries<pre>".print_r($account_search[$serial]['data'],True)."</pre>\n"; @@ -429,6 +440,12 @@ class accounts $data = self::cache_read($id); + // add default description for Admins and Default group + if ($data['account_type'] === 'g' && empty($data['account_description'])) + { + self::add_default_group_description($data); + } + if ($set_depricated_names && $data) { foreach($this->depricated_names as $name) @@ -469,6 +486,28 @@ class accounts return json_encode($account); } + /** + * Add a default description for stock groups: Admins, Default, NoGroup + * + * @param array &$data + */ + protected static function add_default_group_description(array &$data) + { + switch($data['account_lid']) + { + case 'Default': + $data['account_description'] = lang('EGroupware all users group, do NOT delete'); + break; + case 'Admins': + $data['account_description'] = lang('EGroupware administrators group, do NOT delete'); + break; + case 'NoGroup': + $data['account_description'] = lang('EGroupware anonymous users group, do NOT delete'); + break; + } + error_log(__METHOD__."(".array2string($data).")"); + } + /** * Saves / adds the data of one account * @@ -490,6 +529,11 @@ class accounts } } } + // add default description for Admins and Default group + if ($data['account_type'] === 'g' && empty($data['account_description'])) + { + self::add_default_group_description($data); + } if (($id = $this->backend->save($data)) && $data['account_type'] != 'g') { // if we are not on a pure LDAP system, we have to write the account-date via the contacts class now diff --git a/phpgwapi/inc/class.accounts_ads.inc.php b/phpgwapi/inc/class.accounts_ads.inc.php index c454f73e8c..736b43eec7 100644 --- a/phpgwapi/inc/class.accounts_ads.inc.php +++ b/phpgwapi/inc/class.accounts_ads.inc.php @@ -578,10 +578,6 @@ class accounts_ads protected function _save_group(array &$data, array $old=null) { //error_log(__METHOD__.'('.array2string($data).', old='.array2string($old).')'); - if (empty($data['account_description'])) - { - $data['account_description'] = 'EGroupware '.lang('Group').' '.$data['account_lid']; - } if (!$old) // new entry { diff --git a/phpgwapi/inc/class.accounts_ldap.inc.php b/phpgwapi/inc/class.accounts_ldap.inc.php index 2e90d5b048..ee9d23e105 100644 --- a/phpgwapi/inc/class.accounts_ldap.inc.php +++ b/phpgwapi/inc/class.accounts_ldap.inc.php @@ -446,7 +446,7 @@ class accounts_ldap } } $sri = ldap_search($this->ds, $this->group_context,'(&(objectClass=posixGroup)(gidnumber=' . abs($account_id).'))', - array('dn', 'gidnumber', 'cn', 'objectclass', static::MAIL_ATTR, 'memberuid')); + array('dn', 'gidnumber', 'cn', 'objectclass', static::MAIL_ATTR, 'memberuid', 'description')); $ldap_data = ldap_get_entries($this->ds, $sri); if (!$ldap_data['count']) @@ -467,6 +467,7 @@ class accounts_ldap 'objectclass' => array_map('strtolower', $data['objectclass']), 'account_email' => $data[static::MAIL_ATTR][0], 'members' => array(), + 'account_description' => $data['description'][0], ); if (isset($data['memberuid'])) @@ -554,6 +555,7 @@ class accounts_ldap { $to_write['gidnumber'] = abs($data['account_id']); $to_write['cn'] = $data['account_lid']; + $to_write['description'] = $data['account_description']; return $to_write; } diff --git a/phpgwapi/lang/egw_de.lang b/phpgwapi/lang/egw_de.lang index dfb89595d3..31ed10e904 100644 --- a/phpgwapi/lang/egw_de.lang +++ b/phpgwapi/lang/egw_de.lang @@ -277,6 +277,9 @@ edit %1 category for common de %1 Kategorie editieren für edit categories common de Kategorien editieren edit category common de Kategorie editieren egroupware common de EGroupware +egroupware administrators group, do not delete common de EGroupware Gruppe für Administratoren, NICHT löschen +egroupware all users group, do not delete common de EGroupware Gruppe für alle Benutzer, NICHT löschen +egroupware anonymous users group, do not delete common de EGroupware Gruppe für anonymen Benutzer, nicht löschen egroupware api version common de EGroupware API Version egroupware maintenance update %1 available common de EGroupware Fehlerbehebungsupdate %1 ist verfügbar egroupware security update %1 needs to be installed! common de EGroupware Sicherheitsupdate %1 muss installiert werden! diff --git a/phpgwapi/lang/egw_en.lang b/phpgwapi/lang/egw_en.lang index 0efc51cfac..88d404543b 100644 --- a/phpgwapi/lang/egw_en.lang +++ b/phpgwapi/lang/egw_en.lang @@ -277,6 +277,9 @@ edit %1 category for common en Edit %1 category for edit categories common en Edit categories edit category common en Edit category egroupware common en EGroupware +egroupware administrators group, do not delete common en EGroupware administrators group, do NOT delete +egroupware all users group, do not delete common en EGroupware all users group, do NOT delete +egroupware anonymous users group, do not delete common en EGroupware anonymous users group, do NOT delete egroupware api version common en EGroupware API version egroupware maintenance update %1 available common en EGroupware maintenance update %1 available egroupware security update %1 needs to be installed! common en EGroupware security update %1 needs to be installed! From 55dd493e5d705b3bd31f8c50aec071a308531eb3 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Wed, 18 Feb 2015 12:51:23 +0000 Subject: [PATCH 19/43] Make custom theme colors for login page supports Cross-Browser styles --- pixelegg/inc/class.pixelegg_framework.inc.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pixelegg/inc/class.pixelegg_framework.inc.php b/pixelegg/inc/class.pixelegg_framework.inc.php index 9793555879..b4e550cd06 100755 --- a/pixelegg/inc/class.pixelegg_framework.inc.php +++ b/pixelegg/inc/class.pixelegg_framework.inc.php @@ -132,11 +132,17 @@ div#egw_fw_header, div.egw_fw_ui_category:hover,#loginMainDiv,#loginMainDiv #div /*Login background*/ #loginMainDiv #divAppIconBar #divLogo img[src$='svg'] { background-image: -webkit-linear-gradient(top, $color, $color); + background-image: -moz-linear-gradient(top, $color, $color); + background-image: -o-linear-gradient(top,$color, $color); + background-image: linear-gradient(to bottom, $color, $color); } /*Center box in login page*/ #loginMainDiv div#centerBox { background-image: -webkit-linear-gradient(top,$color_hex_dark,$color_hex_darker); + background-image: -moz-linear-gradient(top,$color_hex_dark,$color_hex_darker); + background-image: -o-linear-gradient(top,$color_hex_dark,$color_hex_darker); + background-image: linear-gradient(to bottom, $color_hex_dark,$color_hex_darker); border-top: solid 1px $color_hex_darker; border-left: solid 1px $color_hex_darker; border-right: solid 1px $color_hex_darker; From c4da9ba8d9e5036931cfc956d0f80de39435fdbf Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 13:45:53 +0000 Subject: [PATCH 20/43] remove permanent error_log --- phpgwapi/inc/class.accounts.inc.php | 1 - 1 file changed, 1 deletion(-) diff --git a/phpgwapi/inc/class.accounts.inc.php b/phpgwapi/inc/class.accounts.inc.php index 61839c96a4..b92441c3b3 100644 --- a/phpgwapi/inc/class.accounts.inc.php +++ b/phpgwapi/inc/class.accounts.inc.php @@ -505,7 +505,6 @@ class accounts $data['account_description'] = lang('EGroupware anonymous users group, do NOT delete'); break; } - error_log(__METHOD__."(".array2string($data).")"); } /** From 5ea443a889ccf13e0614fde6389c69c0a14381ca Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 17:00:24 +0000 Subject: [PATCH 21/43] copy 14.2 changelog to trunk to satisfy update checker --- doc/rpm-build/debian.changes | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/rpm-build/debian.changes b/doc/rpm-build/debian.changes index 59c1bf26d5..47dab6a087 100644 --- a/doc/rpm-build/debian.changes +++ b/doc/rpm-build/debian.changes @@ -1,3 +1,25 @@ +egroupware-epl (14.2.20150218-1) hardy; urgency=low + + * THIS RELEASE CONTAINS IMPORTANT SECURITY FIXES, PLEASE UPDATE ASAP + * Critical: Unauthenticated insecure PHP object deseralization allowing arbitrary code execution + * High: Cross site scripting by circumventing content security policy + * High: Unauthenticated local file access read and write under MS Windows + * credits to Andreas Fischer (http://www.andreasfischer.net/) and Lukas Reschke (http://www.statuscode.ch) + * Mail: composed mails saved as draft contains again attachments, drafts created by autosaving every 2 minutes do not for performance reasons + * Tracker: fixed memberships were not taken into account when opening private tickets or reading restricted comments + * InfoLog: new context menu: View parent with children + * Univention: mail app was not working for in UCS created users + * Admin: add a description to stock groups Admins, Default and NoGroup, allow to edit that description for LDAP and ADS + * PostgreSQL: fixed not working new installation + * ProjectManager/PostgreSQL: fix SQL error in project-list caused by new resources column + * InfoLog/Addressbook: refresh CRM view if InfoLog was edited without having InfoLog tab open + * Calendar: fixed week 13 was skiped (due to daylight saving change) when using week navigation, added propper header for multiple week view + * SiteMgr: fix not displayed template preferences + * SiteMgr: fix accordeon to work in 14.x + * Mobile theme: Login page style improvement + + -- Ralf Becker <rb@stylite.de> Wed, 18 Feb 2015 12:56:48 +0100 + egroupware-epl (14.2.20150212-1) hardy; urgency=low * 14.2 final release From 529d154514aabcf2707ae145257d46fcf6ef3d87 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Wed, 18 Feb 2015 17:03:21 +0000 Subject: [PATCH 22/43] * Add ability to select columns to be displayed in gantt chart --- etemplate/js/et2_widget_gantt.js | 178 ++++++++++++++++++++- etemplate/templates/default/etemplate2.css | 22 +++ 2 files changed, 198 insertions(+), 2 deletions(-) diff --git a/etemplate/js/et2_widget_gantt.js b/etemplate/js/et2_widget_gantt.js index 60e86b1ea5..82d9183774 100644 --- a/etemplate/js/et2_widget_gantt.js +++ b/etemplate/js/et2_widget_gantt.js @@ -57,6 +57,14 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], "default": "minute", "description": "The unit for task duration values. One of minute, hour, week, year." }, + columns: { + name: "Columns", + type: "any", + default: [ + {name: "text", label: egw.lang('Title'), tree: true, width: '*'} + ], + description: "Columns for the grid portion of the gantt chart. An array of objects with keys name, label, etc. See http://docs.dhtmlx.com/gantt/api__gantt_columns_config.html" + }, value: {type: 'any'}, needed: {ignore: true}, onfocus: {ignore: true}, @@ -76,6 +84,7 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], show_progress: true, order_branch: false, min_column_width: 30, + min_grid_column_width: 30, task_height: 25, fit_tasks: true, autosize: '', @@ -177,6 +186,10 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], { this.set_value(this.options.value); } + if(this.options.columns) + { + this.set_columns(this.options.columns); + } // Update start & end dates with chart values for consistency if(start_date && this.options.value.data && this.options.value.data.length) @@ -244,6 +257,81 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], } }, + /** + * Set the columns for the grid (left) portion + * + * @param {Object[]} columns - A list of columns + * @param {string} columns[].name The column's ID + * @param {string} columns[].label The title for the column header + * @param {string} columns[].width Width of the column + * + * @see http://docs.dhtmlx.com/gantt/api__gantt_columns_config.html for full options + */ + set_columns: function(columns) + { + this.gantt_config.columns = columns; + + var displayed_columns = []; + var gantt_widget = this; + + // Make sure there's enough room for them all + var width = 0; + for(var col in columns) + { + // Preserve original width, gantt will resize column to fit + if(!columns[col]._width) + { + columns[col]._width = columns[col].width; + } + columns[col].width = columns[col]._width; + if(!columns[col].template) + { + // Use an et2 widget to render the column value, if one was provided + // otherwise, just display the value + columns[col].template = function(task) { + var value = typeof task[this.name] == 'undefined'||task[this.name] == null ? '':task[this.name]; + + // No value, but there's a project title. Try reading the project value. + if(!value && this.name.indexOf('pe_') == 0 && task.pm_title) + { + var pm_col = this.name.replace('pe_','pm_'); + value = typeof task[pm_col] == 'undefined' || task[pm_col] == null ? '':task[pm_col]; + } + if(this.widget && typeof this.widget == 'string') + { + this.widget = et2_createWidget(this.widget, {readonly:true}, gantt_widget); + } + if (this.widget) + { + this.widget.set_value(value); + value = $j(this.widget.getDOMNode()).html(); + } + return value; + }; + } + + // Actual hiding is available in the pro version of gantt chart + if(!columns[col].hide) + { + displayed_columns.push(columns[col]); + width += parseInt(columns[col]._width) || 0; + } + + } + // Add in add column + displayed_columns.push({name: 'add', width: 26}); + + if(width != this.gantt_config.grid_width || typeof this.gantt_config.grid_width == 'undefined') + { + this.gantt_config.grid_width = Math.min(Math.max(200, width), this.htmlNode.width()); + } + + if(this.gantt == null) return; + this.gantt.config.columns = displayed_columns; + this.gantt.config.grid_width = this.gantt_config.grid_width; + this.gantt.render(); + }, + /** * Sets the data to be displayed in the gantt chart. * @@ -700,11 +788,9 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], // Some crazy stuff make sure timing is OK to scroll after re-render // TODO: Make this more consistently go to where you click var id = gantt_widget.gantt.attachEvent("onGanttRender", function() { - console.log('Render'); gantt_widget.gantt.detachEvent(id); gantt_widget.gantt.scrollTo(parseInt($j('.gantt_task_scale',gantt_widget.gantt_node).width() *current_position),0); window.setTimeout(function() { - console.log("Scroll to"); gantt_widget.gantt.scrollTo(parseInt($j('.gantt_task_scale',gantt_widget.gantt_node).width() *current_position),0); },100); }); @@ -729,6 +815,12 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], */ }); + this.gantt.attachEvent("onGridHeaderClick", function(column_name, e) { + if(column_name === "add") + { + gantt_widget._column_selection(e); + } + }); this.gantt.attachEvent("onContextMenu",function(taskId, linkId, e) { if(gantt_widget.options.readonly) return false; if(taskId) @@ -901,6 +993,88 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], }, this, et2_inputWidget); }, + /** + * Start UI for selecting among defined columns + */ + _column_selection: function(e) + { + var self = this; + var columns = []; + var columns_selected = []; + for (var i = 0; i < this.gantt_config.columns.length; i++) + { + var col = this.gantt_config.columns[i]; + columns.push({ + value: col.name, + label: col.label + }) + if(!col.hide) + { + columns_selected.push(col.name); + } + } + + // Build the popup + if(!this.selectPopup) + { + var select = et2_createWidget("select", { + multiple: true, + rows: 8, + empty_label:this.egw().lang("select columns"), + selected_first: false + }, this); + select.set_select_options(columns); + select.set_value(columns_selected); + + var okButton = et2_createWidget("buttononly", {}, this); + okButton.set_label(this.egw().lang("ok")); + okButton.onclick = function() { + // Update columns + var value = select.getValue(); + for (var i = 0; i < columns.length; i++) + { + self.gantt_config.columns[i].hide = value.indexOf(columns[i].value) < 0 ; + } + self.set_columns(self.gantt_config.columns); + + // Hide popup + self.selectPopup.toggle(); + self.selectPopup.remove(); + self.selectPopup = null; + $j('body').off('click.gantt'); + }; + + var cancelButton = et2_createWidget("buttononly", {}, this); + cancelButton.set_label(this.egw().lang("cancel")); + cancelButton.onclick = function() { + self.selectPopup.toggle(); + self.selectPopup.remove(); + self.selectPopup = null; + $j('body').off('click.gantt'); + }; + + // Create and add popup + this.selectPopup = jQuery(document.createElement("div")) + .addClass("colselection ui-dialog ui-widget-content") + .append(select.getDOMNode()) + .append(okButton.getDOMNode()) + .append(cancelButton.getDOMNode()) + .appendTo(this.getInstanceManager().DOMContainer); + // Bind so if you click elsewhere, it closes + window.setTimeout(function() {$j(document).one('mouseup.gantt', function(e){ + if(!self.selectPopup.is(e.target) && self.selectPopup.has(e.target).length === 0) + { + cancelButton.onclick(); + } + });},1); + } + else + { + this.selectPopup.toggle(); + } + this.selectPopup.position({my:'right top', at:'right bottom', of: e.target}); + }, + /** * Link the actions to the DOM nodes / widget bits. * Overridden to make the gantt chart a container, so it can't be selected. diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 3374d1291c..b1305da749 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -1623,6 +1623,28 @@ div.et2_toolbar_activeList h.ui-accordion-header { { overflow: visible; } +/* Style the gantt grid (left side) allowing 2 lines */ +.et2_gantt .gantt_grid_scale :not(.gantt_grid_head_add) { + white-space: normal; + line-height: 16px; + height: auto; +} +/* Column selector */ +.et2_gantt .gantt_grid_scale .gantt_grid_head_add { + background-image: url(images/selectcols.png); + padding: 0px; + margin: 0px; +} +.et2_gantt .gantt_grid_data .gantt_add { + display: none; + padding: 0px; + margin: 0px; +} +/* Display inline, since there's only 1 line*/ +.et2_gantt .gantt_grid_data li { + display: inline-block; + padding-right: 0.5ex; +} .et2_gantt .gantt_task_progress { text-align: left; From 5318ebdb210766b31cf7db23e5283b98ccd1578b Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Wed, 18 Feb 2015 17:20:50 +0000 Subject: [PATCH 23/43] - Fix JS error when selecting no columns - Fix not including selector width caused undesired column resizing --- etemplate/js/et2_widget_gantt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etemplate/js/et2_widget_gantt.js b/etemplate/js/et2_widget_gantt.js index 82d9183774..cdedef9780 100644 --- a/etemplate/js/et2_widget_gantt.js +++ b/etemplate/js/et2_widget_gantt.js @@ -320,6 +320,7 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], } // Add in add column displayed_columns.push({name: 'add', width: 26}); + width += 26; if(width != this.gantt_config.grid_width || typeof this.gantt_config.grid_width == 'undefined') { @@ -1030,7 +1031,7 @@ var et2_gantt = et2_inputWidget.extend([et2_IResizeable,et2_IInput], okButton.set_label(this.egw().lang("ok")); okButton.onclick = function() { // Update columns - var value = select.getValue(); + var value = select.getValue() || []; for (var i = 0; i < columns.length; i++) { self.gantt_config.columns[i].hide = value.indexOf(columns[i].value) < 0 ; From 63cfd63c67fad72534eefc3b99fc38e6af5d3aa0 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Wed, 18 Feb 2015 17:37:07 +0000 Subject: [PATCH 24/43] add group description for accounts in sql too --- phpgwapi/inc/class.accounts_sql.inc.php | 3 ++- phpgwapi/setup/setup.inc.php | 3 ++- phpgwapi/setup/tables_current.inc.php | 3 ++- phpgwapi/setup/tables_update.inc.php | 13 ++++++++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/phpgwapi/inc/class.accounts_sql.inc.php b/phpgwapi/inc/class.accounts_sql.inc.php index 5e98a802fb..ff318e2e89 100644 --- a/phpgwapi/inc/class.accounts_sql.inc.php +++ b/phpgwapi/inc/class.accounts_sql.inc.php @@ -508,7 +508,7 @@ class accounts_sql $accounts = array(); foreach((array) $GLOBALS['egw']->contacts->search($criteria, "1,n_given,n_family,email,id,created,modified,$this->table.account_id AS account_id", - $order,"account_lid,account_type,account_status,account_expires,account_primary_group", + $order,"account_lid,account_type,account_status,account_expires,account_primary_group,account_description", $wildcard,false,$query[0] == '!' ? 'AND' : 'OR', $param['offset'] ? array($param['start'], $param['offset']) : (is_null($param['start']) ? false : $param['start']), $filter,$this->contacts_join) as $contact) @@ -530,6 +530,7 @@ class accounts_sql // addressbook_bo::search() returns everything in user-time, need to convert to server-time 'account_created' => egw_time::user2server($contact['created']), 'account_modified' => egw_time::user2server($contact['modified']), + 'account_description' => $contact['account_description'], ); } } diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index 6a07aa9e5c..07188680a0 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -12,7 +12,7 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['title'] = 'EGroupware API'; -$setup_info['phpgwapi']['version'] = '14.2'; +$setup_info['phpgwapi']['version'] = '15.0.001'; $setup_info['phpgwapi']['versions']['current_header'] = '1.29'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -74,3 +74,4 @@ $setup_info['groupdav']['license'] = 'GPL'; $setup_info['groupdav']['hooks']['preferences'] = 'groupdav_hooks::menus'; $setup_info['groupdav']['hooks']['settings'] = 'groupdav_hooks::settings'; + diff --git a/phpgwapi/setup/tables_current.inc.php b/phpgwapi/setup/tables_current.inc.php index e9fda853d1..efef79ecf7 100644 --- a/phpgwapi/setup/tables_current.inc.php +++ b/phpgwapi/setup/tables_current.inc.php @@ -63,7 +63,8 @@ $phpgw_baseline = array( 'account_status' => array('type' => 'char','precision' => '1','nullable' => False,'default' => 'A'), 'account_expires' => array('type' => 'int','precision' => '4'), 'account_type' => array('type' => 'char','precision' => '1'), - 'account_primary_group' => array('type' => 'int','meta' => 'group','precision' => '4','nullable' => False,'default' => '0') + 'account_primary_group' => array('type' => 'int','meta' => 'group','precision' => '4','nullable' => False,'default' => '0'), + 'account_description' => array('type' => 'varchar','precision' => '255','comment' => 'group description') ), 'pk' => array('account_id'), 'fk' => array(), diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index 94b179344b..5dd9efaa4d 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -59,4 +59,15 @@ function phpgwapi_upgrade14_1_900() $GLOBALS['egw_setup']->add_acl('phpgwapi', 'anonymous', $anonymous); return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '14.2'; -} \ No newline at end of file +} +function phpgwapi_upgrade14_2() +{ + $GLOBALS['egw_setup']->oProc->AddColumn('egw_accounts','account_description',array( + 'type' => 'varchar', + 'precision' => '255', + 'comment' => 'group description' + )); + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '15.0.001'; +} + From 55d5bd98f83d95fc455722c0dca75051a0f12ee1 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Wed, 18 Feb 2015 18:23:35 +0000 Subject: [PATCH 25/43] Custom fields editing: - Fix length, rows & values fields were not properly enabled on first load - If label was not provided, use name --- admin/inc/class.customfields.inc.php | 4 ++++ admin/js/app.js | 18 ++++++++++++++++++ admin/templates/default/customfield_edit.xet | 5 +---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/admin/inc/class.customfields.inc.php b/admin/inc/class.customfields.inc.php index 875fe83932..17125a18d8 100644 --- a/admin/inc/class.customfields.inc.php +++ b/admin/inc/class.customfields.inc.php @@ -305,6 +305,10 @@ class customfields break; case 'save': case 'apply': + if(empty($content['cf_label'])) + { + $content['cf_label'] = $content['cf_name']; + } if (!empty($content['cf_values'])) { $values = array(); diff --git a/admin/js/app.js b/admin/js/app.js index d581c68979..3e0c42fb0c 100644 --- a/admin/js/app.js +++ b/admin/js/app.js @@ -100,6 +100,11 @@ app.classes.admin = AppJS.extend( case 'admin.categories.index': break; + case 'admin.customfield_edit': + // Load settings appropriate to currently set type + var widget = _et2.widgetContainer.getWidgetById('cf_type'); + this.cf_type_change(null,widget); + break; } }, @@ -784,5 +789,18 @@ app.classes.admin = AppJS.extend( } }; et2_dialog.show_dialog(delDialog_callBack,this.egw.lang("Are you sure you want to delete this category ?"),this.egw.lang("Delete"),{},_buttons,et2_dialog.WARNING_MESSAGE,null,'admin'); + }, + + /** + * Change handler for when you change the type of a custom field. + * It toggles options / attributes as appropriate. + */ + cf_type_change: function(e,widget) { + var root = widget.getRoot(); + var attributes = widget.getArrayMgr('content').getEntry('attributes['+widget.getValue()+']')||{}; + root.getWidgetById('cf_values').set_statustext(widget.egw().lang(widget.getArrayMgr('content').getEntry('options['+widget.getValue()+']'))||''); + root.getWidgetById('cf_len').set_disabled(!attributes.cf_len); + root.getWidgetById('cf_rows').set_disabled(!attributes.cf_rows); + root.getWidgetById('cf_values').set_disabled(!attributes.cf_values); } }); diff --git a/admin/templates/default/customfield_edit.xet b/admin/templates/default/customfield_edit.xet index 52422d251d..d1fa8f654a 100644 --- a/admin/templates/default/customfield_edit.xet +++ b/admin/templates/default/customfield_edit.xet @@ -29,10 +29,7 @@ </row> <row> <description value="Type of field"/> - <customfields-types statustext="Type of customfield" id="cf_type" class="et2_fullWidth" span="2" onchange="widget.getRoot().getWidgetById('cf_values').set_statustext(widget.egw().lang(widget.getArrayMgr('content').getEntry('options['+widget.getValue()+']'))||''); -widget.getRoot().getWidgetById('cf_len').set_disabled(!widget.getArrayMgr('content').getEntry('attributes['+widget.getValue()+'][cf_len]')); -widget.getRoot().getWidgetById('cf_rows').set_disabled(!widget.getArrayMgr('content').getEntry('attributes['+widget.getValue()+'][cf_rows]')); -widget.getRoot().getWidgetById('cf_values').set_disabled(!widget.getArrayMgr('content').getEntry('attributes['+widget.getValue()+'][cf_values]'));"/> + <customfields-types statustext="Type of customfield" id="cf_type" class="et2_fullWidth" span="2" onchange="app.admin.cf_type_change"/> <hbox span="2"> <description value="Required"/> <checkbox id="cf_needed"/> From 12b8dc1ed4d9b55d42cc812b834402024345910f Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Wed, 18 Feb 2015 23:02:35 +0000 Subject: [PATCH 26/43] Add label attribute --- etemplate/js/et2_widget_description.js | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/etemplate/js/et2_widget_description.js b/etemplate/js/et2_widget_description.js index 3838aaf414..028e65b44a 100644 --- a/etemplate/js/et2_widget_description.js +++ b/etemplate/js/et2_widget_description.js @@ -25,6 +25,13 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], { attributes: { + "label": { + "name": "Label", + "default": "", + "type": "string", + "description": "The label is displayed by default in front (for radiobuttons behind) each widget (if not empty). If you want to specify a different position, use a '%s' in the label, which gets replaced by the widget itself. Eg. '%s Name' to have the label Name behind a checkbox. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).", + "translate": true + }, "value": { "name": "Value", "type": "string", @@ -119,6 +126,63 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], } }, + set_label: function(_value) { + // Abort if ther was no change in the label + if (_value == this.label) + { + return; + } + + if (_value) + { + // Create the label container if it didn't exist yet + if (this._labelContainer == null) + { + this._labelContainer = $j(document.createElement("label")) + .addClass("et2_label"); + this.getSurroundings().insertDOMNode(this._labelContainer[0]); + } + + // Clear the label container. + this._labelContainer.empty(); + + // Create the placeholder element and set it + var ph = document.createElement("span"); + this.getSurroundings().setWidgetPlaceholder(ph); + + // Split the label at the "%s" + var parts = et2_csvSplit(_value, 2, "%s"); + + // Update the content of the label container + for (var i = 0; i < parts.length; i++) + { + if (parts[i]) + { + this._labelContainer.append(document.createTextNode(parts[i])); + } + if (i == 0) + { + this._labelContainer.append(ph); + } + } + } + else + { + // Delete the labelContainer from the surroundings object + if (this._labelContainer) + { + this.getSurroundings().removeDOMNode(this._labelContainer[0]); + } + this._labelContainer = null; + } + + // Update the surroundings in order to reflect the change in the label + this.getSurroundings().update(); + + // Copy the given value + this.label = _value; + }, + set_value: function(_value) { if (!_value) _value = ""; if (!this.options.no_lang) _value = this.egw().lang(_value); From b1b1269e0e3e06999f6e0841b9600846621bc2fc Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Wed, 18 Feb 2015 23:04:59 +0000 Subject: [PATCH 27/43] Entry type / transformer changes - better support for entry-types in nextmatch-customfilter - if transformer changes widget type, run new widget's beforeSendToClient --- .../inc/class.etemplate_widget_nextmatch.inc.php | 2 +- .../inc/class.etemplate_widget_transformer.inc.php | 12 ++++++++++++ etemplate/js/et2_widget_entry.js | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php index 3daa81b5f9..f92102d4a2 100644 --- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php +++ b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php @@ -1207,8 +1207,8 @@ class etemplate_widget_nextmatch_customfilter extends etemplate_widget_transform $this->setElementAttribute($form_name, 'options', trim($this->attrs['widget_options']) != '' ? $this->attrs['widget_options'] : ''); - parent::beforeSendToClient($cname, $expand); $this->setElementAttribute($form_name, 'type', $this->attrs['type']); + parent::beforeSendToClient($cname, $expand); } } diff --git a/etemplate/inc/class.etemplate_widget_transformer.inc.php b/etemplate/inc/class.etemplate_widget_transformer.inc.php index 34ba40bd85..11306dfecf 100644 --- a/etemplate/inc/class.etemplate_widget_transformer.inc.php +++ b/etemplate/inc/class.etemplate_widget_transformer.inc.php @@ -122,6 +122,7 @@ abstract class etemplate_widget_transformer extends etemplate_widget //echo $this; _debug_array($unmodified); _debug_array($attrs); _debug_array(array_diff_assoc($attrs, $unmodified)); // compute the difference and send it to the client as modifications + $type_changed = false; foreach(array_diff_assoc($attrs, $unmodified) as $attr => $val) { switch($attr) @@ -136,6 +137,7 @@ abstract class etemplate_widget_transformer extends etemplate_widget self::$request->sel_options[$form_name] = $val; break; case 'type': // not an attribute in etemplate2 + $type_changed = true; if($val == 'template') { // If the widget has been transformed into a template, we @@ -146,12 +148,22 @@ abstract class etemplate_widget_transformer extends etemplate_widget $this->expand_widget($transformed_template, $expand); $transformed_template->run('beforeSendToClient',array($cname,$expand)); } + $type_changed = false; } default: self::setElementAttribute($form_name, $attr, $val); break; } } + if($type_changed) + { + // Run the new widget type's beforeSendToClient + $expanded_child = self::factory($attrs['type'], false,$this->id); + $expanded_child->id = $this->id; + $expanded_child->type = $attrs['type']; + $expanded_child->attrs = $attrs; + $expanded_child->run('beforeSendToClient',array($cname,$expand)); + } } /** diff --git a/etemplate/js/et2_widget_entry.js b/etemplate/js/et2_widget_entry.js index 3b47587612..26cfbdbecf 100644 --- a/etemplate/js/et2_widget_entry.js +++ b/etemplate/js/et2_widget_entry.js @@ -150,4 +150,4 @@ var et2_entry = et2_valueWidget.extend( } }); -et2_register_widget(et2_entry, ["entry", 'contact-value', 'contact-account', 'contact-template', 'infolog-value','tracker-value']); \ No newline at end of file +et2_register_widget(et2_entry, ["entry", 'contact-value', 'contact-account', 'contact-template', 'infolog-value','tracker-value','records-value']); \ No newline at end of file From 89741b682c1ca468ed8b22c21d2f2796c89b8971 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Thu, 19 Feb 2015 00:27:21 +0000 Subject: [PATCH 28/43] Force left margin to 0, avoids margin when printing --- phpgwapi/js/framework/fw_base.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/phpgwapi/js/framework/fw_base.js b/phpgwapi/js/framework/fw_base.js index faef5dcac6..491b53618f 100644 --- a/phpgwapi/js/framework/fw_base.js +++ b/phpgwapi/js/framework/fw_base.js @@ -982,13 +982,15 @@ var fw_base = Class.extend({ },et2_list[i],et2_IPrint); } appWindow.onafterprint = null; + // Reset after removing margin + $j('#egw_fw_main').css('margin-left', framework.activeApp.sideboxWidth + "px"); }; if(appWindow.matchMedia) { var mediaQueryList = appWindow.matchMedia('print'); var listener = function(mql) { if (!mql.matches) { - afterPrint(); mediaQueryList.removeListener(listener); + afterPrint(); } }; mediaQueryList.addListener(listener); @@ -998,7 +1000,9 @@ var fw_base = Class.extend({ // Wait for everything to be loaded, then send it off jQuery.when.apply(jQuery, deferred).done(function() { - appWindow.print(); + // Despite being set in the print CSS, this just doesn't work + $j('#egw_fw_main').css('margin-left','0px'); + appWindow.setTimeout(appWindow.print, 0); }).fail(function() { afterPrint(); }); From f1e0cc90d71684e62231ce38efcaa2c85033790f Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Thu, 19 Feb 2015 00:54:49 +0000 Subject: [PATCH 29/43] Slightly gentler reset after forcing margin for printing --- phpgwapi/js/framework/fw_base.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/phpgwapi/js/framework/fw_base.js b/phpgwapi/js/framework/fw_base.js index 491b53618f..ad5772dfb4 100644 --- a/phpgwapi/js/framework/fw_base.js +++ b/phpgwapi/js/framework/fw_base.js @@ -975,15 +975,22 @@ var fw_base = Class.extend({ { // Try to clean up after - not guaranteed var afterPrint = function() { - for(var i = 0; i < et2_list.length; i++) - { - et2_list[i].widgetContainer.iterateOver(function(_widget) { - _widget.afterPrint(); - },et2_list[i],et2_IPrint); - } - appWindow.onafterprint = null; // Reset after removing margin - $j('#egw_fw_main').css('margin-left', framework.activeApp.sideboxWidth + "px"); + $j('#egw_fw_main').css('margin-left', (framework.activeApp.sideboxWidth -1)+ "px"); + var app = framework.activeApp; + framework.activeApp = ''; + framework.setActiveApp(app); + + // Give framework a chance to deal, then reset the etemplates + window.setTimeout(function() { + for(var i = 0; i < et2_list.length; i++) + { + et2_list[i].widgetContainer.iterateOver(function(_widget) { + _widget.afterPrint(); + },et2_list[i],et2_IPrint); + } + },100); + appWindow.onafterprint = null; }; if(appWindow.matchMedia) { var mediaQueryList = appWindow.matchMedia('print'); From be5dd5435b1036bcb8bf39e5e6383afcd8468032 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Thu, 19 Feb 2015 09:43:06 +0000 Subject: [PATCH 30/43] need to use egw_vfs::load_wrapper() to support new filesystem layout in trunk, fixes "Unknown scheme" error in mount --- filemanager/cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager/cli.php b/filemanager/cli.php index 3cfbd01d34..43a141a82a 100755 --- a/filemanager/cli.php +++ b/filemanager/cli.php @@ -489,7 +489,7 @@ function load_wrapper($url) // get eGW's __autoload() function include_once(EGW_API_INC.'/common_functions.inc.php'); - if (!class_exists(str_replace('.','_',$scheme).'_stream_wrapper')) + if (!egw_vfs::load_wrapper($scheme)) { die("Unknown scheme '$scheme' in $url !!!\n\n"); } From 52b4856a1846bf8e48833d110c03a0306e3cd78b Mon Sep 17 00:00:00 2001 From: Klaus Leithoff <kl@stylite.de> Date: Thu, 19 Feb 2015 11:37:42 +0000 Subject: [PATCH 31/43] when dealing with defaults and identities: retrieve the default identity associated with the current imap-account, rather than the default --- mail/inc/class.mail_compose.inc.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index db4076ead3..b7186c33a4 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -374,6 +374,9 @@ class mail_compose $this->changeProfile($_content['serverID']); $composeProfile = $this->mail_bo->profileID; } + // make sure $acc is set/initalized properly with the current composeProfile, as $acc is used down there + // at several locations and not neccesaryly initialized before + $acc = emailadmin_account::read($composeProfile); $buttonClicked = false; if ($_content['composeToolbar'] === 'send') { @@ -1022,7 +1025,7 @@ class mail_compose // fetch the signature, prepare the select box, ... if (empty($content['mailidentity'])) { - $content['mailidentity'] = $acc['ident_id']; + $content['mailidentity'] = $acc['ident_id']?$acc['ident_id']:$acc['acc_id']; } $disableRuler = false; @@ -2437,7 +2440,7 @@ class mail_compose */ public function ajax_saveAsDraft ($content, $action='button[saveAsDraft]') { - //error_log(__METHOD__."(, action=$action)"); + //error_log(__METHOD__.__LINE__.array2string($content)."(, action=$action)"); $response = egw_json_response::get(); $success = true; @@ -3057,8 +3060,8 @@ class mail_compose */ function setDefaults($content=array()) { - // retrieve the signature accociated with the identity - $id = $this->mail_bo->getDefaultIdentity(); + // retrieve the signature accociated with the identity associated with the current account, rather than the default + $id = ($this->mail_bo->icServer->ident_id?$this->mail_bo->icServer->ident_id:$this->mail_bo->getDefaultIdentity()); if (isset($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']) && !empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'])) { $sigPref = $GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']; From 212e98ccdb2f4671da54aab045db93da141ad1e5 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Thu, 19 Feb 2015 11:45:05 +0000 Subject: [PATCH 32/43] Fix in mobile theme not able to dismiss the context menu: - touch and open entries and swip (left/right) over any rows on the next match list will dismiss the context menu --- etemplate/js/et2_dataview_view_aoi.js | 4 +++- phpgwapi/js/egw_action/egw_action_popup.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/etemplate/js/et2_dataview_view_aoi.js b/etemplate/js/et2_dataview_view_aoi.js index 8cbefc99cf..b7a6621b9c 100644 --- a/etemplate/js/et2_dataview_view_aoi.js +++ b/etemplate/js/et2_dataview_view_aoi.js @@ -77,6 +77,8 @@ function et2_dataview_rowAOI(_node) case "left": case "right": state = 1; + // Hide context menu on swip actions + if(_egw_active_menu) _egw_active_menu.hide(); break; } } @@ -106,7 +108,7 @@ function et2_dataview_rowAOI(_node) click: function (event) { selectHandler(event); - }, + } }); } else { diff --git a/phpgwapi/js/egw_action/egw_action_popup.js b/phpgwapi/js/egw_action/egw_action_popup.js index 02cf677e90..f15b5f9f70 100644 --- a/phpgwapi/js/egw_action/egw_action_popup.js +++ b/phpgwapi/js/egw_action/egw_action_popup.js @@ -150,6 +150,9 @@ function egwPopupActionImplementation() e.stopPropagation(); e.cancelBubble = true; + // remove context menu if we are in mobile theme + // and intended to open the entry + if (_egw_active_menu && e.which == 1) _egw_active_menu.hide(); return false; }; From acb4f11d24eb0de128295083b7e580f6e44727d8 Mon Sep 17 00:00:00 2001 From: Klaus Leithoff <kl@stylite.de> Date: Thu, 19 Feb 2015 12:01:18 +0000 Subject: [PATCH 33/43] * Mail: feature to allow to void the (configured) spam/junk folder on right-click action on foldertree --- mail/inc/class.mail_ui.inc.php | 65 +++++++++++++++++++++++++++++++++- mail/js/app.js | 51 ++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 03ef6d2601..6c58b72b6c 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -624,6 +624,19 @@ class mail_ui ); break; } + ++$group; // put empty spam immediately in own group + $junkFolder = $this->mail_bo->getJunkFolder(); + //error_log(__METHOD__.__LINE__.$junkFolder); + if ($junkFolder && !empty($junkFolder)) + { + $tree_actions['empty_spam'] = array( + 'caption' => 'empty spam', + 'icon' => 'dhtmlxtree/MailFolderJunk', + 'enabled' => 'javaScript:app.mail.spamfolder_enabled', + 'onExecute' => 'javaScript:app.mail.mail_emptySpam', + 'group' => $group, + ); + } // enforce global (group-specific) ACL if (!mail_hooks::access('aclmanagement')) @@ -817,7 +830,7 @@ class mail_ui 'child'=> (int)($acc_id != $_profileID || $folderObjects), // dynamic loading on unfold 'parent' => '', // mark on account if Sieve is enabled - 'data' => array('sieve' => $accountObj->imapServer()->acc_sieve_enabled), + 'data' => array('sieve' => $accountObj->imapServer()->acc_sieve_enabled,'spamfolder'=>($accountObj->imapServer()->acc_folder_junk?true:false)), ); $this->setOutStructure($oA, $out, self::$delimiter); @@ -4213,6 +4226,56 @@ class mail_ui $response->call('app.mail.mail_setQuotaDisplay',array('data'=>$content)); } + /** + * Empty spam/junk folder + * + * @param string $icServerID id of the server to empty its junkFolder + * @param string $selectedFolder seleted(active) folder by nm filter + * @return nothing + */ + function ajax_emptySpam($icServerID, $selectedFolder) + { + //error_log(__METHOD__.__LINE__.' '.$icServerID); + translation::add_app('mail'); + $response = egw_json_response::get(); + $rememberServerID = $this->mail_bo->profileID; + if ($icServerID && $icServerID != $this->mail_bo->profileID) + { + //error_log(__METHOD__.__LINE__.' change Profile to ->'.$icServerID); + $this->changeProfile($icServerID); + } + $junkFolder = $this->mail_bo->getJunkFolder(); + if(!empty($junkFolder)) { + if ($selectedFolder == $icServerID.self::$delimiter.$junkFolder) + { + // Lock the tree if the active folder is Trash folder + $response->call('app.mail.lock_tree'); + } + $this->mail_bo->deleteMessages('all',$junkFolder,'remove_immediately'); + + $heirarchyDelimeter = $this->mail_bo->getHierarchyDelimiter(true); + $fShortName = array_pop(explode($heirarchyDelimeter, $junkFolder)); + $fStatus = array( + $icServerID.self::$delimiter.$trashFolder => lang($fShortName) + ); + //Call to reset folder status counter, after junkFolder triggered not from Junk folder + //-as we don't have trash folder specific information available on client-side we need to deal with it on server + $response->call('app.mail.mail_setFolderStatus',$fStatus); + } + if ($rememberServerID != $this->mail_bo->profileID) + { + $oldFolderInfo = $this->mail_bo->getFolderStatus($junkFolder,false,false,false); + $response->call('egw.message',lang('empty junk')); + $response->call('app.mail.mail_reloadNode',array($icServerID.self::$delimiter.$junkFolder=>$oldFolderInfo['shortDisplayName'])); + //error_log(__METHOD__.__LINE__.' change Profile to ->'.$rememberServerID); + $this->changeProfile($rememberServerID); + } + else if ($selectedFolder == $icServerID.self::$delimiter.$junkFolder) + { + $response->call('egw.refresh',lang('empty junk'),'mail'); + } + } + /** * Empty trash folder * diff --git a/mail/js/app.js b/mail/js/app.js index cf7755be72..399424375f 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -1210,6 +1210,25 @@ app.classes.mail = AppJS.extend( return true; }, + /** + * Check if SpamFolder is enabled on that account + * + * SpamFolder enabled is stored as data { spamfolder: true/false } on account node. + * + * @param {object} _action + * @param {object} _senders the representation of the tree leaf to be manipulated + * @param {object} _currentNode + */ + spamfolder_enabled: function(_action,_senders,_currentNode) + { + var ftree = this.et2.getWidgetById(this.nm_index+'[foldertree]'); + var acc_id = _senders[0].id.split('::')[0]; + var node = ftree ? ftree.getNode(acc_id) : null; + + return node && node.data && node.data.spamfolder; + }, + + /** * Check if Sieve is enabled on that account * @@ -1574,6 +1593,38 @@ app.classes.mail = AppJS.extend( // setting class of row, the old style }, + /** + * mail_emptySpam + * + * @param {object} action + * @param {object} _senders + */ + mail_emptySpam: function(action,_senders) { + var server = _senders[0].iface.id.split('::'); + var activeFilters = this.mail_getActiveFilters(); + var self = this; + + this.egw.message(this.egw.lang('empty spam')); + egw.json('mail.mail_ui.ajax_emptySpam',[server[0], activeFilters['selectedFolder']? activeFilters['selectedFolder']:null],function(){self.unlock_tree();}) + .sendRequest(true); + + // Directly delete any trash cache for selected server + if(window.localStorage) + { + for(var i = 0; i < window.localStorage.length; i++) + { + var key = window.localStorage.key(i); + + // Find directly by what the key would look like + if(key.indexOf('cached_fetch_mail::{"selectedFolder":"'+server[0]+'::') == 0 && + key.toLowerCase().indexOf(egw.lang('junk').toLowerCase()) > 0) + { + window.localStorage.removeItem(key); + } + } + } + }, + /** * mail_emptyTrash * From c67ab8744af398c29b9e45f44aaf3fe104aebdce Mon Sep 17 00:00:00 2001 From: Klaus Leithoff <kl@stylite.de> Date: Thu, 19 Feb 2015 12:26:44 +0000 Subject: [PATCH 34/43] remove probably wrong assumption on missing ident_id of mailaccount object (as it should not be missing at all) --- mail/inc/class.mail_compose.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index b7186c33a4..d232693127 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -1025,7 +1025,7 @@ class mail_compose // fetch the signature, prepare the select box, ... if (empty($content['mailidentity'])) { - $content['mailidentity'] = $acc['ident_id']?$acc['ident_id']:$acc['acc_id']; + $content['mailidentity'] = $acc['ident_id']; } $disableRuler = false; From 3ef17e277f4da1925f5ff9881bd3cfbd5184eccb Mon Sep 17 00:00:00 2001 From: Klaus Leithoff <kl@stylite.de> Date: Thu, 19 Feb 2015 13:41:40 +0000 Subject: [PATCH 35/43] add missing translations for emptying spam/junk folder; fix setting folder status after voiding spam/junk folder content --- mail/inc/class.mail_ui.inc.php | 8 ++++---- mail/js/app.js | 2 +- mail/lang/egw_de.lang | 1 + mail/lang/egw_en.lang | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 6c58b72b6c..69f13fbea9 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -630,7 +630,7 @@ class mail_ui if ($junkFolder && !empty($junkFolder)) { $tree_actions['empty_spam'] = array( - 'caption' => 'empty spam', + 'caption' => 'empty junk', 'icon' => 'dhtmlxtree/MailFolderJunk', 'enabled' => 'javaScript:app.mail.spamfolder_enabled', 'onExecute' => 'javaScript:app.mail.mail_emptySpam', @@ -4248,7 +4248,7 @@ class mail_ui if(!empty($junkFolder)) { if ($selectedFolder == $icServerID.self::$delimiter.$junkFolder) { - // Lock the tree if the active folder is Trash folder + // Lock the tree if the active folder is junk folder $response->call('app.mail.lock_tree'); } $this->mail_bo->deleteMessages('all',$junkFolder,'remove_immediately'); @@ -4256,10 +4256,10 @@ class mail_ui $heirarchyDelimeter = $this->mail_bo->getHierarchyDelimiter(true); $fShortName = array_pop(explode($heirarchyDelimeter, $junkFolder)); $fStatus = array( - $icServerID.self::$delimiter.$trashFolder => lang($fShortName) + $icServerID.self::$delimiter.$junkFolder => lang($fShortName) ); //Call to reset folder status counter, after junkFolder triggered not from Junk folder - //-as we don't have trash folder specific information available on client-side we need to deal with it on server + //-as we don't have junk folder specific information available on client-side we need to deal with it on server $response->call('app.mail.mail_setFolderStatus',$fStatus); } if ($rememberServerID != $this->mail_bo->profileID) diff --git a/mail/js/app.js b/mail/js/app.js index 399424375f..0cb8550669 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -1604,7 +1604,7 @@ app.classes.mail = AppJS.extend( var activeFilters = this.mail_getActiveFilters(); var self = this; - this.egw.message(this.egw.lang('empty spam')); + this.egw.message(this.egw.lang('empty junk')); egw.json('mail.mail_ui.ajax_emptySpam',[server[0], activeFilters['selectedFolder']? activeFilters['selectedFolder']:null],function(){self.unlock_tree();}) .sendRequest(true); diff --git a/mail/lang/egw_de.lang b/mail/lang/egw_de.lang index 59874e27e5..3750e9f9f5 100644 --- a/mail/lang/egw_de.lang +++ b/mail/lang/egw_de.lang @@ -135,6 +135,7 @@ email notification update failed mail de Die Benachrichtigung über den Gelesen- email notification update failed! you need to set an email address! mail de Aktualisierung der E-Mail Benachrichtigungen fehlgeschlagen! Es muss eine E-Mail-Adresse ausgewählt sein! emailaddress admin de E-Mail emailadmin: profilemanagement mail de E-Mail-Admin: Profilmanagement +empty junk mail de Junk-/Spam- Ordner leeren empty trash mail de Papierkorb leeren enable mail de Aktivieren enabled! mail de aktiviert! diff --git a/mail/lang/egw_en.lang b/mail/lang/egw_en.lang index 3a3c1be5bc..af4bdb06cb 100644 --- a/mail/lang/egw_en.lang +++ b/mail/lang/egw_en.lang @@ -135,6 +135,7 @@ email notification update failed mail en email notification update failed email notification update failed! you need to set an email address! mail en email notification update failed! You need to set an email address! emailaddress admin en emailaddress emailadmin: profilemanagement mail en eMailAdmin: Profilemanagement +empty junk mail en empty junk/spam folder empty trash mail en empty trash enable mail en Enable enabled! mail en enabled! From a6bb56d2e1e2b5bf206fc963d6f645f3a1539101 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Thu, 19 Feb 2015 17:14:31 +0000 Subject: [PATCH 36/43] Fix bug in rule removal prevented adding more rules --- phpgwapi/js/jsapi/egw_css.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpgwapi/js/jsapi/egw_css.js b/phpgwapi/js/jsapi/egw_css.js index 590f8eb6c0..00b56969ed 100644 --- a/phpgwapi/js/jsapi/egw_css.js +++ b/phpgwapi/js/jsapi/egw_css.js @@ -77,6 +77,10 @@ egw.extend('css', egw.MODULE_WND_LOCAL, function(_app, _wnd) { } delete (selectors[_selector]); + if(!_rule) + { + selectorCount--; + } } else { From 904bc8b19fff8a12b7e997fe56589f966bb273e8 Mon Sep 17 00:00:00 2001 From: Nathan Gray <nathangray.bsc@gmail.com> Date: Thu, 19 Feb 2015 17:16:39 +0000 Subject: [PATCH 37/43] Better row limiting by using CSS instead of grid's average height --- etemplate/js/et2_extension_nextmatch.js | 61 ++++++++++++++++--------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index b1df8f0888..cd28e13f85 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -1875,7 +1875,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin this.resize(); // Reset height to auto (after width resize) so there's no restrictions this.dynheight.innerNode.css('height', 'auto'); - + // Check for rows that aren't loaded yet, or lots of rows var range = this.controller._grid.getIndexRange(); this.old_height = this.controller._grid._scrollHeight; @@ -1953,30 +1953,20 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin defer.reject(); return; } - if(value < total) - { - // Set height to the requested number of rows, using the average height. - // We add one, in case there's some larger rows we - // try to get most of it but that's pretty hacky - nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value+1)); - } + // Use CSS to hide all but the requested rows + // Prevents us from showing more than requested, if actual height was less than average + nm.print_row_selector = ".egwGridView_grid > tbody > tr:not(:nth-child(-n+"+value+"))"; + egw.css(nm.print_row_selector, 'display: none'); + + // No scrollbar in print view + $j('.egwGridView_scrollarea',this.div).css('overflow-y','hidden'); + // Show it all + $j('.egwGridView_scrollarea',this.div).css('height','auto'); // Grid needs to redraw before it can be printed, so wait window.setTimeout(jQuery.proxy(function() { dialog.destroy(); - - if(value < total) - { - // Show requested number, based on average height - nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value)); - // No scrollbar in print view - $j('.egwGridView_scrollarea',this.div).css('overflow-y','hidden'); - } - else - { - // Show it all - $j('.egwGridView_scrollarea',this.div).css('height','auto'); - } + // Should be OK to print now defer.resolve(); },nm),ET2_GRID_INVALIDATE_TIMEOUT); @@ -1991,7 +1981,15 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin else { // Don't need more rows, limit to requested and finish - this.controller._grid.setScrollHeight(this.controller._grid.getAverageHeight() * (value)); + + // Show it all + $j('.egwGridView_scrollarea',this.div).css('height','auto'); + + // Use CSS to hide all but the requested rows + // Prevents us from showing more than requested, if actual height was less than average + this.print_row_selector = ".egwGridView_grid > tbody > tr:not(:nth-child(-n+"+value+"))"; + egw.css(this.print_row_selector, 'display: none'); + // No scrollbar in print view $j('.egwGridView_scrollarea',this.div).css('overflow-y','hidden'); // Give dialog a chance to close, or it will be in the print @@ -2017,10 +2015,29 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin } // Don't return anything, just work normally }, + + /** + * Try to clean up the mess we made getting ready for printing + * in beforePrint() + */ afterPrint: function() { + this.div.removeClass('print'); + + // Put scrollbar back + $j('.egwGridView_scrollarea',this.div).css('overflow-y',''); + + // Correct size of grid, and trigger resize to fix it this.controller._grid.setScrollHeight(this.old_height); delete this.old_height; + + // Remove CSS rule hiding extra rows + if(this.print_row_selector) + { + egw.css(this.print_row_selector, false); + delete this.print_row_selector; + } + this.dynheight.outerNode.css('max-width','inherit'); this.resize(); } From f6260c1c8c59c66868f5f7d3dccf9cf14eeafe0d Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Fri, 20 Feb 2015 12:28:13 +0000 Subject: [PATCH 38/43] Change the login layout if only it is called from devices with small screen --- pixelegg/css/mobile.css | 124 ++++++++++++++++---------------- pixelegg/css/mobile.less | 150 ++++++++++++++++++++------------------- 2 files changed, 139 insertions(+), 135 deletions(-) diff --git a/pixelegg/css/mobile.css b/pixelegg/css/mobile.css index 1421dda292..4344359391 100644 --- a/pixelegg/css/mobile.css +++ b/pixelegg/css/mobile.css @@ -6265,67 +6265,6 @@ a.textSidebox { body { background-color: transparent; } - body div#loginMainDiv #divAppIconBar #divLogo img[src$="svg"] { - width: 40%; - margin-top: 5px; - } - body div#loginMainDiv div#centerBox { - position: absolute; - margin: 0; - width: 100%; - background-color: transparent; - padding: 0; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 0; - -webkit-border-bottom-left-radius: 0; - -webkit-border-top-left-radius: 0; - -moz-border-radius-topright: 0; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topleft: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 0; - background-color: none; - background-image: none; - background-repeat: none; - border: none; - border-radius: none; - } - body div#loginMainDiv div#centerBox form { - margin-top: -2em; - margin-right: 3em; - } - body div#loginMainDiv div#centerBox form table.divLoginbox { - width: 100%; - float: left; - } - body div#loginMainDiv div#centerBox form table.divLoginbox tr.hiddenCredential { - display: none; - } - body div#loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { - font-size: xx-large; - } - body div#loginMainDiv div#centerBox form table.divLoginbox input, - body div#loginMainDiv div#centerBox form table.divLoginbox select { - width: 100%; - height: 60px; - } - body div#loginMainDiv div#centerBox form table.divLoginbox td { - font-size: 300%; - padding: 0.8%; - } - body div#loginMainDiv div#centerBox form table.divLoginbox td.registration { - font-size: 180%; - } - body div#loginMainDiv div#centerBox form table.divLoginbox td select { - background-size: 48px auto; - } - body div#loginMainDiv div#centerBox #loginCdMessage { - font-size: large; - padding: 0; - } body div.egw_fw_mobile_iOS_popup_appHeader { padding-top: 15px; } @@ -7040,6 +6979,69 @@ a.textSidebox { background-position: center; } } +@media only screen and (max-device-width : 1024px) { + div#loginMainDiv #divAppIconBar #divLogo img[src$="svg"] { + width: 40%; + margin-top: 5px; + } + div#loginMainDiv div#centerBox { + position: absolute; + margin: 0; + width: 100%; + background-color: transparent; + padding: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; + -webkit-border-top-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topleft: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + background-color: none; + background-image: none; + background-repeat: none; + border: none; + border-radius: none; + } + div#loginMainDiv div#centerBox form { + margin-top: -2em; + margin-right: 3em; + } + div#loginMainDiv div#centerBox form table.divLoginbox { + width: 100%; + float: left; + } + div#loginMainDiv div#centerBox form table.divLoginbox tr.hiddenCredential { + display: none; + } + div#loginMainDiv div#centerBox form table.divLoginbox input[type="submit"] { + font-size: xx-large; + } + div#loginMainDiv div#centerBox form table.divLoginbox input, + div#loginMainDiv div#centerBox form table.divLoginbox select { + width: 100%; + height: 60px; + } + div#loginMainDiv div#centerBox form table.divLoginbox td { + font-size: 300%; + padding: 0.8%; + } + div#loginMainDiv div#centerBox form table.divLoginbox td.registration { + font-size: 180%; + } + div#loginMainDiv div#centerBox form table.divLoginbox td select { + background-size: 48px auto; + } + div#loginMainDiv div#centerBox #loginCdMessage { + font-size: large; + padding: 0; + } +} @media only screen and (max-device-width : 1024px) and (orientation : portrait) { body div#loginMainDiv #divAppIconBar #divLogo img[src$="svg"] { width: 70%; diff --git a/pixelegg/css/mobile.less b/pixelegg/css/mobile.less index 63aac28461..161ebe80e0 100644 --- a/pixelegg/css/mobile.less +++ b/pixelegg/css/mobile.less @@ -23,6 +23,7 @@ @smartphone-max: 768px; /*Smartphones Min-Width*/ @smartphone-min: 321px; +@handheld: ~"only screen and (max-device-width : @{tablet-max})"; /*All devices portrait mode*/ @handheld-portrait: ~"only screen and (max-device-width : @{tablet-max}) and (orientation : portrait)"; /*All devices landscape mode*/ @@ -35,80 +36,6 @@ @media all { body{ - - div#loginMainDiv{ - #divAppIconBar { - #divLogo img[src$="svg"] { - width:40%; - margin-top: 5px; - } - } - div#centerBox{ - position:absolute; - margin: 0; - width: 100%; - background-color: transparent; - padding: 0; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius:0; - -webkit-border-bottom-left-radius: 0; - -webkit-border-top-left-radius: 0; - -moz-border-radius-topright: 0; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topleft: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 0; - background-color: none; - background-image: none; - background-image: none; - background-image: none; - background-image: none; - background-image: none; - background-image: none; - background-image: none; - background-repeat: none; - border:none; - border-radius: none; - form { - margin-top: -2em; - margin-right: 3em; - - table.divLoginbox { - width:100%; - float:left; - tr.hiddenCredential { - display:none; - } - input[type="submit"] { - font-size: xx-large; - - } - input, select { - width:100%; - height:60px; - } - td { - font-size: 300%; - padding:0.8%; - &.registration{ - font-size: 180%; - } - select { - background-size: 48px auto; - } - } - } - } - #loginCdMessage { - font-size:large; - padding:0; - } - } - } - background-color: transparent; // iOS appHeader class div.egw_fw_mobile_iOS_popup_appHeader{ @@ -829,6 +756,81 @@ background-position: center; } } +@media @handheld +{ + div#loginMainDiv{ + #divAppIconBar { + #divLogo img[src$="svg"] { + width:40%; + margin-top: 5px; + } + } + div#centerBox{ + position:absolute; + margin: 0; + width: 100%; + background-color: transparent; + padding: 0; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius:0; + -webkit-border-bottom-left-radius: 0; + -webkit-border-top-left-radius: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topleft: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + background-color: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-image: none; + background-repeat: none; + border:none; + border-radius: none; + form { + margin-top: -2em; + margin-right: 3em; + + table.divLoginbox { + width:100%; + float:left; + tr.hiddenCredential { + display:none; + } + input[type="submit"] { + font-size: xx-large; + + } + input, select { + width:100%; + height:60px; + } + td { + font-size: 300%; + padding:0.8%; + &.registration{ + font-size: 180%; + } + select { + background-size: 48px auto; + } + } + } + } + #loginCdMessage { + font-size:large; + padding:0; + } + } + } +} @media @handheld-portrait { body{ From 8ab9d48a96ac5407008d03d63a92d9ba7356d74f Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Fri, 20 Feb 2015 14:05:32 +0000 Subject: [PATCH 39/43] Let the mobile theme uses pixelegg custom color --- pixelegg/inc/class.pixelegg_framework.inc.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pixelegg/inc/class.pixelegg_framework.inc.php b/pixelegg/inc/class.pixelegg_framework.inc.php index b4e550cd06..9ea8a16d6d 100755 --- a/pixelegg/inc/class.pixelegg_framework.inc.php +++ b/pixelegg/inc/class.pixelegg_framework.inc.php @@ -98,7 +98,6 @@ class pixelegg_framework extends jdots_framework */ public function _get_css() { - if (html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile') return egw_framework::_get_css(); $ret = parent::_get_css(); // color to use $color = str_replace('custom',$GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'], @@ -124,9 +123,9 @@ class pixelegg_framework extends jdots_framework -popup toolbar */ div#egw_fw_header, div.egw_fw_ui_category:hover,#loginMainDiv,#loginMainDiv #divAppIconBar #divLogo, -#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_content .egw_fw_ui_category_active:hover, +#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_category_active:hover, .dialogFooterToolbar, .ui-widget-header{ - background-color: $color; + background-color: $color !important; } /*Login background*/ @@ -150,8 +149,8 @@ div#egw_fw_header, div.egw_fw_ui_category:hover,#loginMainDiv,#loginMainDiv #div } /*Sidebar menu active category*/ -#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_content .egw_fw_ui_category_active{ - background-color: $color_hex_darker; +#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_category_active{ + background-color: $color_hex_darker !important; } "; } From d1ce50fe5e8d205cc3c68b12b952f18d22bc8d7d Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Fri, 20 Feb 2015 16:06:18 +0000 Subject: [PATCH 40/43] Fix toolbar gets background after setting template custom color --- pixelegg/inc/class.pixelegg_framework.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelegg/inc/class.pixelegg_framework.inc.php b/pixelegg/inc/class.pixelegg_framework.inc.php index 9ea8a16d6d..77a6df04ee 100755 --- a/pixelegg/inc/class.pixelegg_framework.inc.php +++ b/pixelegg/inc/class.pixelegg_framework.inc.php @@ -124,7 +124,7 @@ class pixelegg_framework extends jdots_framework */ div#egw_fw_header, div.egw_fw_ui_category:hover,#loginMainDiv,#loginMainDiv #divAppIconBar #divLogo, #egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_category_active:hover, -.dialogFooterToolbar, .ui-widget-header{ +.dialogFooterToolbar, .et2_portlet .ui-widget-header{ background-color: $color !important; } From 229d4063a3b26381c84a1951ba626c1688468a92 Mon Sep 17 00:00:00 2001 From: Hadi Nategh <hn@stylite.de> Date: Fri, 20 Feb 2015 16:14:34 +0000 Subject: [PATCH 41/43] Add translation of "Link title for events to show" into calendar --- calendar/lang/egw_de.lang | 1 + calendar/lang/egw_en.lang | 1 + 2 files changed, 2 insertions(+) diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang index 3331f79bdf..a0a60b9688 100644 --- a/calendar/lang/egw_de.lang +++ b/calendar/lang/egw_de.lang @@ -307,6 +307,7 @@ last changed calendar de letzte Änderung lastname of person to notify calendar de Nachname der zu benachrichtigenden Person length of the time interval calendar de Länge des Zeitintervalls limit number of description lines in list view (default 5, 0 for no limit) calendar de Anzahl Zeilen der Beschreibung in der Listenansicht (voreingestellt 5, 0 für alle) +link title for events to show calendar de Erweiterung des Link-Titels für Kalender-Einträge link to view the event calendar de Verweis (Weblink) um den Termin anzuzeigen links calendar de Verknüpfungen links and attached files calendar de Verknüpfungen zu anderen Datensätzen und angefügten Dateien diff --git a/calendar/lang/egw_en.lang b/calendar/lang/egw_en.lang index 82070e586e..92b22f74aa 100644 --- a/calendar/lang/egw_en.lang +++ b/calendar/lang/egw_en.lang @@ -307,6 +307,7 @@ last changed calendar en Last changed lastname of person to notify calendar en Last name of a person to notify length of the time interval calendar en Length of the time interval limit number of description lines in list view (default 5, 0 for no limit) calendar en Limit number of description lines in list view. Default is 5, 0 for no limit. +link title for events to show calendar en Link title for events to show link to view the event calendar en Link to view the event links calendar en Links links and attached files calendar en Links and attached files From 370e503dabbf1c4544437c44f755a4af2d1ca4dc Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Fri, 20 Feb 2015 19:04:58 +0000 Subject: [PATCH 42/43] * Safari: fix security warning caused by auto-complete when submitting from https to about:blank --- etemplate/js/etemplate2.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js index 50a0f30fa7..380b822e1f 100644 --- a/etemplate/js/etemplate2.js +++ b/etemplate/js/etemplate2.js @@ -597,8 +597,11 @@ etemplate2.prototype.autocomplete_fixer = function () form.onsubmit = function(){return false;}; // Firefox give a security warning when transmitting to "about:blank" from a https site // we work around that by giving existing etemplate/empty.html url - if (navigator.userAgent.match(/firefox/i)) jQuery(form).attr({action: egw.webserverUrl+'/etemplate/empty.html',method:'post'}); - + // Safari shows same warning, thought Chrome userAgent also includes Safari + if (navigator.userAgent.match(/(firefox|safari)/i) && !navigator.userAgent.match(/chrome/i)) + { + jQuery(form).attr({action: egw.webserverUrl+'/etemplate/empty.html',method:'post'}); + } form.submit(); } }; @@ -937,7 +940,7 @@ etemplate2.prototype.print = function() },this,et2_IPrint); return deferred; -} +}; // Some static things to make getting into widget context a little easier // From 9cc1d409eba799feaab7b9659dbcf49943155532 Mon Sep 17 00:00:00 2001 From: Ralf Becker <ralfbecker@outdoor-training.de> Date: Sat, 21 Feb 2015 13:29:10 +0000 Subject: [PATCH 43/43] harden login page by no longer using www.groupware.org to load social media icons --- phpgwapi/inc/class.egw_framework.inc.php | 50 +++++++++++++++---- phpgwapi/js/login.js | 34 +++++++------ .../default/images/login_contact.svg | 21 ++++++++ .../default/images/login_facebook.svg | 19 +++++++ .../default/images/login_twitter.svg | 24 +++++++++ 5 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 phpgwapi/templates/default/images/login_contact.svg create mode 100644 phpgwapi/templates/default/images/login_facebook.svg create mode 100644 phpgwapi/templates/default/images/login_twitter.svg diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index 65ad82747b..88aa4193d3 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -180,18 +180,50 @@ abstract class egw_framework //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace()); } } - //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_script_src_attrs)).' '.function_backtrace()); + //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_connect_src_attrs)).' '.function_backtrace()); return implode(' ', self::$csp_connect_src_attrs); } /** - * Query additional CSP frame-src from current app + * Additional attributes or urls for CSP frame-src 'self' * - * @return array + * @var array */ - protected function _get_csp_frame_src() + private static $csp_frame_src_attrs; + + /** + * Set/get Content-Security-Policy attributes for frame-src: + * + * Calling this method with an empty array sets no frame-src, but "'self'"! + * + * @param string|array $set =array() URL (incl. protocol!) + * @return string with attributes eg. "'unsafe-inline'" + */ + public static function csp_frame_src_attrs($set=null) { - return $GLOBALS['egw']->hooks->single('csp-frame-src', $GLOBALS['egw_info']['flags']['currentapp']); + // set frame-src attrs of API and apps via hook + if (!isset(self::$csp_frame_src_attrs) && !isset($set)) + { + $frame_src = array('manual.egroupware.org', 'www.egroupware.org'); + if (($additional = $GLOBALS['egw']->hooks->single('csp-frame-src', $GLOBALS['egw_info']['flags']['currentapp']))) + { + $frame_src = array_unique(array_merge($frame_src, $additional)); + } + return self::csp_frame_src_attrs($frame_src); + } + + if (!isset(self::$csp_frame_src_attrs)) self::$csp_frame_src_attrs = array(); + + foreach((array)$set as $attr) + { + if (!in_array($attr, self::$csp_frame_src_attrs)) + { + self::$csp_frame_src_attrs[] = $attr; + //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace()); + } + } + //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_frame_src_attrs)).' '.function_backtrace()); + return implode(' ', self::$csp_frame_src_attrs); } /** @@ -207,13 +239,10 @@ abstract class egw_framework // - "connect-src 'self'" allows ajax requests only to self // - "style-src 'self' 'unsave-inline'" allows only self and inline style, which we need // - "frame-src 'self' manual.egroupware.org" allows frame and iframe content only for self or manual.egroupware.org - $frame_src = array("'self'", 'manual.egroupware.org', 'www.egroupware.org'); - if (($additional = $this->_get_csp_frame_src())) $frame_src = array_unique(array_merge($frame_src, $additional)); - $csp = "script-src 'self' ".self::csp_script_src_attrs(). "; connect-src 'self' ".self::csp_connect_src_attrs(). "; style-src 'self' ".self::csp_style_src_attrs(). - "; frame-src ".implode(' ', $frame_src); + "; frame-src 'self' ".self::csp_frame_src_attrs(); //$csp = "default-src * 'unsafe-eval' 'unsafe-inline'"; // allow everything header("Content-Security-Policy: $csp"); @@ -512,8 +541,7 @@ abstract class egw_framework */ function login_screen($extra_vars) { - //allow to include JSONP file with social media urls from egroupware.org - self::csp_script_src_attrs('https://www.egroupware.org'); + self::csp_frame_src_attrs(array()); // array() no external frame-sources //error_log(__METHOD__."() server[template_dir]=".array2string($GLOBALS['egw_info']['server']['template_dir']).", this->template=$this->template, this->template_dir=$this->template_dir, get_class(this)=".get_class($this)); $tmpl = new Template($GLOBALS['egw_info']['server']['template_dir']); diff --git a/phpgwapi/js/login.js b/phpgwapi/js/login.js index 0fff07bc79..26820641fb 100644 --- a/phpgwapi/js/login.js +++ b/phpgwapi/js/login.js @@ -1,19 +1,19 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. +/** + * EGroupware login page javascript + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @version $Id$ */ - -egw_LAB.wait(function() { - $j.ajax('https://www.egroupware.org/social.js', { - dataType: "jsonp", - jsonp: false, - jsonpCallback: "do_social", - cache: true - }).done(function(_data) +egw_LAB.wait(function() +{ + $j(document).ready(function() { - $j(document).ready(function() { + function do_social(_data) + { var isPixelegg = $j('link[href*="pixelegg.css"]')[0]; var social = $j(document.createElement('div')) .attr({ @@ -34,6 +34,12 @@ egw_LAB.wait(function() { .append($j(document.createElement('img')) .attr('src', data.svg)); } - }); + } + + do_social([ + { "svg": egw_webserverUrl+"/phpgwapi/templates/default/images/login_contact.svg", "url": "https://www.egroupware.org/en/contact.html", "lang": { "de": "https://www.egroupware.org/de/kontakt.html" }}, + { "svg": egw_webserverUrl+"/phpgwapi/templates/default/images/login_facebook.svg", "url": "https://www.facebook.com/egroupware" }, + { "svg": egw_webserverUrl+"/phpgwapi/templates/default/images/login_twitter.svg", "url": "https://twitter.com/egroupware" } + ]); }); }); diff --git a/phpgwapi/templates/default/images/login_contact.svg b/phpgwapi/templates/default/images/login_contact.svg new file mode 100644 index 0000000000..5ce8142435 --- /dev/null +++ b/phpgwapi/templates/default/images/login_contact.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<?xml-stylesheet type="text/css" href="../../../../pixelegg/less/svg.css" ?> +<svg version="1.1" id="mail_navbar" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="28.347px" height="28.347px" viewBox="0 0 28.347 28.347" enable-background="new 0 0 28.347 28.347" xml:space="preserve"> +<g> + <path fill-rule="evenodd" clip-rule="evenodd" fill="#ECEDED" d="M20.038,16.039c-3.507,0-5.767,2.659-5.767,5.72 + c0,2.867,2.195,4.649,4.788,4.649c1.059,0,1.942-0.163,2.812-0.595l-0.253-0.639c-0.647,0.342-1.485,0.521-2.386,0.521 + c-2.338,0-4.06-1.545-4.06-4.026c0-3.016,2.164-4.947,4.739-4.947c2.448,0,3.823,1.561,3.823,3.729 + c0,1.708-0.901,2.733-1.707,2.703c-0.521-0.015-0.711-0.534-0.473-1.663l0.537-2.689c-0.412-0.178-1.028-0.312-1.691-0.312 + c-2.196,0-3.743,1.68-3.743,3.521c0,1.174,0.789,1.871,1.706,1.871c0.947,0,1.674-0.43,2.228-1.307h0.046 + c-0.03,0.921,0.554,1.307,1.186,1.307c1.469,0,2.797-1.307,2.797-3.535C24.62,17.866,22.787,16.039,20.038,16.039z M20.67,21.061 + c-0.174,0.92-1.011,2.02-1.927,2.02c-0.695,0-1.043-0.476-1.043-1.129c0-1.44,1.121-2.674,2.512-2.674 + c0.363,0,0.632,0.06,0.79,0.119L20.67,21.061z"/> + <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#ECEDED" points="22.897,4.501 22.467,2.624 2.439,7.218 2.869,9.095 + 14.319,13.058 "/> + <path fill-rule="evenodd" clip-rule="evenodd" fill="#ECEDED" d="M23.185,6.551l-8.578,8.556l-11.45-3.961l2.44,9.842l7.337-1.684 + c0.902-2.904,3.611-5.013,6.812-5.013c2.052,0,3.896,0.872,5.197,2.259l0.683-0.157L23.185,6.551z"/> +</g> +</svg> diff --git a/phpgwapi/templates/default/images/login_facebook.svg b/phpgwapi/templates/default/images/login_facebook.svg new file mode 100644 index 0000000000..11f9e72b08 --- /dev/null +++ b/phpgwapi/templates/default/images/login_facebook.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="facebook" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="28.35px" height="28.35px" viewBox="0 0 28.35 28.35" enable-background="new 0 0 28.35 28.35" xml:space="preserve"> +<g> + <defs> + <rect id="SVGID_1_" x="0.003" y="0.003" width="28.344" height="28.344"/> + </defs> + <clipPath id="SVGID_2_"> + <use xlink:href="#SVGID_1_" overflow="visible"/> + </clipPath> + <path clip-path="url(#SVGID_2_)" fill="#35528F" d="M26.782,28.347c0.863,0,1.564-0.7,1.564-1.564V1.567 + c0-0.863-0.701-1.563-1.564-1.563H1.567c-0.864,0-1.564,0.7-1.564,1.563v25.216c0,0.864,0.7,1.564,1.564,1.564H26.782z"/> + <path clip-path="url(#SVGID_2_)" fill="#FFFFFF" d="M19.56,28.347V17.371h3.684l0.553-4.278H19.56v-2.731 + c0-1.238,0.344-2.083,2.119-2.083l2.266-0.001V4.452c-0.393-0.053-1.736-0.169-3.301-0.169c-3.266,0-5.502,1.993-5.502,5.654v3.155 + h-3.693v4.278h3.693v10.976H19.56z"/> +</g> +</svg> diff --git a/phpgwapi/templates/default/images/login_twitter.svg b/phpgwapi/templates/default/images/login_twitter.svg new file mode 100644 index 0000000000..4fcc581878 --- /dev/null +++ b/phpgwapi/templates/default/images/login_twitter.svg @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="twitter" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="28.35px" height="28.35px" viewBox="0 0 28.35 28.35" enable-background="new 0 0 28.35 28.35" xml:space="preserve"> +<g> + <defs> + <rect id="SVGID_1_" x="0.002" y="0.003" width="28.345" height="28.344"/> + </defs> + <clipPath id="SVGID_2_"> + <use xlink:href="#SVGID_1_" overflow="visible"/> + </clipPath> + <path clip-path="url(#SVGID_2_)" fill="#6BACD9" d="M26.782,28.347c0.863,0,1.564-0.701,1.564-1.564V1.566 + c0-0.863-0.701-1.563-1.564-1.563H1.566c-0.864,0-1.564,0.7-1.564,1.563v25.216c0,0.863,0.7,1.564,1.564,1.564H26.782z"/> + <path clip-path="url(#SVGID_2_)" fill="#FFFFFF" d="M26.041,7.459c-0.874,0.388-1.813,0.65-2.8,0.768 + c1.006-0.604,1.779-1.559,2.143-2.697c-0.941,0.559-1.984,0.965-3.096,1.183C21.4,5.764,20.132,5.173,18.73,5.173 + c-2.693,0-4.875,2.184-4.875,4.875c0,0.383,0.043,0.755,0.125,1.111C9.928,10.956,6.335,9.014,3.93,6.065 + c-0.42,0.72-0.66,1.558-0.66,2.45c0,1.692,0.861,3.185,2.169,4.06c-0.799-0.026-1.551-0.245-2.208-0.61 + C3.23,11.985,3.23,12.006,3.23,12.026c0,2.362,1.681,4.333,3.911,4.78c-0.409,0.111-0.84,0.172-1.284,0.172 + c-0.314,0-0.62-0.031-0.918-0.088c0.621,1.938,2.422,3.348,4.555,3.387c-1.669,1.307-3.771,2.088-6.055,2.088 + c-0.394,0-0.782-0.023-1.163-0.068c2.157,1.383,4.72,2.189,7.474,2.189c8.968,0,13.872-7.43,13.872-13.872 + c0-0.212-0.006-0.422-0.014-0.631C24.562,9.296,25.387,8.436,26.041,7.459"/> +</g> +</svg>