Mercurial > hg > rc1
comparison plugins/vcard_attachments/vcard_attachments.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:50:29 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1e000243b222 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * Detects VCard attachments and show a button to add them to address book | |
5 * Adds possibility to attach a contact vcard to mail messages | |
6 * | |
7 * @license GNU GPLv3+ | |
8 * @author Thomas Bruederli, Aleksander Machniak | |
9 */ | |
10 class vcard_attachments extends rcube_plugin | |
11 { | |
12 public $task = 'mail'; | |
13 | |
14 private $message; | |
15 private $vcard_parts = array(); | |
16 private $vcard_bodies = array(); | |
17 | |
18 function init() | |
19 { | |
20 $rcmail = rcmail::get_instance(); | |
21 | |
22 if ($rcmail->action == 'show' || $rcmail->action == 'preview') { | |
23 $this->add_hook('message_load', array($this, 'message_load')); | |
24 $this->add_hook('template_object_messagebody', array($this, 'html_output')); | |
25 } | |
26 else if ($rcmail->action == 'upload') { | |
27 $this->add_hook('attachment_from_uri', array($this, 'attach_vcard')); | |
28 } | |
29 else if ($rcmail->action == 'compose' && !$rcmail->output->framed) { | |
30 $skin_path = $this->local_skin_path(); | |
31 $btn_class = strpos($skin_path, 'classic') ? 'button' : 'listbutton'; | |
32 | |
33 $this->add_texts('localization', true); | |
34 $this->include_stylesheet($skin_path . '/style.css'); | |
35 $this->include_script('vcardattach.js'); | |
36 $this->add_button( | |
37 array( | |
38 'type' => 'link', | |
39 'label' => 'vcard_attachments.vcard', | |
40 'command' => 'attach-vcard', | |
41 'class' => $btn_class . ' vcard disabled', | |
42 'classact' => $btn_class . ' vcard', | |
43 'title' => 'vcard_attachments.attachvcard', | |
44 'innerclass' => 'inner', | |
45 ), | |
46 'compose-contacts-toolbar'); | |
47 } | |
48 else if (!$rcmail->output->framed && (!$rcmail->action || $rcmail->action == 'list')) { | |
49 $icon = 'plugins/vcard_attachments/' .$this->local_skin_path(). '/vcard.png'; | |
50 $rcmail->output->set_env('vcard_icon', $icon); | |
51 $this->include_script('vcardattach.js'); | |
52 } | |
53 | |
54 $this->register_action('plugin.savevcard', array($this, 'save_vcard')); | |
55 } | |
56 | |
57 /** | |
58 * Check message bodies and attachments for vcards | |
59 */ | |
60 function message_load($p) | |
61 { | |
62 $this->message = $p['object']; | |
63 | |
64 // handle attachments vcard attachments | |
65 foreach ((array)$this->message->attachments as $attachment) { | |
66 if ($this->is_vcard($attachment)) { | |
67 $this->vcard_parts[] = $attachment->mime_id; | |
68 } | |
69 } | |
70 // the same with message bodies | |
71 foreach ((array)$this->message->parts as $part) { | |
72 if ($this->is_vcard($part)) { | |
73 $this->vcard_parts[] = $part->mime_id; | |
74 $this->vcard_bodies[] = $part->mime_id; | |
75 } | |
76 } | |
77 | |
78 if ($this->vcard_parts) { | |
79 $this->add_texts('localization'); | |
80 } | |
81 } | |
82 | |
83 /** | |
84 * This callback function adds a box below the message content | |
85 * if there is a vcard attachment available | |
86 */ | |
87 function html_output($p) | |
88 { | |
89 $attach_script = false; | |
90 | |
91 foreach ($this->vcard_parts as $part) { | |
92 $vcards = rcube_vcard::import($this->message->get_part_content($part, null, true)); | |
93 | |
94 // successfully parsed vcards? | |
95 if (empty($vcards)) { | |
96 continue; | |
97 } | |
98 | |
99 // remove part's body | |
100 if (in_array($part, $this->vcard_bodies)) { | |
101 $p['content'] = ''; | |
102 } | |
103 | |
104 foreach ($vcards as $idx => $vcard) { | |
105 // skip invalid vCards | |
106 if (empty($vcard->email) || empty($vcard->email[0])) { | |
107 continue; | |
108 } | |
109 | |
110 $display = $vcard->displayname . ' <'.$vcard->email[0].'>'; | |
111 | |
112 // add box below message body | |
113 $p['content'] .= html::p(array('class' => 'vcardattachment'), | |
114 html::a(array( | |
115 'href' => "#", | |
116 'onclick' => "return plugin_vcard_save_contact('" . rcube::JQ($part.':'.$idx) . "')", | |
117 'title' => $this->gettext('addvcardmsg'), | |
118 ), | |
119 html::span(null, rcube::Q($display))) | |
120 ); | |
121 } | |
122 | |
123 $attach_script = true; | |
124 } | |
125 | |
126 if ($attach_script) { | |
127 $this->include_script('vcardattach.js'); | |
128 $this->include_stylesheet($this->local_skin_path() . '/style.css'); | |
129 } | |
130 | |
131 return $p; | |
132 } | |
133 | |
134 /** | |
135 * Handler for request action | |
136 */ | |
137 function save_vcard() | |
138 { | |
139 $this->add_texts('localization', true); | |
140 | |
141 $uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST); | |
142 $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); | |
143 $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST); | |
144 | |
145 $rcmail = rcmail::get_instance(); | |
146 $message = new rcube_message($uid, $mbox); | |
147 | |
148 if ($uid && $mime_id) { | |
149 list($mime_id, $index) = explode(':', $mime_id); | |
150 $part = $message->get_part_content($mime_id, null, true); | |
151 } | |
152 | |
153 $error_msg = $this->gettext('vcardsavefailed'); | |
154 | |
155 if ($part && ($vcards = rcube_vcard::import($part)) | |
156 && ($vcard = $vcards[$index]) && $vcard->displayname && $vcard->email | |
157 ) { | |
158 $CONTACTS = $this->get_address_book(); | |
159 $email = $vcard->email[0]; | |
160 $contact = $vcard->get_assoc(); | |
161 $valid = true; | |
162 | |
163 // skip entries without an e-mail address or invalid | |
164 if (empty($email) || !$CONTACTS->validate($contact, true)) { | |
165 $valid = false; | |
166 } | |
167 else { | |
168 // We're using UTF8 internally | |
169 $email = rcube_utils::idn_to_utf8($email); | |
170 | |
171 // compare e-mail address | |
172 $existing = $CONTACTS->search('email', $email, 1, false); | |
173 // compare display name | |
174 if (!$existing->count && $vcard->displayname) { | |
175 $existing = $CONTACTS->search('name', $vcard->displayname, 1, false); | |
176 } | |
177 | |
178 if ($existing->count) { | |
179 $rcmail->output->command('display_message', $this->gettext('contactexists'), 'warning'); | |
180 $valid = false; | |
181 } | |
182 } | |
183 | |
184 if ($valid) { | |
185 $plugin = $rcmail->plugins->exec_hook('contact_create', array('record' => $contact, 'source' => null)); | |
186 $contact = $plugin['record']; | |
187 | |
188 if (!$plugin['abort'] && $CONTACTS->insert($contact)) | |
189 $rcmail->output->command('display_message', $this->gettext('addedsuccessfully'), 'confirmation'); | |
190 else | |
191 $rcmail->output->command('display_message', $error_msg, 'error'); | |
192 } | |
193 } | |
194 else { | |
195 $rcmail->output->command('display_message', $error_msg, 'error'); | |
196 } | |
197 | |
198 $rcmail->output->send(); | |
199 } | |
200 | |
201 /** | |
202 * Checks if specified message part is a vcard data | |
203 * | |
204 * @param rcube_message_part Part object | |
205 * | |
206 * @return boolean True if part is of type vcard | |
207 */ | |
208 function is_vcard($part) | |
209 { | |
210 return ( | |
211 // Content-Type: text/vcard; | |
212 $part->mimetype == 'text/vcard' || | |
213 // Content-Type: text/x-vcard; | |
214 $part->mimetype == 'text/x-vcard' || | |
215 // Content-Type: text/directory; profile=vCard; | |
216 ($part->mimetype == 'text/directory' && ( | |
217 ($part->ctype_parameters['profile'] && | |
218 strtolower($part->ctype_parameters['profile']) == 'vcard') | |
219 // Content-Type: text/directory; (with filename=*.vcf) | |
220 || ($part->filename && preg_match('/\.vcf$/i', $part->filename)) | |
221 ) | |
222 ) | |
223 ); | |
224 } | |
225 | |
226 /** | |
227 * Getter for default (writable) addressbook | |
228 */ | |
229 private function get_address_book() | |
230 { | |
231 if ($this->abook) { | |
232 return $this->abook; | |
233 } | |
234 | |
235 $rcmail = rcmail::get_instance(); | |
236 $abook = $rcmail->config->get('default_addressbook'); | |
237 | |
238 // Get configured addressbook | |
239 $CONTACTS = $rcmail->get_address_book($abook, true); | |
240 | |
241 // Get first writeable addressbook if the configured doesn't exist | |
242 // This can happen when user deleted the addressbook (e.g. Kolab folder) | |
243 if ($abook === null || $abook === '' || !is_object($CONTACTS)) { | |
244 $source = reset($rcmail->get_address_sources(true)); | |
245 $CONTACTS = $rcmail->get_address_book($source['id'], true); | |
246 } | |
247 | |
248 return $this->abook = $CONTACTS; | |
249 } | |
250 | |
251 /** | |
252 * Attaches a contact vcard to composed mail | |
253 */ | |
254 public function attach_vcard($args) | |
255 { | |
256 if (preg_match('|^vcard://(.+)$|', $args['uri'], $m)) { | |
257 list($cid, $source) = explode('-', $m[1]); | |
258 | |
259 $vcard = $this->get_contact_vcard($source, $cid, $filename); | |
260 $params = array( | |
261 'filename' => $filename, | |
262 'mimetype' => 'text/vcard', | |
263 ); | |
264 | |
265 if ($vcard) { | |
266 $args['attachment'] = rcmail_save_attachment($vcard, null, $args['compose_id'], $params); | |
267 } | |
268 } | |
269 | |
270 return $args; | |
271 } | |
272 | |
273 /** | |
274 * Get vcard data for specified contact | |
275 */ | |
276 private function get_contact_vcard($source, $cid, &$filename = null) | |
277 { | |
278 $rcmail = rcmail::get_instance(); | |
279 $source = $rcmail->get_address_book($source); | |
280 $contact = $source->get_record($cid, true); | |
281 | |
282 if ($contact) { | |
283 $fieldmap = $source ? $source->vcard_map : null; | |
284 | |
285 if (empty($contact['vcard'])) { | |
286 $vcard = new rcube_vcard('', RCUBE_CHARSET, false, $fieldmap); | |
287 $vcard->reset(); | |
288 | |
289 foreach ($contact as $key => $values) { | |
290 list($field, $section) = explode(':', $key); | |
291 // avoid unwanted casting of DateTime objects to an array | |
292 // (same as in rcube_contacts::convert_save_data()) | |
293 if (is_object($values) && is_a($values, 'DateTime')) { | |
294 $values = array($values); | |
295 } | |
296 | |
297 foreach ((array) $values as $value) { | |
298 if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) { | |
299 $vcard->set($field, $value, strtoupper($section)); | |
300 } | |
301 } | |
302 } | |
303 | |
304 $contact['vcard'] = $vcard->export(); | |
305 } | |
306 | |
307 $name = rcube_addressbook::compose_list_name($contact); | |
308 $filename = (self::parse_filename($name) ?: 'contact') . '.vcf'; | |
309 | |
310 // fix folding and end-of-line chars | |
311 $vcard = preg_replace('/\r|\n\s+/', '', $contact['vcard']); | |
312 $vcard = preg_replace('/\n/', rcube_vcard::$eol, $vcard); | |
313 | |
314 return rcube_vcard::rfc2425_fold($vcard) . rcube_vcard::$eol; | |
315 } | |
316 } | |
317 | |
318 /** | |
319 * Helper function to convert contact name into filename | |
320 */ | |
321 static private function parse_filename($str) | |
322 { | |
323 $str = preg_replace('/[\t\n\r\0\x0B:\/]+\s*/', ' ', $str); | |
324 | |
325 return trim($str, " ./_"); | |
326 } | |
327 } |