first version of linked attachments

This commit is contained in:
Ralf Becker 2014-12-01 20:14:18 +00:00
parent e8056c388b
commit 63a6ac9a6d
6 changed files with 299 additions and 21 deletions

View File

@ -40,6 +40,29 @@ class mail_compose
"plain"=>"plain", "plain"=>"plain",
"html"=>"html" "html"=>"html"
); );
/**
* Modes for sending files
*
* @var array
*/
static $filemodes = array(
'attach' => array(
'label' => 'Attachment',
'title' => 'Works reliable for total size up to 1-2 MB, might work for 5-10 MB, most likely to fail for >10MB',
),
'link' => array(
'label' => 'Download link',
'title' => 'Link is appended to mail allowing recipients to download currently attached version of files',
),
'share_ro' => array(
'label' => 'Share readonly',
'title' => 'Link is appended to mail allowing recipients to download up to date version of files',
),
'share_rw' => array(
'label' => 'Share writable',
'title' => 'Link is appended to mail allowing recipients to download or modify up to date version of files (EPL only)'
),
);
/** /**
* Instance of mail_bo * Instance of mail_bo
@ -103,18 +126,17 @@ class mail_compose
} }
/** /**
* function compose * Compose dialog
* this function is used to fill the compose dialog with the content provided by $_content
* *
* @var _content array the etemplate content array * @var arra $_content =null etemplate content array
* @var msg string a possible message to be passed and displayed to the userinterface * @var string $msg =null a possible message to be passed and displayed to the userinterface
* @var _focusElement varchar subject, to, body supported * @var string $_focusElement ='to' subject, to, body supported
* @var suppressSigOnTop boolean * @var boolean $suppressSigOnTop =false
* @var isReply boolean * @var boolean $isReply =false
*/ */
function compose(array $_content=null,$msg=null, $_focusElement='to',$suppressSigOnTop=false, $isReply=false) function compose(array $_content=null,$msg=null, $_focusElement='to',$suppressSigOnTop=false, $isReply=false)
{ {
unset($msg); // not used if ($msg) egw_framework::message($msg);
if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed'])) if (!empty($GLOBALS['egw_info']['user']['preferences']['mail']['LastSignatureIDUsed']))
{ {
@ -1103,6 +1125,7 @@ class mail_compose
if (isset($content['mimeType'])) $preserv['mimeType'] = $content['mimeType']; if (isset($content['mimeType'])) $preserv['mimeType'] = $content['mimeType'];
$sel_options['mimeType'] = self::$mimeTypes; $sel_options['mimeType'] = self::$mimeTypes;
$sel_options['priority'] = self::$priorities; $sel_options['priority'] = self::$priorities;
$sel_options['filemode'] = self::$filemodes;
if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3; if (!isset($content['priority']) || empty($content['priority'])) $content['priority']=3;
//$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed //$GLOBALS['egw_info']['flags']['currentapp'] = 'mail';//should not be needed
$etpl = new etemplate_new('mail.compose'); $etpl = new etemplate_new('mail.compose');
@ -1994,9 +2017,9 @@ class mail_compose
* @param egw_mailer $_mailObject * @param egw_mailer $_mailObject
* @param array $_formData * @param array $_formData
* @param array $_identity * @param array $_identity
* @param boolean $_convertLinks * @param boolean $_send =false true: create to send message: false: create to save as draft
*/ */
function createMessage(egw_mailer $_mailObject, array $_formData, array $_identity, $_convertLinks=false) function createMessage(egw_mailer $_mailObject, array $_formData, array $_identity, $_send=false)
{ {
$mail_bo = $this->mail_bo; $mail_bo = $this->mail_bo;
$activeMailProfile = emailadmin_account::read($this->mail_bo->profileID); $activeMailProfile = emailadmin_account::read($this->mail_bo->profileID);
@ -2078,10 +2101,25 @@ class mail_compose
{ {
$signature = mail_bo::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id'))); $signature = mail_bo::merge($signature,array($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')));
} }
if ($_formData['attachments'] && $_formData['filemode'] != 'attach' && $_send)
{
$attachment_links = $this->getAttachmentLinks($_formData['attachments'], $_formData['filemode'],
$_formData['mimeType'] == 'html', array_merge((array)$_formData['to'], (array)$_formData['cc'], (array)$_formData['bcc']));
}
if($_formData['mimeType'] == 'html') if($_formData['mimeType'] == 'html')
{ {
$body = $_formData['body']; $body = $_formData['body'];
if ($attachment_links)
{
if (strpos($body, '<!-- HTMLSIGBEGIN -->') !== false)
{
$body = str_replace('<!-- HTMLSIGBEGIN -->', $attachment_links.'<!-- HTMLSIGBEGIN -->', $body);
}
else
{
$body .= $attachment_links;
}
}
if(!empty($signature)) if(!empty($signature))
{ {
$body .= ($disableRuler ?'<br>':'<hr style="border:1px dotted silver; width:90%;">').$signature; $body .= ($disableRuler ?'<br>':'<hr style="border:1px dotted silver; width:90%;">').$signature;
@ -2095,14 +2133,19 @@ class mail_compose
$_mailObject->setBody($this->convertHTMLToText($_formData['body'],true,true)); $_mailObject->setBody($this->convertHTMLToText($_formData['body'],true,true));
} }
// convert URL Images to inline images - if possible // convert URL Images to inline images - if possible
if ($_convertLinks) mail_bo::processURL2InlineImages($_mailObject, $body); if ($_send) mail_bo::processURL2InlineImages($_mailObject, $body);
if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false) if (strpos($body,"<!-- HTMLSIGBEGIN -->")!==false)
{ {
$body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body); $body = str_replace(array('<!-- HTMLSIGBEGIN -->','<!-- HTMLSIGEND -->'),'',$body);
} }
$_mailObject->setHtmlBody($body, null, false); // false = no automatic alternative, we called setBody() $_mailObject->setHtmlBody($body, null, false); // false = no automatic alternative, we called setBody()
} else { }
else
{
$body = $this->convertHTMLToText($_formData['body'],false); $body = $this->convertHTMLToText($_formData['body'],false);
if ($attachment_links) $body .= $attachment_links;
#$_mailObject->Body = $_formData['body']; #$_mailObject->Body = $_formData['body'];
if(!empty($signature)) { if(!empty($signature)) {
$body .= ($disableRuler ?"\r\n":"\r\n-- \r\n"). $body .= ($disableRuler ?"\r\n":"\r\n-- \r\n").
@ -2165,7 +2208,7 @@ class mail_compose
break; break;
} }
} }
else elseif ($_formData['filemode'] == 'attach')
{ {
if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs') if (isset($attachment['file']) && parse_url($attachment['file'],PHP_URL_SCHEME) == 'vfs')
{ {
@ -2189,6 +2232,66 @@ class mail_compose
} }
} }
/**
* Get html or text containing links to attachments
*
* We only care about file attachments, not forwarded messages or parts
*
* @param array $attachments
* @param string $filemode 'attach', 'link', 'share_ro', 'share_rw'
* @param boolean $html
* @return string might be empty if no file attachments found
*/
protected function getAttachmentLinks(array $attachments, $filemode, $html, $recipients=array())
{
switch ($filemode)
{
case 'attach':
return '';
case 'links':
case 'share_ro':
$func = 'egw_sharing::create';
break;
case 'share_rw':
$func = 'stylite_sharing::create';
break;
}
$links = array();
foreach($attachments as $attachment)
{
$path = $attachment['file'];
if (empty($path)) continue; // we only care about file attachments, not forwarded messages or parts
if (parse_url($attachment['file'],PHP_URL_SCHEME) != 'vfs')
{
$path = $GLOBALS['egw_info']['server']['temp_dir'].SEP.basename($path);
}
$share = call_user_func($func, $path, $attachment['name'], $filemode, $recipients);
$link = egw_sharing::share2link($share);
$name = egw_vfs::basename($attachment['name'] ? $attachment['name'] : $attachment['file']);
if ($html)
{
$links[] = html::a_href($name, $link).' '.egw_vfs::hsize($attachment['size']);
}
else
{
$links[] = $name.' '.egw_vfs::hsize($attachment['size']).': '.$link;
}
}
if (!$links)
{
return null; // no file attachments found
}
elseif ($html)
{
return '<p>'.lang('Download attachments').":</p>\n<ul><li>".implode("</li>\n<li>", $links)."</li></ul>\n";
}
return lang('Download attachments').":\n- ".implode("\n- ", $links)."\n";
}
/** /**
* Save compose mail as draft * Save compose mail as draft
* *

View File

@ -315,12 +315,16 @@ class mail_hooks
), ),
'insertSignatureAtTopOfMessage' => array( 'insertSignatureAtTopOfMessage' => array(
'type' => 'select', 'type' => 'select',
'label' => 'signature at top', 'label' => 'Signature position and visibility',
'help' => 'insert the signature at top of the new (or reply) message when opening compose dialog (you may not be able to switch signatures)', 'help' => 'Should signature be inserted after (standard) or before a reply or inline forward, and should signature be visible and changeable during compose.',
'name' => 'insertSignatureAtTopOfMessage', 'name' => 'insertSignatureAtTopOfMessage',
'values' => $no_yes, 'values' => array(
'0' => 'After reply, visible during compose',
'1' => 'Before reply, visible during compose',
'' => 'Appened after reply before sending',
),
'xmlrpc' => True, 'xmlrpc' => True,
'default'=> 0, 'default'=> '0',
'admin' => False, 'admin' => False,
), ),
'attachVCardAtCompose' => array( 'attachVCardAtCompose' => array(

View File

@ -364,6 +364,22 @@ input[type=button] {
/********************************* /*********************************
************ mailCompose ****** ************ mailCompose ******
*********************************/ *********************************/
div.mailUploadSection {
border-top: 1px solid silver;
margin-top: 16px;
}
div.mailUploadSection > label {
margin-top: -10px;
padding-left: 4px;
padding-right: 12px;
background-color: white;
display: table-caption;
white-space: nowrap;
margin-left: 6px;
}
#mail-compose_filemode {
margin-left: 6px;
}
#mail-compose_selectFromVFSForCompose{ #mail-compose_selectFromVFSForCompose{
width:20px; width:20px;
float:none; float:none;

