Add possibility to attach contact vCard to composed message (#4997)

pull/5566/head^2
Aleksander Machniak 8 years ago
parent 692cb9c63b
commit a487055c5a

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- vcard_attachments: Add possibility to attach contact vCard to composed message (#4997)
- Preserve message internal/received date on import in mbox format (#5559)
- Zipdownload: Fix date format in mbox "From line"
- Possibility to display QR code for contacts data (#5030)

@ -1,9 +1,9 @@
{
"name": "roundcube/vcard_attachments",
"type": "roundcube-plugin",
"description": "This plugin detects vCard attachments/bodies and shows a button(s) to add them to address book",
"description": "Detects vCard attachments and allows to add them to address book. Also allows to attach vCards of your contacts to composed messages",
"license": "GPLv3+",
"version": "3.2",
"version": "4.0",
"authors": [
{
"name": "Thomas Bruederli",

@ -19,5 +19,7 @@
$labels = array();
$labels['addvcardmsg'] = 'Add vCard to addressbook';
$labels['vcardsavefailed'] = 'Unable to save vCard';
$labels['attachvcard'] = 'Attach vCard';
$labels['vcard'] = 'vCard';
?>

@ -15,3 +15,15 @@ p.vcardattachment a {
padding: 0.7em 0.5em 0.3em 42px;
height: 22px;
}
#abookactions a.vcard span {
text-indent: -5000px;
display: inline-block;
height: 22px;
width: 15px;
background: url(../../../../skins/classic/images/messageicons.png) 0 -168px no-repeat;
}
#abookactions a.vcard.disabled span {
opacity: 0.5;
}

@ -14,3 +14,8 @@ p.vcardattachment a {
background: url(vcard_add_contact.png) 6px 2px no-repeat;
padding: 1.2em 0.5em 0.7em 46px;
}
a.listbutton.vcard .inner
{
background-position: center -2107px;
}

@ -1,7 +1,8 @@
<?php
/**
* Detect VCard attachments and show a button to add them to address book
* Detects VCard attachments and show a button to add them to address book
* Adds possibility to attach a contact vcard to mail messages
*
* @license GNU GPLv3+
* @author Thomas Bruederli, Aleksander Machniak
@ -11,16 +12,39 @@ class vcard_attachments extends rcube_plugin
public $task = 'mail';
private $message;
private $vcard_parts = array();
private $vcard_parts = array();
private $vcard_bodies = array();
function init()
{
$rcmail = rcmail::get_instance();
if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
$this->add_hook('message_load', array($this, 'message_load'));
$this->add_hook('template_object_messagebody', array($this, 'html_output'));
}
else if ($rcmail->action == 'upload') {
$this->add_hook('attachment_from_uri', array($this, 'attach_vcard'));
}
else if ($rcmail->action == 'compose' && !$rcmail->output->framed) {
$skin_path = $this->local_skin_path();
$btn_class = strpos($skin_path, 'classic') ? 'button' : 'listbutton';
$this->add_texts('localization', true);
$this->include_stylesheet($skin_path . '/style.css');
$this->include_script('vcardattach.js');
$this->add_button(
array(
'type' => 'link',
'label' => 'vcard_attachments.vcard',
'command' => 'attach-vcard',
'class' => $btn_class . ' vcard disabled',
'classact' => $btn_class . ' vcard',
'title' => 'vcard_attachments.attachvcard',
'innerclass' => 'inner',
),
'compose-contacts-toolbar');
}
else if (!$rcmail->output->framed && (!$rcmail->action || $rcmail->action == 'list')) {
$icon = 'plugins/vcard_attachments/' .$this->local_skin_path(). '/vcard.png';
$rcmail->output->set_env('vcard_icon', $icon);
@ -46,13 +70,14 @@ class vcard_attachments extends rcube_plugin
// the same with message bodies
foreach ((array)$this->message->parts as $part) {
if ($this->is_vcard($part)) {
$this->vcard_parts[] = $part->mime_id;
$this->vcard_parts[] = $part->mime_id;
$this->vcard_bodies[] = $part->mime_id;
}
}
if ($this->vcard_parts)
if ($this->vcard_parts) {
$this->add_texts('localization');
}
}
/**
@ -87,9 +112,9 @@ class vcard_attachments extends rcube_plugin
// add box below message body
$p['content'] .= html::p(array('class' => 'vcardattachment'),
html::a(array(
'href' => "#",
'href' => "#",
'onclick' => "return plugin_vcard_save_contact('" . rcube::JQ($part.':'.$idx) . "')",
'title' => $this->gettext('addvcardmsg'),
'title' => $this->gettext('addvcardmsg'),
),
html::span(null, rcube::Q($display)))
);
@ -222,4 +247,81 @@ class vcard_attachments extends rcube_plugin
return $this->abook = $CONTACTS;
}
/**
* Attaches a contact vcard to composed mail
*/
public function attach_vcard($args)
{
if (preg_match('|^vcard://(.+)$|', $args['uri'], $m)) {
list($cid, $source) = explode('-', $m[1]);
$vcard = $this->get_contact_vcard($source, $cid, $filename);
$params = array(
'filename' => $filename,
'mimetype' => 'text/vcard',
);
if ($vcard) {
$args['attachment'] = rcmail_save_attachment($vcard, null, $args['compose_id'], $params);
}
}
return $args;
}
/**
* Get vcard data for specified contact
*/
private function get_contact_vcard($source, $cid, &$filename = null)
{
$rcmail = rcmail::get_instance();
$source = $rcmail->get_address_book($source);
$contact = $source->get_record($cid, true);
if ($contact) {
$fieldmap = $source ? $source->vcard_map : null;
if (empty($contact['vcard'])) {
$vcard = new rcube_vcard('', RCUBE_CHARSET, false, $fieldmap);
$vcard->reset();
foreach ($contact as $key => $values) {
list($field, $section) = explode(':', $key);
// avoid unwanted casting of DateTime objects to an array
// (same as in rcube_contacts::convert_save_data())
if (is_object($values) && is_a($values, 'DateTime')) {
$values = array($values);
}
foreach ((array) $values as $value) {
if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) {
$vcard->set($field, $value, strtoupper($section));
}
}
}
$contact['vcard'] = $vcard->export();
}
$name = rcube_addressbook::compose_list_name($contact);
$filename = (self::parse_filename($name) ?: 'contact') . '.vcf';
// fix folding and end-of-line chars
$vcard = preg_replace('/\r|\n\s+/', '', $contact['vcard']);
$vcard = preg_replace('/\n/', rcube_vcard::$eol, $vcard);
return rcube_vcard::rfc2425_fold($vcard) . rcube_vcard::$eol;
}
}
/**
* Helper function to convert contact name into filename
*/
static private function parse_filename($str)
{
$str = preg_replace('/[\t\n\r\0\x0B:\/]+\s*/', ' ', $str);
return trim($str, " ./_");
}
}

@ -4,7 +4,7 @@
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) 2012-2014, The Roundcube Dev Team
* Copyright (c) 2012-2016, The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
@ -33,6 +33,40 @@ function plugin_vcard_insertrow(data)
}
}
if (window.rcmail && rcmail.gui_objects.messagelist) {
rcmail.addEventListener('insertrow', function(data, evt) { plugin_vcard_insertrow(data); });
function plugin_vcard_attach()
{
var id, n, contacts = [],
ts = new Date().getTime(),
args = {_uploadid: ts, _id: rcmail.env.compose_id};
for (n=0; n < rcmail.contact_list.selection.length; n++) {
id = rcmail.contact_list.selection[n];
if (id && id.charAt(0) != 'E' && rcmail.env.contactdata[id])
contacts.push(id);
}
if (!contacts.length)
return false;
args._uri = 'vcard://' + contacts.join(',');
// add to attachments list
if (!rcmail.add2attachment_list(ts, {name: '', html: rcmail.get_label('attaching'), classname: 'uploading', complete: false}))
rcmail.file_upload_id = rcmail.set_busy(true, 'attaching');
rcmail.http_post('upload', args);
}
window.rcmail && rcmail.addEventListener('init', function(evt) {
if (rcmail.gui_objects.messagelist)
rcmail.addEventListener('insertrow', function(data, evt) { plugin_vcard_insertrow(data); });
if (rcmail.env.action == 'compose' && rcmail.gui_objects.contactslist) {
rcmail.env.compose_commands.push('attach-vcard');
rcmail.register_command('attach-vcard', function() { plugin_vcard_attach(); });
rcmail.contact_list.addEventListener('select', function(list) {
// TODO: support attaching more than one at once
rcmail.enable_command('attach-vcard', list.selection.length == 1 && rcmail.contact_list.selection[0].charAt(0) != 'E');
});
}
});

@ -109,7 +109,7 @@ if ($plugin['abort']) {
}
// send downlaod headers
header('Content-Type: text/x-vcard; charset='.RCUBE_CHARSET);
header('Content-Type: text/vcard; charset=' . RCUBE_CHARSET);
header('Content-Disposition: attachment; filename="contacts.vcf"');
while ($result && ($row = $result->next())) {

@ -102,13 +102,18 @@ if ($uri) {
&& $RCMAIL->get_user_name() == rawurldecode($url['user'])
) {
$message = new rcube_message($params['_uid'], $params['_mbox']);
if ($message && !empty($message->headers)) {
$attachment = rcmail_save_attachment($message, $params['_part'], $COMPOSE_ID);
}
}
}
if ($message && !empty($message->headers)
&& ($attachment = rcmail_save_attachment($message, $params['_part'], $COMPOSE_ID))
) {
rcmail_attachment_success($attachment, $uploadid);
$plugin = $RCMAIL->plugins->exec_hook('attachment_from_uri', array(
'attachment' => $attachment, 'uri' => $uri, 'compose_id' => $COMPOSE_ID));
if ($plugin['attachment']) {
rcmail_attachment_success($plugin['attachment'], $uploadid);
}
else {
$OUTPUT->command('display_message', $RCMAIL->gettext('filelinkerror'), 'error');

@ -2248,42 +2248,51 @@ function rcmail_save_attachment($message, $pid, $compose_id, $params = array())
$mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
$filename = $params['filename'] ?: rcmail_attachment_name($part);
}
else {
else if (is_object($message)) {
// the whole message requested
$size = $message->size;
$size = $message->size;
$mimetype = 'message/rfc822';
$filename = $params['filename'] ?: 'message_rfc822.eml';
}
else if (is_string($message)) {
// the whole message requested
$size = strlen($message);
$data = $message;
$mimetype = $params['mimetype'];
$filename = $params['filename'];
}
if (!isset($data)) {
// don't load too big attachments into memory
if (!rcube_utils::mem_check($size)) {
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
if ($pid) {
// part body
$message->get_part_body($pid, false, 0, $fp);
}
else {
// complete message
$storage->get_raw_body($message->uid, $fp);
}
// don't load too big attachments into memory
if (!rcube_utils::mem_check($size)) {
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
if ($pid) {
// part body
$message->get_part_body($pid, false, 0, $fp);
fclose($fp);
}
else {
// complete message
$storage->get_raw_body($message->uid, $fp);
return false;
}
fclose($fp);
}
else if ($pid) {
// part body
$data = $message->get_part_body($pid);
}
else {
return false;
// complete message
$data = $storage->get_raw_body($message->uid);
}
}
else if ($pid) {
// part body
$data = $message->get_part_body($pid);
}
else {
// complete message
$data = $storage->get_raw_body($message->uid);
}
$attachment = array(
'group' => $compose_id,
@ -2293,7 +2302,7 @@ function rcmail_save_attachment($message, $pid, $compose_id, $params = array())
'data' => $data,
'path' => $path,
'size' => $path ? filesize($path) : strlen($data),
'charset' => $part ? $part->charset : null,
'charset' => $part ? $part->charset : $params['charset'],
);
$attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment);

@ -710,6 +710,11 @@ table.records-table tr.selected td
background-color: #CC3333;
}
table.records-table tr.selected td a
{
color: #FFFFFF;
}
table.records-table tr.focused td
{
}

@ -1707,6 +1707,7 @@ input.from_address
position: absolute;
margin-right: 5px;
right: 0;
top: 0;
}
#abookactions

@ -49,7 +49,16 @@
<div id="mainscreen">
<div id="compose-contacts">
<div class="boxtitle"><roundcube:label name="contacts" /></div>
<div class="boxtitle">
<roundcube:label name="contacts" />
<div id="abookcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<span style="float:left">&nbsp;</span>
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
<div class="boxlistcontent">
<div class="searchbox">
<img id="searchmenulink" src="/images/icons/glass.png" width="16" height="16" />
@ -60,17 +69,11 @@
<roundcube:object name="addresslist" id="contacts-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="abookactions" class="pagenav">
<div id="abookactions">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="button disabled" classAct="button" content="To &amp;raquo;" />
<roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="button disabled" classAct="button" content="Cc &amp;raquo;" />
<roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="button disabled" classAct="button" content="Bcc &amp;raquo;" />
</div>
<div id="abookcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<span style="float:left">&nbsp;</span>
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
<roundcube:container name="compose-contacts-toolbar" id="compose-contacts-toolbar" />
</div>
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@ -58,7 +58,7 @@
<roundcube:object name="addresslist" id="contacts-table" class="listing iconized" noheader="true" role="listbox" />
</div>
<div class="boxfooter">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="listbutton addto disabled" classAct="listbutton addto" innerClass="inner" content="To+" /><roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="listbutton addcc disabled" classAct="listbutton addcc" innerClass="inner" content="Cc+" /><roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="listbutton addbcc disabled" classAct="listbutton addbcc" innerClass="inner" content="Bcc+" />
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="listbutton addto disabled" classAct="listbutton addto" innerClass="inner" content="To+" /><roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="listbutton addcc disabled" classAct="listbutton addcc" innerClass="inner" content="Cc+" /><roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="listbutton addbcc disabled" classAct="listbutton addbcc" innerClass="inner" content="Bcc+" /><roundcube:container name="compose-contacts-toolbar" id="compose-contacts-toolbar" />
</div>
<div class="boxpagenav">
<roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" label="first" />

Loading…
Cancel
Save