Fix VFS save action in mail

This commit is contained in:
Hadi Nategh 2017-11-08 19:07:04 +01:00
parent f53c60c141
commit aaa0d729fb
4 changed files with 190 additions and 180 deletions

View File

@ -2343,9 +2343,6 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
} }
if (is_array($attachments) && count($attachments) > 0) { if (is_array($attachments) && count($attachments) > 0) {
$url_img_vfs = Api\Html::image('filemanager','navbar', lang('Filemanager'), ' height="16"');
$url_img_vfs_save_all = Api\Html::image('mail','save_all', lang('Save all'));
foreach ($attachments as $key => $value) foreach ($attachments as $key => $value)
{ {
if (Mail\Smime::isSmime($value['mimeType'])) continue; if (Mail\Smime::isSmime($value['mimeType'])) continue;
@ -2770,36 +2767,62 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
} }
/** /**
* Ajax function to save message(s) in the vfs * Ajax function to save message(s)/attachment(s) in the vfs
* *
* @param array $ids array of mail ids * @param array $params array of mail ids and action name
* params = array (
* ids => array of string
* action => string
* )
* @param string $path path to save the emails * @param string $path path to save the emails
*/ */
function ajax_vfsSaveMessage ($ids,$path) function ajax_vfsSave ($params,$path)
{ {
$result = $this->vfsSaveMessage($ids, $path);
$response = Api\Json\Response::get(); $response = Api\Json\Response::get();
$response->data($result);
switch ($params['action'])
{
case 'message':
$result = $this->vfsSaveMessages($params['ids'], $path);
break;
case 'attachment':
$result = $this->vfsSaveAttachments($params['ids'], $path);
break;
}
$response->call('app.mail.vfsSaveCallback', $result);
} }
/** /**
* Save an Message in the vfs * Save Message(s) in the vfs
* *
* @param string|array $ids use splitRowID, to separate values * @param string|array $ids use splitRowID, to separate values
* @param string $path path in vfs (no Vfs::PREFIX!), only directory for multiple id's ($ids is an array) * @param string $path path in vfs (no Vfs::PREFIX!), only directory for multiple id's ($ids is an array)
* @return string|boolean javascript eg. to close the selector window if $close is true, or success/fail if $close is false *
* @return array returns an array including message and success result
* array (
* 'msg' => STRING,
* 'success' => BOOLEAN
* )
*/ */
function vfsSaveMessage($ids,$path) function vfsSaveMessages($ids,$path)
{ {
//error_log(__METHOD__.' IDs:'.array2string($ids).' SaveToPath:'.$path); // add mail translation
if (is_array($ids) && !Vfs::is_writable($path) || !is_array($ids) && !Vfs::is_writable(dirname($path)))
{
return 'alert("'.addslashes(lang('%1 is NOT writable by you!',$path)).'"); Egw(window).close();';
}
Api\Translation::add_app('mail'); Api\Translation::add_app('mail');
$res = array ();
$rememberServerID = $this->mail_bo->profileID; // extract dir from the path
$dir = Vfs::is_dir($path) ? $path : Vfs::dirname($path);
// exit if user has no right to the dir
if (!Vfs::is_writable($dir))
{
return array (
'msg' => lang('%1 is NOT writable by you!',$path),
'success' => false
);
}
$preservedServerID = $this->mail_bo->profileID;
foreach((array)$ids as $id) foreach((array)$ids as $id)
{ {
$hA = self::splitRowID($id); $hA = self::splitRowID($id);
@ -2808,31 +2831,44 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
$icServerID = $hA['profileID']; $icServerID = $hA['profileID'];
if ($icServerID && $icServerID != $this->mail_bo->profileID) if ($icServerID && $icServerID != $this->mail_bo->profileID)
{ {
//error_log(__METHOD__.__LINE__.' change Profile to ->'.$icServerID);
$this->changeProfile($icServerID); $this->changeProfile($icServerID);
} }
$message = $this->mail_bo->getMessageRawBody($uid, $partID='', $mailbox); $message = $this->mail_bo->getMessageRawBody($uid, $partID='', $mailbox);
$err=null;
// is multiple messages
if (Vfs::is_dir($path)) if (Vfs::is_dir($path))
{ {
$headers = $this->mail_bo->getMessageHeader($uid,$partID,true,false,$mailbox); $headers = $this->mail_bo->getMessageHeader($uid,$partID,true,false,$mailbox);
$file = $path . '/'.preg_replace('/[\f\n\t\v\\:*#?<>\|]/',"_",$headers['SUBJECT']).'.eml'; $file = $dir . '/'.preg_replace('/[\f\n\t\v\\:*#?<>\|]/',"_",$headers['SUBJECT']).'.eml';
} }
else else
{ {
$file = $path; $file = $path;
} }
// Check if file already exists, then try to assign a none existance filename
$counter = 1;
$tmp_file = $file;
while (Vfs::file_exists($tmp_file))
{
$tmp_file = $file;
$pathinfo = pathinfo(Vfs::basename($tmp_file));
$tmp_file = $dir . '/' . $pathinfo['filename'] . '(' . $counter . ')' . '.' . $pathinfo['extension'];
$counter++;
}
$file = $tmp_file;
if (!($fp = Vfs::fopen($file,'wb')) || !fwrite($fp,$message)) if (!($fp = Vfs::fopen($file,'wb')) || !fwrite($fp,$message))
{ {
$err .= lang('Error saving %1!',$file); $res['msg'] = lang('Error saving %1!',$file);
$succeeded = false; $res['success'] = false;
} }
else else
{ {
$succeeded = true; $res['success'] = true;
} }
if ($fp) fclose($fp); if ($fp) fclose($fp);
if ($succeeded) if ($res['success'])
{ {
unset($headers['SUBJECT']);//already in filename unset($headers['SUBJECT']);//already in filename
$infoSection = Mail::createHeaderInfoSection($headers, 'SUPPRESS', false); $infoSection = Mail::createHeaderInfoSection($headers, 'SUPPRESS', false);
@ -2840,45 +2876,44 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
Vfs::proppatch($file,$props); Vfs::proppatch($file,$props);
} }
} }
if ($rememberServerID != $this->mail_bo->profileID) if ($preservedServerID != $this->mail_bo->profileID)
{ {
//error_log(__METHOD__.__LINE__.' change Profile back to where we came from ->'.$rememberServerID); //change Profile back to where we came from
$this->changeProfile($rememberServerID); $this->changeProfile($preservedServerID);
} }
return $res;
return $succeeded;
} }
/** /**
* Ajax function to store attachments in the vfs * Save attachment(s) in the vfs
* @param string|array $ids '::' delimited mailbox::uid::part-id::is_winmail::name (::name for multiple id's)
* @param string $path path in vfs (no Vfs::PREFIX!), only directory for multiple id's ($ids is an array)
*/
function ajax_vfsSaveAttachment($ids,$path)
{
$result = $this->vfsSaveAttachment($ids, $path);
$response = Api\Json\Response::get();
$response->data($result);
}
/**
* Save an attachment in the vfs
* *
* @param string|array $ids '::' delimited mailbox::uid::part-id::is_winmail::name (::name for multiple id's) * @param string|array $ids '::' delimited mailbox::uid::part-id::is_winmail::name (::name for multiple id's)
* @param string $path path in vfs (no Vfs::PREFIX!), only directory for multiple id's ($ids is an array) * @param string $path path in vfs (no Vfs::PREFIX!), only directory for multiple id's ($ids is an array)
* @return string javascript eg. to close the selector window *
* @return array returns an array including message and success result
* array (
* 'msg' => STRING,
* 'success' => BOOLEAN
* )
*/ */
function vfsSaveAttachment($ids,$path) function vfsSaveAttachments($ids,$path)
{ {
//error_log(__METHOD__.__LINE__.'("'.array2string($ids).'","'.$path."\")');"); $res = array (
'msg' => 'Attachment has been saved successfully.',
'success' => true
);
if (is_array($ids) && !Vfs::is_writable($path) || !is_array($ids) && !Vfs::is_writable(dirname($path))) $dir = Vfs::is_dir($path) ? $path : Vfs::dirname($path);
if (!Vfs::is_writable($dir))
{ {
return 'alert("'.addslashes(lang('%1 is NOT writable by you!',$path)).'"); Egw(window).close();'; return array (
'msg' => lang('%1 is NOT writable by you!',$path),
'success' => false
);
} }
$err=null;
$dupe_count = array(); $preservedServerID = $this->mail_bo->profileID;
$rememberServerID = $this->mail_bo->profileID;
/** /**
* Extract all parameteres from the given id * Extract all parameteres from the given id
@ -2903,7 +2938,6 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
//Examine the first attachment to see if attachment //Examine the first attachment to see if attachment
//is winmail.dat embedded attachments. //is winmail.dat embedded attachments.
$isMultipleDownload=is_array($ids);
$p = $getParams((is_array($ids)?$ids[0]:$ids)); $p = $getParams((is_array($ids)?$ids[0]:$ids));
if ($p['is_winmail']) if ($p['is_winmail'])
{ {
@ -2921,15 +2955,16 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
foreach((array)$ids as $id) foreach((array)$ids as $id)
{ {
$params = $getParams($id); $params = $getParams($id);
// when downloading a single file, name is not set
if (!$params['name']&&isset($_GET['name'])&&!$isMultipleDownload) $params['name'] = $_GET['name'];
if ($params['icServer'] && $params['icServer'] != $this->mail_bo->profileID) if ($params['icServer'] && $params['icServer'] != $this->mail_bo->profileID)
{ {
//error_log(__METHOD__.__LINE__.' change Profile to ->'.$icServerID);
$this->changeProfile($params['icServer']); $this->changeProfile($params['icServer']);
} }
//error_log(__METHOD__.__LINE__.array2string($hA));
$this->mail_bo->reopen($params['mailbox']); $this->mail_bo->reopen($params['mailbox']);
// is multiple attachments
if (Vfs::is_dir($path) || $params['is_winmail'])
{
if ($params['is_winmail']) if ($params['is_winmail'])
{ {
// Try to find the right content for file id // Try to find the right content for file id
@ -2942,36 +2977,45 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
{ {
$attachment = $this->mail_bo->getAttachment($params['uid'],$params['part'],$params['is_winmail'],false); $attachment = $this->mail_bo->getAttachment($params['uid'],$params['part'],$params['is_winmail'],false);
} }
$file = $params['name'];
// when $isMultipleDownload the path holds no filename
while(Vfs::file_exists($path.($file && $isMultipleDownload ? '/'.$file : '')))
{
$dupe_count[$params['name']]++;
$file = pathinfo($params['name'], PATHINFO_FILENAME) .
' ('.($dupe_count[$params['name']] + 1).')' . '.' .
pathinfo($params['name'], PATHINFO_EXTENSION);
} }
$params['name'] = $file; else
//error_log(__METHOD__.__LINE__.array2string($attachment)); {
// when $isMultipleDownload the path holds no filename $attachment = $this->mail_bo->getAttachment($params['uid'],$params['part'],$params['is_winmail'],false);
if (!($fp = Vfs::fopen($file=$path.($params['name'] && $isMultipleDownload ? '/'.$params['name'] : ''),'wb')) || }
$file = $dir. '/' . $attachment['filename'];
$counter = 1;
$tmp_file = $file;
while (Vfs::file_exists($tmp_file))
{
$tmp_file = $file;
$pathinfo = pathinfo(Vfs::basename($tmp_file));
$tmp_file = $dir . '/' . $pathinfo['filename'] . '(' . $counter . ')' . '.' . $pathinfo['extension'];
$counter++;
}
$file = $tmp_file;
if (!($fp = Vfs::fopen($file,'wb')) ||
!fwrite($fp,$attachment['attachment'])) !fwrite($fp,$attachment['attachment']))
{ {
$err .= lang('Error saving %1!',$file); $res['msg'] = lang('Error saving %1!',$file);
$res['success'] = false;
} }
if ($fp) if ($fp)
{ {
fclose($fp); fclose($fp);
} }
} }
$this->mail_bo->closeConnection(); $this->mail_bo->closeConnection();
if ($rememberServerID != $this->mail_bo->profileID)
if ($preservedServerID != $this->mail_bo->profileID)
{ {
//error_log(__METHOD__.__LINE__.' change Profile back to where we came from ->'.$rememberServerID); //change Profile back to where we came from
$this->changeProfile($rememberServerID); $this->changeProfile($preservedServerID);
} }
Framework::window_close(($err?$err:null)); return $res;
} }
/** /**

View File

@ -2893,113 +2893,79 @@ app.classes.mail = AppJS.extend(
egw.openPopup(egw.link('/index.php', get_param), width, height, windowName); egw.openPopup(egw.link('/index.php', get_param), width, height, windowName);
}, },
saveAttachment: function(tag_info, widget) /**
* Callback function to handle vfsSave response messages
*
* @param {type} _data
*/
vfsSaveCallback: function (_data)
{ {
var mailid; egw.message(_data.msg, _data.success ? "success": "error");
var attgrid;
if (this.mail_isMainWindow)
{
mailid = this.mail_currentlyFocussed;//this.et2.getArrayMgr("content").getEntry('mail_id');
var p = widget.getParent();
var cont = p.getArrayMgr("content").data;
attgrid = cont[widget.id.replace(/\[save\]/,'')];
}
else
{
mailid = this.et2.getArrayMgr("content").getEntry('mail_id');
attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments')[widget.id.replace(/\[save\]/,'')];
}
var url = window.egw_webserverUrl+'/index.php?';
url += 'menuaction=mail.mail_ui.getAttachment'; // todo compose for Draft folder
url += '&mode=save';
url += '&id='+mailid;
url += '&part='+attgrid.partID;
url += '&is_winmail='+attgrid.winmailFlag;
url += '&smime_type='+ (attgrid.smime_type?attgrid.smime_type:'');
this.et2._inst.download(url);
}, },
saveAllAttachmentsToZip: function(tag_info, widget) /**
* A handler for saving to VFS/downloading attachments
*
* @param {type} widget
* @param {type} action
* @param {type} row_id
*/
saveAttachmentHandler: function (widget, action, row_id)
{ {
var mailid; var mail_id, attachments;
var attgrid;
if (this.mail_isMainWindow) if (this.mail_isMainWindow)
{ {
mailid = this.mail_currentlyFocussed;//this.et2.getArrayMgr("content").getEntry('mail_id'); mail_id = this.mail_currentlyFocussed;
var p = widget.getParent(); var p = widget.getParent();
var cont = p.getArrayMgr("content").data; attachments = p.getArrayMgr("content").data;
attgrid = cont[widget.id.replace(/\[save_zip\]/,'')];
} }
else else
{ {
mailid = this.et2.getArrayMgr("content").getEntry('mail_id'); mail_id = this.et2.getArrayMgr("content").getEntry('mail_id');
attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments')[widget.id.replace(/\[save\]/,'')]; attachments = this.et2.getArrayMgr("content").getEntry('mail_displayattachments');
}
var url = window.egw_webserverUrl+'/index.php?';
url += 'menuaction=mail.mail_ui.download_zip'; // todo compose for Draft folder
url += '&mode=save';
url += '&id='+mailid;
url += '&smime_type='+ (attgrid.smime_type?attgrid.smime_type:'');
this.et2._inst.download(url);
},
saveAttachmentToVFS: function(tag_info, widget)
{
var mailid;
var attgrid;
if (this.mail_isMainWindow)
{
mailid = this.mail_currentlyFocussed;//this.et2.getArrayMgr("content").getEntry('mail_id');
var p = widget.getParent();
var cont = p.getArrayMgr("content").data;
attgrid = cont[widget.id.replace(/\[saveAsVFS\]/,'')];
}
else
{
mailid = this.et2.getArrayMgr("content").getEntry('mail_id');
attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments')[widget.id.replace(/\[saveAsVFS\]/,'')];
}
var vfs_select = et2_createWidget('vfs-select', {
mode: 'saveas',
method: 'mail.mail_ui.ajax_vfsSaveAttachment',
button_label: egw.lang('Save'),
dialog_title: "Save attchment",
method_id: mailid+'::'+attgrid.partID+'::'+attgrid.winmailFlag,
name: attgrid.filename
});
vfs_select.click();
},
saveAllAttachmentsToVFS: function(tag_info, widget)
{
var mailid;
var attgrid;
if (this.mail_isMainWindow)
{
mailid = this.mail_currentlyFocussed;//this.et2.getArrayMgr("content").getEntry('mail_id');
var p = widget.getParent();
attgrid = p.getArrayMgr("content").data;
}
else
{
mailid = this.et2.getArrayMgr("content").getEntry('mail_id');
attgrid = this.et2.getArrayMgr("content").getEntry('mail_displayattachments');
} }
switch (action)
{
case 'saveOneToVfs':
case 'saveAllToVfs':
var ids = []; var ids = [];
for (var i=0;i<attgrid.length;i++) attachments = action === 'saveOneToVfs' ? [attachments[row_id]] : attachments;
for (var i=0;i<attachments.length;i++)
{ {
if (attgrid[i] != null) ids.push(mailid+'::'+attgrid[i].partID+'::'+attgrid[i].winmailFlag+'::'+attgrid[i].filename); if (attachments[i] != null)
{
ids.push(mail_id+'::'+attachments[i].partID+'::'+attachments[i].winmailFlag+'::'+attachments[i].filename);
}
} }
var vfs_select = et2_createWidget('vfs-select', { var vfs_select = et2_createWidget('vfs-select', {
mode: 'select-dir', mode: action === 'saveOneToVfs' ? 'saveas' : 'select-dir',
method: 'mail.mail_ui.ajax_vfsSaveAttachment', method: 'mail.mail_ui.ajax_vfsSave',
button_label: egw.lang('Save all'), button_label: egw.lang(action === 'saveOneToVfs' ? 'Save' : 'Save all'),
dialog_title: "Save attchments", dialog_title: egw.lang(action === 'saveOneToVfs' ? 'Save attchment' : 'Save attchments'),
method_id: ids.length > 1 ? ids: ids[0] method_id: ids.length > 1 ? {ids:ids, action:'attachment'} : {ids: ids[0], action: 'attachment'},
name: action === 'saveOneToVfs' ? attachments[0]['filename'] : null
}); });
vfs_select.click(); vfs_select.click();
break;
case 'downloadOneAsFile':
case 'downloadAllToZip':
var attachment = attachments[row_id];
var url = window.egw_webserverUrl+'/index.php?';
url += jQuery.param({
menuaction: action === 'downloadOneAsFile' ?
'mail.mail_ui.getAttachment' : 'mail.mail_ui.download_zip',
mode: 'save',
id: mail_id,
part: attachment.partID,
is_winmail: attachment.winmailFlag,
smime_type: (attachment.smime_type ? attachment.smime_type : '')
});
this.et2._inst.download(url);
break;
}
}, },
/** /**
@ -3039,10 +3005,10 @@ app.classes.mail = AppJS.extend(
var vfs_select = et2_createWidget('vfs-select', { var vfs_select = et2_createWidget('vfs-select', {
mode: _elems.length > 1 ? 'select-dir' : 'saveas', mode: _elems.length > 1 ? 'select-dir' : 'saveas',
mime: 'message/rfc822', mime: 'message/rfc822',
method: 'mail.mail_ui.ajax_vfsSaveMessage', method: 'mail.mail_ui.ajax_vfsSave',
button_label: _elems.length>1 ? egw.lang('Save all') : egw.lang('save'), button_label: _elems.length>1 ? egw.lang('Save all') : egw.lang('save'),
dialog_title: "Save email", dialog_title: "Save email",
method_id: _elems.length > 1 ? ids: ids[0], method_id: _elems.length > 1 ? {ids:ids, action:'message'}: {ids: ids[0], action: 'message'},
name: _elems.length > 1 ? names : names[0], name: _elems.length > 1 ? names : names[0],
}); });
vfs_select.click(); vfs_select.click();

View File

@ -78,10 +78,10 @@
<description id="${row}[winmailFlag]" /> <description id="${row}[winmailFlag]" />
<description class="useEllipsis et2_link" id="${row}[filename]" extra_link_target="$row_cont[windowName]" extra_link_popup="$row_cont[popup]" expose_view="true" mime="$row_cont[type]" no_lang="1" mime_data="$row_cont[mime_data]" href="$row_cont[mime_url]"/> <description class="useEllipsis et2_link" id="${row}[filename]" extra_link_target="$row_cont[windowName]" extra_link_popup="$row_cont[popup]" expose_view="true" mime="$row_cont[type]" no_lang="1" mime_data="$row_cont[mime_data]" href="$row_cont[mime_url]"/>
<description align="right" id="${row}[size]" /> <description align="right" id="${row}[size]" />
<buttononly id="${row}[save]" value="save" image="fileexport" onclick="app.mail.saveAttachment"/> <buttononly id="${row}[save]" value="save" image="fileexport" onclick="app.mail.saveAttachmentHandler(widget,'downloadOneAsFile', ${row})"/>
<buttononly id="${row}[saveAsVFS]" value="save" image="filemanager/navbar" onclick="app.mail.saveAttachmentToVFS"/> <buttononly id="${row}[saveAsVFS]" value="save" image="filemanager/navbar" onclick="app.mail.saveAttachmentHandler(widget,'saveOneToVfs', ${row})"/>
<buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_all]" value="save_all" image="mail/save_all" onclick="app.mail.saveAllAttachmentsToVFS"/> <buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_all]" value="save_all" image="mail/save_all" onclick="app.mail.saveAttachmentHandler(widget,'saveAllToVfs', ${row})"/>
<buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_zip]" value="save_zip" image="mail/save_zip" onclick="app.mail.saveAllAttachmentsToZip" label="Save as Zip"/> <buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_zip]" value="save_zip" image="mail/save_zip" onclick="app.mail.saveAttachmentHandler(widget,'downloadAllToZip', ${row})" label="Save as Zip"/>
</row> </row>
</rows> </rows>
</grid> </grid>

View File

@ -62,10 +62,10 @@
<description id="${row}[winmailFlag]" /> <description id="${row}[winmailFlag]" />
<description class="et2_link useEllipsis" id="${row}[filename]" extra_link_target="$row_cont[windowName]" extra_link_popup="$row_cont[popup]" no_lang="1" expose_view="true" mime="$row_cont[type]" mime_data="$row_cont[mime_data]" href="$row_cont[mime_url]"/> <description class="et2_link useEllipsis" id="${row}[filename]" extra_link_target="$row_cont[windowName]" extra_link_popup="$row_cont[popup]" no_lang="1" expose_view="true" mime="$row_cont[type]" mime_data="$row_cont[mime_data]" href="$row_cont[mime_url]"/>
<description align="right" id="${row}[size]"/> <description align="right" id="${row}[size]"/>
<buttononly id="${row}[save]" image="fileexport" onclick="app.mail.saveAttachment"/> <buttononly id="${row}[save]" image="fileexport" onclick="app.mail.saveAttachmentHandler(widget,'downloadOneAsFile', ${row})"/>
<buttononly id="${row}[saveAsVFS]" image="filemanager/navbar" onclick="app.mail.saveAttachmentToVFS"/> <buttononly id="${row}[saveAsVFS]" image="filemanager/navbar" onclick="app.mail.saveAttachmentHandler(widget,'saveOneToVfs', ${row})"/>
<buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_all]" image="mail/save_all" onclick="app.mail.saveAllAttachmentsToVFS"/> <buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_all]" image="mail/save_all" onclick="app.mail.saveAttachmentHandler(widget,'saveAllToVfs', ${row})"/>
<buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_zip]" image="mail/save_zip" onclick="app.mail.saveAllAttachmentsToZip" label="Save as Zip"/> <buttononly class="$row_cont[classSaveAllPossiblyDisabled]" id="${row}[save_zip]" image="mail/save_zip" onclick="app.mail.saveAttachmentHandler(widget,'downloadAllToZip', ${row})" label="Save as Zip"/>
</row> </row>
</rows> </rows>
</grid> </grid>