View File

@ -111,8 +111,8 @@
<hbox disabled="@is_html" class="mailComposeBody"> <hbox disabled="@is_html" class="mailComposeBody">
<textbox multiline="true" rows="40" cols="120" width="100%" span="all" no_lang="1" name="mail_plaintext" id="mail_plaintext"/> <textbox multiline="true" rows="40" cols="120" width="100%" span="all" no_lang="1" name="mail_plaintext" id="mail_plaintext"/>
</hbox> </hbox>
<groupbox class="et2_file mailUploadSection" disabled="@no_griddata"> <vbox class="et2_file mailUploadSection" disabled="@no_griddata">
<caption label="Files"/> <select id="filemode" label="Send files as"/>
<grid id="attachments" width="100%" maxheight="165" class="egwGridView_grid"> <grid id="attachments" width="100%" maxheight="165" class="egwGridView_grid">
<columns> <columns>
<column disabled="!@showtempname" width="10%"/> <column disabled="!@showtempname" width="10%"/>
@ -129,7 +129,7 @@
</row> </row>
</rows> </rows>
</grid> </grid>
</groupbox> </vbox>
</vbox> </vbox>
</template> </template>
</overlay> </overlay>

View File

@ -361,6 +361,22 @@ input[type=button] {
/********************************* /*********************************
************ mailCompose ****** ************ mailCompose ******
*********************************/ *********************************/
div.mailUploadSection {
border-top: 1px solid silver;
margin-top: 16px;
}
div.mailUploadSection > label {
margin-top: -10px;
padding-left: 4px;
padding-right: 12px;
background-color: white;
display: table-caption;
white-space: nowrap;
margin-left: 6px;
}
#mail-compose_filemode {
margin-left: 6px;
}
#mail-compose_selectFromVFSForCompose { #mail-compose_selectFromVFSForCompose {
width: 20px; width: 20px;
float: none; float: none;

