diff --git a/calendar/inc/class.calendar_hooks.inc.php b/calendar/inc/class.calendar_hooks.inc.php index 5c5727392d..ce4a21a17c 100644 --- a/calendar/inc/class.calendar_hooks.inc.php +++ b/calendar/inc/class.calendar_hooks.inc.php @@ -45,8 +45,10 @@ class calendar_hooks 'mime' => array( 'text/calendar' => array( 'menuaction' => 'calendar.calendar_uiforms.edit', - 'mime_id' => 'ical_vfs', + 'mime_data' => 'ical_data', + 'mime_url' => 'ical_url', 'mime_popup' => '750x400', + 'mime_target' => '_blank' ), ), 'merge' => true, diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index 1a64b39bbe..b394568555 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -1213,11 +1213,30 @@ class calendar_uiforms extends calendar_ui unset($event['participants']); return $this->process_edit($event); } - if (!empty($_GET['ical']) || !empty($_GET['ical_vfs']) && egw_vfs::file_exists($_GET['ical_vfs'])) + // vfs url + if (!empty($_GET['ical_url']) && parse_url($_GET['ical_url'], PHP_URL_SCHEME) == 'vfs') + { + $_GET['ical_vfs'] = parse_url($_GET['ical_url'], PHP_URL_PATH); + } + // vfs path + if (!empty($_GET['ical_vfs']) && + (!egw_vfs::file_exists($_GET['ical_vfs']) || !($_GET['ical'] = file_get_contents(egw_vfs::PREFIX.$_GET['ical_vfs'])))) + { + //error_log(__METHOD__."() Error: importing the iCal: vfs file not found '$_GET[ical_vfs]'!"); + $msg = lang('Error: importing the iCal').': '.lang('VFS file not found').': '.$_GET['ical_vfs']; + $event =& $this->default_add_event(); + } + if (!empty($_GET['ical_data']) && + !($_GET['ical'] = egw_link::get_data($_GET['ical_data']))) + { + //error_log(__METHOD__."() Error: importing the iCal: data not found '$_GET[ical_data]'!"); + $msg = lang('Error: importing the iCal').': '.lang('Data not found').': '.$_GET['ical_data']; + $event =& $this->default_add_event(); + } + if (!empty($_GET['ical'])) { $ical = new calendar_ical(); - $ical_string = !empty($_GET['ical']) ? $_GET['ical'] : file_get_contents(egw_vfs::PREFIX.$_GET['ical_vfs']); - if (!($events = $ical->icaltoegw($ical_string, '', 'utf-8')) || count($events) != 1) + if (!($events = $ical->icaltoegw($_GET['ical'], '', 'utf-8')) || count($events) != 1) { error_log(__METHOD__."('$_GET[ical]') error parsing iCal!"); $msg = lang('Error: importing the iCal'); @@ -1245,7 +1264,6 @@ class calendar_uiforms extends calendar_ui //error_log(__METHOD__."(...) parsed as ".array2string($event)); } unset($ical); - unset($ical_string); } elseif (!$cal_id || $cal_id && !($event = $this->bo->read($cal_id))) { diff --git a/emailadmin/inc/class.emailadmin_imapbase.inc.php b/emailadmin/inc/class.emailadmin_imapbase.inc.php index 564f48f824..4779f40bbb 100644 --- a/emailadmin/inc/class.emailadmin_imapbase.inc.php +++ b/emailadmin/inc/class.emailadmin_imapbase.inc.php @@ -5124,20 +5124,40 @@ class emailadmin_imapbase } /** - * retrieve a attachment + * Get attachment data as string, to be used with egw_link::(get|set)_data() * - * @param int _uid the uid of the message - * @param string _partID the id of the part, which holds the attachment - * @param int _winmail_nr winmail.dat attachment nr. - * @param boolean _returnPart flag to indicate if the attachment is to be returned as horde mime part object - * @param boolean _stream flag to indicate if the attachment is to be fetched or returned as filepointer + * @param int $acc_id + * @param string $_mailbox + * @param int $_uid + * @param string $_partID + * @param int $_winmail_nr + * @return resource stream with attachment content + */ + public static function getAttachmentAccount($acc_id, $_mailbox, $_uid, $_partID, $_winmail_nr) + { + $bo = self::getInstance(false, $acc_id); + + $attachment = $bo->getAttachment($_uid, $_partID, $_winmail_nr, false, true, $_mailbox); + + return $attachment['attachment']; + } + + /** + * Retrieve a attachment + * + * @param int $_uid the uid of the message + * @param string $_partID the id of the part, which holds the attachment + * @param int $_winmail_nr = 0 winmail.dat attachment nr. + * @param boolean $_returnPart =true flag to indicate if the attachment is to be returned as horde mime part object + * @param boolean $_stream =false flag to indicate if the attachment is to be fetched or returned as filepointer + * @param string $_folder =null folder to use if not current folder * * @return array */ - function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false) + function getAttachment($_uid, $_partID, $_winmail_nr=0, $_returnPart=true, $_stream=false, $_folder=null) { //error_log(__METHOD__.__LINE__."Uid:$_uid, PartId:$_partID, WinMailNr:$_winmail_nr, ReturnPart:$_returnPart, Stream:$_stream"); - $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox()); + if (!isset($_folder)) $_folder = ($this->sessionData['mailbox']? $this->sessionData['mailbox'] : $this->icServer->getCurrentMailbox()); $uidsToFetch = new Horde_Imap_Client_Ids(); if (!(is_object($_uid) || is_array($_uid))) $_uid = (array)$_uid; @@ -5733,6 +5753,8 @@ class emailadmin_imapbase */ static function checkFileBasics(&$_formData, $IDtoAddToFileName='', $reqMimeType='message/rfc822') { + if (parse_url($_formData['file'],PHP_URL_SCHEME) == 'egw-data') return $_formData['file']; + //error_log(__METHOD__.__FILE__.array2string($_formData).' Id:'.$IDtoAddToFileName.' ReqMimeType:'.$reqMimeType); $importfailed = $tmpFileName = false; // ignore empty files, but allow to share vfs directories (which can have 0 size) @@ -6162,11 +6184,20 @@ class emailadmin_imapbase */ function parseFileIntoMailObject(egw_mailer $mailer, $tmpFileName) { - if (parse_url($tmpFileName, PHP_URL_SCHEME) != 'vfs') + switch (parse_url($tmpFileName, PHP_URL_SCHEME)) { - $tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].SEP.basename($tmpFileName); + case 'vfs': + break; + case 'egw-data': + $message = egw_link::get_data(parse_url($tmpFileName, PHP_URL_HOST), true); + break; + default: + $tmpFileName = $GLOBALS['egw_info']['server']['temp_dir'].SEP.basename($tmpFileName); + break; } - if (!($message = fopen($tmpFileName, 'r'))) + if (!isset($message)) $message = fopen($tmpFileName, 'r'); + + if (!$message) { throw new egw_exception_not_found("File '$tmpFileName' not found!"); } diff --git a/etemplate/js/et2_widget_description.js b/etemplate/js/et2_widget_description.js index 3838aaf414..00c1544df1 100644 --- a/etemplate/js/et2_widget_description.js +++ b/etemplate/js/et2_widget_description.js @@ -15,6 +15,7 @@ /*egw:uses jquery.jquery; et2_core_baseWidget; + /etemplate/js/expose.js; */ /** @@ -22,9 +23,16 @@ * * @augments et2_baseWidget */ -var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], +var et2_description = expose(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", @@ -62,7 +70,7 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], "extra_link_target": { "name": "Link target", "type": "string", - "default": "_self", + "default": "_browser", "description": "Link target for href attribute" }, "extra_link_popup": { @@ -75,6 +83,24 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], "type": "string", "description": "Link title which is displayed on mouse over.", "translate": true + }, + "expose_view":{ + name: "Expose view", + type: "boolean", + default: false, + description: "Clicking on description with href value would popup an expose view, and will show content referenced by href." + }, + mime:{ + name: "Mime type", + type: "string", + default: '', + description: "Mime type of the registered link" + }, + mime_data:{ + name: "Mime data", + type: "string", + default: '', + description: "hash for data stored on service-side with egw_link::(get|set)_data()" } }, @@ -119,6 +145,82 @@ 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; + }, + /** + * Function to get media content to feed the expose + * @param {type} _value + * @returns {Array|Array.getMedia.mediaContent} + */ + getMedia: function (_value) + { + var base_url = egw.webserverUrl.match(/^\//,'ig')?egw(window).window.location.origin :''; + var mediaContent = []; + if (_value) + { + mediaContent = [{ + title: this.options.label, + href: base_url + _value, + type: this.options.type + "/*", + thumbnail: base_url + _value + }]; + } + return mediaContent; + }, set_value: function(_value) { if (!_value) _value = ""; if (!this.options.no_lang) _value = this.egw().lang(_value); @@ -130,17 +232,24 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], this.span[0], this.options.href ? this.options.extra_link_target : '_blank' ); - if(this.options.extra_link_popup) + if(this.options.extra_link_popup || this.options.mime) { - var href = this.options.href; - var title = this.options.extra_link_title; - var popup = this.options.extra_link_popup; - jQuery('a',this.span) - .click(function(e) { - egw.open_link(href, title,popup); - e.preventDefault(); - return false; - }); + var self= this; + var $span = this.options.mime_data? jQuery(this.span): jQuery('a',this.span); + $span.click(function(e) { + if (self.options.expose_view && typeof self.options.mime !='undefined' && self.options.mime.match(/video\/|image\/|audio\//,'ig')) + { + // Do not show thumbnail indicator for single expose view + self.expose_options.thumbnailIndicators = false; + self._init_blueimp_gallery(e,self.options.href); + } + else + { + egw(window).open_link(self.options.mime_data || self.options.href, self.options.extra_link_target, self.options.extra_link_popup, null, null, self.options.mime); + } + e.preventDefault(); + return false; + }); } }, @@ -216,6 +325,6 @@ var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], _nodes[0].setAttribute("class", _values["class"]); } } -}); +})); et2_register_widget(et2_description, ["description", "label"]); diff --git a/etemplate/js/expose.js b/etemplate/js/expose.js index f5823b345d..9585ab995c 100644 --- a/etemplate/js/expose.js +++ b/etemplate/js/expose.js @@ -373,6 +373,9 @@ function expose (widget) set_value:function (_value) { + if (typeof this._super == 'undefined') return; + + this._super.apply(this,arguments); // Do not run set value of expose if expose_view is not set // it causes a wired error on nested image widgets which // seems the expose is not its child widget @@ -380,7 +383,7 @@ function expose (widget) { return; } - this._super.apply(this,arguments); + var self=this; // If the media type is not supported do not bind the click handler @@ -488,6 +491,7 @@ function expose (widget) * @param {DOMNode} slide */ expose_onslide: function (gallery, index, slide){ + if (typeof this._super == 'undefined') return; // First let parent try this._super.apply(this, arguments); var nm = find_nextmatch(this); diff --git a/filemanager/js/app.js b/filemanager/js/app.js index 3a96760661..4d48a474db 100644 --- a/filemanager/js/app.js +++ b/filemanager/js/app.js @@ -745,7 +745,7 @@ app.classes.filemanager = AppJS.extend( } else { - egw.open({path: path, type: data.data.mime}, 'file'); + egw.open({path: path, type: data.data.mime}, 'file','view',null,'_browser'); } return false; }, diff --git a/jdots/js/fw_mobile.js b/jdots/js/fw_mobile.js index 170d290ce3..0f23d55fcd 100644 --- a/jdots/js/fw_mobile.js +++ b/jdots/js/fw_mobile.js @@ -139,11 +139,19 @@ */ init:function(_wnd) { + var self = this; this.$container = $j(document.createElement('div')).addClass('egw_fw_mobile_popup_container egw_fw_mobile_popup_loader'); this.$iFrame = $j(document.createElement('iframe')) .addClass('egw_fw_mobile_popupFrame') .appendTo(this.$container); this.$container.appendTo('body'); + // Create close button for popups + var $closeBtn = $j(document.createElement('span')) + .addClass('egw_fw_mobile_popup_close') + .click(function (){self.close(framework.popup_idx(self.$iFrame[0].contentWindow));}); + if (framework.getUserAgent() === 'iOS' && !framework.isNotFullScreen()) $closeBtn.css({top:"15px"}); + this.$container.prepend($closeBtn); + this.windowOpener = _wnd; }, @@ -168,20 +176,8 @@ this.$iFrame.on('onpopupload', function (){ var popupWindow = this.contentWindow; var $appHeader = $j(popupWindow.document).find('#divAppboxHeader'); - var $closeBtn = $appHeader.find('.egw_fw_mobile_popup_close'); - if ($closeBtn.length == 0) - { - $closeBtn = $j(document.createElement('span')) - .addClass('egw_fw_mobile_popup_close') - .click(function (){self.close(framework.popup_idx(self.$iFrame[0].contentWindow));}); - if ($appHeader.length > 0) - { - $appHeader.addClass('egw_fw_mobile_popup_appHeader'); - // Add close button only after everything is loaded - setTimeout(function(){$appHeader.prepend($closeBtn);},0); - } - } - + $appHeader.addClass('egw_fw_mobile_popup_appHeader'); + //Remove the loading class self.$container.removeClass('egw_fw_mobile_popup_loader'); self.$iFrame.css({visibility:'visible'}); @@ -204,24 +200,6 @@ // If the popup is not an et2_popup else if ($et2_container.length == 0) { - // Since the popup is not et2, there's no css loaded - // therefore we need to add the style - var close_btn_styles = {width: "32px", - height: "32px", - float:"right", - "background-image": 'url(' +egw.webserverUrl+ '/pixelegg/images/cancelled.png)', - "-webkit-filter": "contrast(2)", - "background-repeat": "no-repeat", - "z-index": 999, - "padding-right": "5px"}; - - var $closeBtn = $j(document.createElement('span')) - .addClass('egw_fw_mobile_popup_close') - .click(function (){self.close(framework.popup_idx(self.$iFrame[0].contentWindow));}) - .css(close_btn_styles); - setTimeout(function(){ - $j('body',popupWindow.document).prepend($closeBtn); - },0); self.$container.removeClass('egw_fw_mobile_popup_loader'); self.$iFrame.css({visibility:'visible'}); } @@ -401,6 +379,7 @@ */ orientation: function () { + if (!this.isLandscape()) this.toggleMenu('on'); this.arrangeToolbar(this.isLandscape()?'landscape':'portrait'); }, diff --git a/mail/inc/class.mail_hooks.inc.php b/mail/inc/class.mail_hooks.inc.php index 3f1ec938e6..f38fb243b5 100644 --- a/mail/inc/class.mail_hooks.inc.php +++ b/mail/inc/class.mail_hooks.inc.php @@ -93,7 +93,10 @@ class mail_hooks 'message/rfc822' => array( 'menuaction' => 'mail.mail_ui.importMessageFromVFS2DraftAndDisplay', 'mime_url' => 'formData[file]', + 'mime_data' => 'formData[data]', + 'formData[type]' => 'message/rfc822', 'mime_popup' => '870xavailHeight', + 'mime_target' => '_blank' ), ), 'entry' => 'Mail', diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php index 18845c5115..16f96bb8c6 100644 --- a/mail/inc/class.mail_ui.inc.php +++ b/mail/inc/class.mail_ui.inc.php @@ -2168,14 +2168,20 @@ class mail_ui $attachmentHTML[$key]['filename'] = $x; } } -//error_log(array2string($value)); -//error_log(strtoupper($value['mimeType']) .'<->'. mime_magic::filename2mime($attachmentHTML[$key]['filename'])); + //error_log(array2string($value)); + //error_log(strtoupper($value['mimeType']) .'<->'. mime_magic::filename2mime($attachmentHTML[$key]['filename'])); if (strtoupper($value['mimeType']=='APPLICATION/OCTET-STREAM')) $value['mimeType'] = mime_magic::filename2mime($attachmentHTML[$key]['filename']); $attachmentHTML[$key]['type']=$value['mimeType']; $attachmentHTML[$key]['mimetype']=mime_magic::mime2label($value['mimeType']); + list(, $acc_id) = explode(self::$delimiter, $rowID); + + $attachmentHTML[$key]['mime_data'] = egw_link::set_data($value['mimeType'], 'emailadmin_imapbase::getAttachmentAccount', array( + $acc_id, $mailbox, $uid, $value['partID'], $value['is_winmail'], true + )); $attachmentHTML[$key]['size']=egw_vfs::hsize($value['size']); $attachmentHTML[$key]['attachment_number']=$key; $attachmentHTML[$key]['partID']=$value['partID']; + $attachmentHTML[$key]['mail_id'] = $rowID; $attachmentHTML[$key]['winmailFlag']=$value['is_winmail']; $attachmentHTML[$key]['classSaveAllPossiblyDisabled'] = "mail_DisplayNone"; @@ -2185,7 +2191,7 @@ class mail_ui $linkData = array ( 'menuaction' => 'mail.mail_ui.displayMessage', - //'mode' => 'display', //message/rfc822 attachments should be opened in display mode + 'mode' => 'display', //message/rfc822 attachments should be opened in display mode 'id' => $rowID, 'part' => $value['partID'], 'is_winmail' => $value['is_winmail'] @@ -2252,6 +2258,14 @@ class mail_ui $linkView = "window.location.href = '".egw::link('/index.php',$linkData)."';"; break; } + // we either use mime_data for server-side supported mime-types or mime_url for client-side or download + if (empty($attachmentHTML[$key]['mime_data'])) + { + $attachmentHTML[$key]['mime_url'] = egw::link('/index.php',$linkData); + unset($attachmentHTML[$key]['mime_data']); + } + $attachmentHTML[$key]['windowName'] = $windowName; + //error_log(__METHOD__.__LINE__.$linkView); $attachmentHTML[$key]['link_view'] = ''. ($value['name'] ? ( $filename ? $filename : $value['name'] ) : lang('(no subject)')). @@ -2532,8 +2546,7 @@ class mail_ui html::safe_content_header($attachment['attachment'], $filename, $attachment['type'], $size=0, True, $_GET['mode'] == "save"); echo $attachment['attachment']; - $GLOBALS['egw']->common->egw_exit(); - exit; + common::egw_exit(); } @@ -3295,8 +3308,6 @@ class mail_ui if ($importfailed === false) { $mailObject = new egw_mailer(); - $Header = ''; - $Body = ''; try { $this->mail_bo->parseFileIntoMailObject($mailObject, $tmpFileName); @@ -3381,15 +3392,20 @@ class mail_ui //error_log(__METHOD__.__LINE__.':'.array2string($formData).' Mode:'.$mode.'->'.function_backtrace()); $draftFolder = $this->mail_bo->getDraftFolder(false); $importID = mail_bo::getRandomString(); + + // handling for mime-data hash + if (!empty($formData['data'])) + { + $formData['file'] = 'egw-data://'.$formData['data']; + } // name should be set to meet the requirements of checkFileBasics - if (parse_url($formData['file'],PHP_URL_SCHEME) == 'vfs' && (!isset($formData['name']) || empty($formData['name']))) + if (parse_url($formData['file'],PHP_URL_SCHEME) == 'vfs' && empty($formData['name'])) { $buff = explode('/',$formData['file']); - $suffix = ''; if (is_array($buff)) $formData['name'] = array_pop($buff); // take the last part as name } // type should be set to meet the requirements of checkFileBasics - if (parse_url($formData['file'],PHP_URL_SCHEME) == 'vfs' && (!isset($formData['type']) || empty($formData['type']))) + if (parse_url($formData['file'],PHP_URL_SCHEME) == 'vfs' && empty($formData['type'])) { $buff = explode('.',$formData['file']); $suffix = ''; @@ -3419,18 +3435,12 @@ class mail_ui { $linkData['mode']=$mode; } - + egw::redirect_link('/index.php',$linkData); } catch (egw_exception_wrong_userinput $e) { - $linkData = array - ( - 'menuaction' => 'mail.mail_ui.importMessage', - 'msg' => htmlspecialchars($e->getMessage()), - ); + egw_framework::window_close($e->getMessage()); } - egw::redirect_link('/index.php',$linkData); - exit; } /** diff --git a/mail/js/app.js b/mail/js/app.js index b503b2d6f1..eaee0ff023 100644 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -3682,6 +3682,13 @@ app.classes.mail = AppJS.extend( */ compose_resizeHandler: function() { + // Do not resize compose dialog if it's running on mobile device + // in this case user would be able to edit mail body by scrolling down, + // which is more convenient on small devices. Also resize mailbody with + // ckeditor may causes performance regression, especially on devices with + // very limited resources and slow proccessor. + if (egwIsMobile()) return; + var bodyH = egw_getWindowInnerHeight(); var textArea = this.et2.getWidgetById('mail_plaintext'); var $headerSec = jQuery('.mailComposeHeaderSection'); diff --git a/mail/templates/default/app.css b/mail/templates/default/app.css index 63d9e2c7ec..927a2106f4 100644 --- a/mail/templates/default/app.css +++ b/mail/templates/default/app.css @@ -813,7 +813,7 @@ blockquote blockquote blockquote blockquote blockquote blockquote{ height: auto; } - div#mail-display.et2_container { + #mail-display.et2_container { min-height: initial; } diff --git a/mail/templates/default/display.xet b/mail/templates/default/display.xet index 62608b421a..8de96bb2af 100644 --- a/mail/templates/default/display.xet +++ b/mail/templates/default/display.xet @@ -19,7 +19,7 @@ - + @@ -51,7 +51,7 @@