View File

@ -199,6 +199,145 @@ class egw_sharing
return $token; return $token;
} }
/**
* Create a new share
*
* @param string $path either path in temp_dir or vfs with optional vfs scheme
* @param string $mode 'link': copy file in users tmp-dir or 'share_ro' share given vfs file, if no vfs behave as 'link'
* @param string $name filename to use for $mode='link', default basename of $path
* @param string|array $recipients one or more recipient email addresses
* @param array $extra =array() extra data to store
* @throw egw_exception_not_found if $path not found
* @throw egw_excpetion_assertion_failed if user temp. directory does not exist and can not be created
* @return array with share data, eg. value for key 'share_token'
*/
public static function create($path, $mode, $name, $recipients, $extra=array())
{
if (!isset(self::$db)) self::$db = $GLOBALS['egw']->db;
if (empty($name)) $name = $path;
$path2tmp =& egw_cache::getSession(__CLASS__, 'path2tmp');
// allow filesystem path only for temp_dir
$temp_dir = $GLOBALS['egw_info']['server']['temp_dir'].'/';
if (substr($path, 0, strlen($temp_dir)) == $temp_dir)
{
$mode = 'link';
}
elseif(parse_url($path, PHP_URL_SCHEME) !== 'vfs')
{
$path = 'vfs://default'.($path[0] == '/' ? '' : '/').$path;
}
// check if file exists and is readable
if (!file_exists($path) || is_readable($path))
{
throw new egw_exception_not_found("'$path' NOT found!");
}
// check if file has been shared before
if (($mode != 'link' || isset($path2tmp[$path])) &&
($share = self::$db->select(self::TABLE, '*', array(
'share_path' => $mode == 'link' ? $path2tmp[$path] : egw_vfs::parse_url($path, PHP_URL_PATH),
'share_owner' => $GLOBALS['egw_info']['user']['account_id'],
)+$extra, __LINE__, __FILE__)->fetch()))
{
// if yes, just add additional recipients
$share['share_recipients'] = $share['share_recipients'] ? explode(',', $share['recipients']) : array();
$need_save = false;
foreach((array)$recipients as $recipient)
{
if (!in_array($recipient, $share['recipients']))
{
$share['recipients'][] = $recipient;
$need_save = true;
}
}
$share['share_recipients'] = implode(',', $share['recipients']);
if ($need_save)
{
self::$db->update(self::TABLE, array(
'share_recipients' => $share['share_recipients'],
), array(
'share_id' => $share['share_id'],
), __LINE__, __FILE__);
}
}
else
{
// if not create new share
if ($mode == 'link')
{
$user_tmp = '/home/'.$GLOBALS['egw_info']['user']['account_lid'].'/.tmp';
if (!egw_vfs::file_exists($user_tmp) && !egw_vfs::mkdir($user_tmp))
{
throw new egw_exception_assertion_failed("Could NOT create temp. directory '$user_tmp'!");
}
$n = 0;
do {
$tmp_file = egw_vfs::concat($user_tmp, ($n?$n.'.':'').egw_vfs::basename($name));
} while(!($fp = egw_vfs::fopen($tmp_file, 'x')) && $n++ < 100);
if ($n >= 100)
{
throw new egw_exception_assertion_failed("Could NOT create temp. file '$tmp_file'!");
}
fclose($fp);
if (!copy($path, egw_vfs::PREFIX.$tmp_file))
{
throw new egw_exception_assertion_failed("Could NOT create temp. file '$tmp_file'!");
}
// store temp. path in session, to be able to add more recipients
$path2tmp[$path] = $tmp_file;
$path = $tmp_file;
}
$i = 0;
while(true) // self::token() can return an existing value
{
try {
self::$db->insert(self::TABLE, $share = array(
'share_token' => self::token(),
'share_path' => egw_vfs::parse_url($path, PHP_URL_PATH),
'share_owner' => $GLOBALS['egw_info']['user']['account_id'],
'share_with' => implode(',', (array)$recipients),
'share_created' => time(),
)+$extra, false, __LINE__, __FILE__);
$share['share_id'] = self::$db->get_last_insert_id(self::TABLE, 'share_id');
break;
}
catch(egw_exception_db $e) {
if ($i++ > 3) throw $e;
unset($e);
}
}
}
return $share;
}
/**
* Generate link from share or share-token
*
* @param string|array $share share or share-token
* @return string
*/
public static function share2link($share)
{
if (is_array($share)) $share = $share['share_token'];
$link = egw::link('/share.php').'/'.$share;
if ($link[0] == '/')
{
$link = ($_SERVER['HTTPS'] ? 'https://' : 'http://').
($GLOBALS['egw_info']['server']['hostname'] ?
$GLOBALS['egw_info']['server']['hostname'] : $_SERVER['HTTP_HOST']).
$link;
}
return $link;
}
} }
if (file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php')) if (file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php'))