1<?php
2
3/**
4 * IMAP modules
5 * @package modules
6 * @subpackage imap
7 */
8
9if (!defined('DEBUG_MODE')) { die(); }
10
11/**
12 * Build a source list for sent folders
13 * @subpackage imap/functions
14 * @param string $callback javascript callback function name
15 * @param array $configured user specific sent folders
16 * @param string $inbox include inbox in search for auto-bcc messages
17 * @return array
18 */
19if (!hm_exists('imap_sent_sources')) {
20function imap_sent_sources($callback, $configured, $inbox) {
21    $sources = array();
22    foreach (Hm_IMAP_List::dump() as $index => $vals) {
23        if (array_key_exists('hide', $vals) && $vals['hide']) {
24            continue;
25        }
26        if (array_key_exists($index, $configured) && array_key_exists('sent', $configured[$index]) && $configured[$index]['sent']) {
27            $sources[] = array('callback' => $callback, 'folder' => bin2hex($configured[$index]['sent']), 'type' => 'imap', 'name' => $vals['name'], 'id' => $index);
28        }
29        elseif ($inbox) {
30            $sources[] = array('callback' => $callback, 'folder' => bin2hex('INBOX'), 'type' => 'imap', 'name' => $vals['name'], 'id' => $index);
31        }
32        else {
33            $sources[] = array('callback' => $callback, 'folder' => bin2hex('SPECIAL_USE_CHECK'), 'nodisplay' => true, 'type' => 'imap', 'name' => $vals['name'], 'id' => $index);
34        }
35    }
36    return $sources;
37}}
38
39/**
40 * Build a source list
41 * @subpackage imap/functions
42 * @param string $callback javascript callback function name
43 * @param array $custom user specific assignments
44 * @return array
45 */
46if (!hm_exists('imap_data_sources')) {
47function imap_data_sources($callback, $custom=array()) {
48    $sources = array();
49    foreach (Hm_IMAP_List::dump() as $index => $vals) {
50        if (array_key_exists('hide', $vals) && $vals['hide']) {
51            continue;
52        }
53        if (!array_key_exists('user', $vals)) {
54            continue;
55        }
56        $sources[] = array('callback' => $callback, 'folder' => bin2hex('INBOX'), 'type' => 'imap', 'name' => $vals['name'], 'id' => $index);
57    }
58    foreach ($custom as $path => $type) {
59        $parts = explode('_', $path, 3);
60        $remove_id = false;
61
62        if ($type == 'add') {
63            $details = Hm_IMAP_List::dump($parts[1]);
64            if ($details) {
65                $sources[] = array('callback' => $callback, 'folder' => $parts[2], 'type' => 'imap', 'name' => $details['name'], 'id' => $parts[1]);
66            }
67        }
68        elseif ($type == 'remove') {
69            foreach ($sources as $index => $vals) {
70                if ($vals['folder'] == $parts[2] && $vals['id'] == $parts[1]) {
71                    $remove_id = $index;
72                    break;
73                }
74            }
75            if ($remove_id !== false) {
76                unset($sources[$remove_id]);
77            }
78        }
79    }
80    return $sources;
81}}
82
83/**
84 * Prepare and format message list data
85 * @subpackage imap/functions
86 * @param array $msgs list of message headers to format
87 * @param object $mod Hm_Output_Module
88 * @return void
89 */
90if (!hm_exists('prepare_imap_message_list')) {
91function prepare_imap_message_list($msgs, $mod, $type) {
92    $style = $mod->get('news_list_style') ? 'news' : 'email';
93    if ($mod->get('is_mobile')) {
94        $style = 'news';
95    }
96    $res = format_imap_message_list($msgs, $mod, $type, $style);
97    $mod->out('formatted_message_list', $res);
98}}
99
100/**
101 * Build HTML for a list of IMAP folders
102 * @subpackage imap/functions
103 * @param array $folders list of folder data
104 * @param mixed $id IMAP server id
105 * @param object $mod Hm_Output_Module
106 * @return string
107 */
108if (!hm_exists('format_imap_folder_section')) {
109function format_imap_folder_section($folders, $id, $output_mod) {
110    $results = '<ul class="inner_list">';
111    $manage = $output_mod->get('imap_folder_manage_link');
112    foreach ($folders as $folder_name => $folder) {
113        $folder_name = bin2hex($folder_name);
114        $results .= '<li class="imap_'.$id.'_'.$output_mod->html_safe($folder_name).'">';
115        if ($folder['children']) {
116            $results .= '<a href="#" class="imap_folder_link expand_link" data-target="imap_'.intval($id).'_'.$output_mod->html_safe($folder_name).'">+</a>';
117        }
118        else {
119            $results .= ' <img class="folder_icon" src="'.Hm_Image_Sources::$folder.'" alt="" width="16" height="16" />';
120        }
121        if (!$folder['noselect']) {
122            $results .= '<a data-id="imap_'.intval($id).'_'.$output_mod->html_safe($folder_name).
123                '" href="?page=message_list&amp;list_path='.
124                urlencode('imap_'.intval($id).'_'.$output_mod->html_safe($folder_name)).
125                '">'.$output_mod->html_safe($folder['basename']).'</a>';
126        }
127        else {
128            $results .= $output_mod->html_safe($folder['basename']);
129        }
130        $results .= '<span class="unread_count unread_imap_'.$id.'_'.$output_mod->html_safe($folder_name).'"></span></li>';
131    }
132    if ($manage) {
133        $results .= '<li class="manage_folders_li"><a class="manage_folder_link" href="'.$manage.'"><img class="folder_icon manage_folder_icon" src="'.Hm_Image_Sources::$cog.'" alt="" width="16" height="16" />'.$output_mod->trans('Manage Folders').'</a>';
134    }
135    $results .= '</ul>';
136    return $results;
137}}
138
139/**
140 * Format a from/to field for message list display
141 * @subpackage imap/functions
142 * @param string $fld field to format
143 * @return string
144 */
145if (!hm_exists('format_imap_from_fld')) {
146function format_imap_from_fld($fld) {
147    $res = array();
148    foreach (process_address_fld($fld) as $vals) {
149        if (trim($vals['label'])) {
150            $res[] = $vals['label'];
151        }
152        elseif (trim($vals['email'])) {
153            $res[] = $vals['email'];
154        }
155    }
156    return implode(', ', $res);
157}}
158
159/**
160 * Format a list of message headers
161 * @subpackage imap/functions
162 * @param array $msg_list list of message headers
163 * @param object $mod Hm_Output_Module
164 * @param mixed $parent_list parent list id
165 * @param string $style list style (email or news)
166 * @return array
167 */
168if (!hm_exists('format_imap_message_list')) {
169function format_imap_message_list($msg_list, $output_module, $parent_list=false, $style='email') {
170    $res = array();
171    if ($msg_list === array(false)) {
172        return $msg_list;
173    }
174    $show_icons = $output_module->get('msg_list_icons');
175    $list_page = $output_module->get('list_page', 0);
176    $list_sort = $output_module->get('list_sort');
177    $list_filter = $output_module->get('list_filter');
178    foreach($msg_list as $msg) {
179        $row_class = 'email';
180        $icon = 'env_open';
181        if (!$parent_list) {
182            $parent_value = sprintf('imap_%d_%s', $msg['server_id'], $msg['folder']);
183        }
184        else {
185            $parent_value = $parent_list;
186        }
187        $id = sprintf("imap_%s_%s_%s", $msg['server_id'], $msg['uid'], $msg['folder']);
188        if (!trim($msg['subject'])) {
189            $msg['subject'] = '[No Subject]';
190        }
191        $subject = $msg['subject'];
192        if ($parent_list == 'sent') {
193            $icon = 'sent';
194            $from = $msg['to'];
195        }
196        else {
197            $from = $msg['from'];
198        }
199        $from = format_imap_from_fld($from);
200        $nofrom = '';
201        if (!trim($from)) {
202            $from = '[No From]';
203            $nofrom = ' nofrom';
204        }
205        $timestamp = strtotime($msg['internal_date']);
206        $date = translate_time_str(human_readable_interval($msg['internal_date']), $output_module);
207        $flags = array();
208        if (!stristr($msg['flags'], 'seen')) {
209            $flags[] = 'unseen';
210            $row_class .= ' unseen';
211            if ($icon != 'sent') {
212                $icon = 'env_closed';
213            }
214        }
215        if (trim($msg['x_auto_bcc']) === 'cypht') {
216            $from = preg_replace("/(\<.+\>)/U", '', $msg['to']);
217            $icon = 'sent';
218        }
219        foreach (array('attachment', 'deleted', 'flagged', 'answered') as $flag) {
220            if (stristr($msg['flags'], $flag)) {
221                $flags[] = $flag;
222            }
223        }
224        $source = $msg['server_name'];
225        $row_class .= ' '.str_replace(' ', '_', $source);
226        if ($msg['folder'] && hex2bin($msg['folder']) != 'INBOX') {
227            $source .= '-'.preg_replace("/^INBOX.{1}/", '', hex2bin($msg['folder']));
228        }
229        $url = '?page=message&uid='.$msg['uid'].'&list_path='.sprintf('imap_%d_%s', $msg['server_id'], $msg['folder']).'&list_parent='.$parent_value;
230        if ($list_page) {
231            $url .= '&list_page='.$output_module->html_safe($list_page);
232        }
233        if ($list_sort) {
234            $url .= '&sort='.$output_module->html_safe($list_sort);
235        }
236        if ($list_filter) {
237            $url .= '&filter='.$output_module->html_safe($list_filter);
238        }
239        if (!$show_icons) {
240            $icon = false;
241        }
242        if ($style == 'news') {
243            $res[$id] = message_list_row(array(
244                    array('checkbox_callback', $id),
245                    array('icon_callback', $flags),
246                    array('subject_callback', $subject, $url, $flags, $icon),
247                    array('safe_output_callback', 'source', $source),
248                    array('safe_output_callback', 'from'.$nofrom, $from),
249                    array('date_callback', $date, $timestamp),
250                ),
251                $id,
252                $style,
253                $output_module,
254                $row_class
255            );
256        }
257        else {
258            $res[$id] = message_list_row(array(
259                    array('checkbox_callback', $id),
260                    array('safe_output_callback', 'source', $source, $icon),
261                    array('safe_output_callback', 'from'.$nofrom, $from),
262                    array('subject_callback', $subject, $url, $flags),
263                    array('date_callback', $date, $timestamp),
264                    array('icon_callback', $flags)
265                ),
266                $id,
267                $style,
268                $output_module,
269                $row_class
270            );
271        }
272    }
273    return $res;
274}}
275
276/**
277 * Process message ids
278 * @subpackage imap/functions
279 * @param array $ids list of ids
280 * @return array
281 */
282if (!hm_exists('process_imap_message_ids')) {
283function process_imap_message_ids($ids) {
284    $res = array();
285    foreach (explode(',', $ids) as $id) {
286        if (preg_match("/imap_(\S+)_(\S+)_(\S+)$/", $id, $matches)) {
287            $server = $matches[1];
288            $uid = $matches[2];
289            $folder = $matches[3];
290            if (!isset($res[$server])) {
291                $res[$server] = array();
292            }
293            if (!isset($res[$server][$folder])) {
294                $res[$server][$folder] = array();
295            }
296            $res[$server][$folder][] = $uid;
297        }
298    }
299    return $res;
300}}
301
302/**
303 * Format a message part row
304 * @subpackage imap/functions
305 * @param string $id message identifier
306 * @param array $vals details of the message
307 * @param object $mod Hm_Output_Module
308 * @param int $level indention level
309 * @param string $part currently selected part
310 * @param string $dl_args base arguments for a download link URL
311 * @param bool $use_icons flag to enable/disable message part icons
312 * @param bool $simmple_view flag to hide complex message structure
313 * @param bool $mobile flag to indicate a mobile browser
314 * @return string
315 */
316if (!hm_exists('format_msg_part_row')) {
317function format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_args, $use_icons=false, $simple_view=false, $mobile=false) {
318    $allowed = array(
319        'textplain',
320        'texthtml',
321        'messagedisposition-notification',
322        'messagedelivery-status',
323        'messagerfc822-headers',
324        'textcsv',
325        'textcss',
326        'textunknown',
327        'textx-vcard',
328        'textcalendar',
329        'textx-vcalendar',
330        'textx-sql',
331        'textx-comma-separated-values',
332        'textenriched',
333        'textrfc822-headers',
334        'textx-diff',
335        'textx-patch',
336        'applicationpgp-signature',
337        'applicationx-httpd-php',
338        'imagepng',
339        'imagesvg+xml',
340        'imagejpg',
341        'imagejpeg',
342        'imagepjpeg',
343        'imagegif',
344    );
345    $icons = array(
346        'text' => 'doc',
347        'image' => 'camera',
348        'application' => 'save',
349        'multipart' => 'folder',
350        'audio' => 'audio',
351        'video' => 'monitor',
352        'binary' => 'save',
353
354        'textx-vcard' => 'calendar',
355        'textcalendar' => 'calendar',
356        'textx-vcalendar' => 'calendar',
357        'applicationics' => 'calendar',
358        'multipartdigest' => 'spreadsheet',
359        'applicationpgp-keys' => 'key',
360        'applicationpgp-signature' => 'key',
361        'multipartsigned' => 'lock',
362        'messagerfc822' => 'env_open',
363        'octetstream' => 'paperclip',
364    );
365    $hidden_parts= array(
366        'multipartdigest',
367        'multipartsigned',
368        'multipartmixed',
369        'messagerfc822',
370    );
371    $lc_type = strtolower($vals['type']).strtolower($vals['subtype']);
372    if ($simple_view) {
373        if (filter_message_part($vals)) {
374            return '';
375        }
376        if (in_array($lc_type, $hidden_parts, true)) {
377            return '';
378        }
379    }
380    if ($level > 6) {
381        $class = 'row_indent_max';
382    }
383    else {
384        $class = 'row_indent_'.$level;
385    }
386    $desc = get_part_desc($vals, $id, $part);
387    $size = get_imap_size($vals);
388    $res = '<tr';
389    if ($id == $part) {
390        $res .= ' class="selected_part"';
391    }
392    $res .= '><td><div class="'.$class.'">';
393    $icon = false;
394    if ($use_icons && array_key_exists($lc_type, $icons)) {
395        $icon = $icons[$lc_type];
396    }
397    elseif ($use_icons && array_key_exists(strtolower($vals['type']), $icons)) {
398        $icon = $icons[strtolower($vals['type'])];
399    }
400    if ($icon) {
401        $res .= '<img class="msg_part_icon" src="'.Hm_Image_Sources::$$icon.'" width="16" height="16" alt="'.$output_mod->trans('Attachment').'" /> ';
402    }
403    else {
404        $res .= '<img class="msg_part_icon msg_part_placeholder" src="'.Hm_Image_Sources::$doc.'" width="16" height="16" alt="'.$output_mod->trans('Attachment').'" /> ';
405    }
406    if (in_array($lc_type, $allowed, true)) {
407        $res .= '<a href="#" class="msg_part_link" data-message-part="'.$output_mod->html_safe($id).'">'.$output_mod->html_safe(strtolower($vals['type'])).
408            ' / '.$output_mod->html_safe(strtolower($vals['subtype'])).'</a>';
409    }
410    else {
411        $res .= $output_mod->html_safe(strtolower($vals['type'])).' / '.$output_mod->html_safe(strtolower($vals['subtype']));
412    }
413    if ($mobile) {
414        $res .= '<div class="part_size">'.$output_mod->html_safe($size);
415        $res .= '</div><div class="part_desc">'.$output_mod->html_safe(decode_fld($desc)).'</div>';
416        $res .= '<div class="download_link"><a href="?'.$dl_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'">'.$output_mod->trans('Download').'</a></div></td></tr>';
417    }
418    else {
419        $res .= '</td><td class="part_size">'.$output_mod->html_safe($size);
420        if (!$simple_view) {
421            $res .= '</td><td class="part_encoding">'.(isset($vals['encoding']) ? $output_mod->html_safe(strtolower($vals['encoding'])) : '').
422                '</td><td class="part_charset">'.(isset($vals['attributes']['charset']) && trim($vals['attributes']['charset']) ? $output_mod->html_safe(strtolower($vals['attributes']['charset'])) : '');
423        }
424        $res .= '</td><td class="part_desc">'.$output_mod->html_safe(decode_fld($desc)).'</td>';
425        $res .= '<td class="download_link"><a href="?'.$dl_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'">'.$output_mod->trans('Download').'</a></td></tr>';
426    }
427    return $res;
428}}
429
430/*
431 * Find a message part description/filename
432 * @param array $vals bodystructure info for this message part
433 * @param int $uid message number
434 * @param string $part_id message part number
435 * @return string
436 */
437if (!hm_exists('get_part_desc')) {
438function get_part_desc($vals, $id, $part) {
439    $desc = '';
440    if (isset($vals['description']) && trim($vals['description'])) {
441        $desc = $vals['description'];
442    }
443    elseif (isset($vals['name']) && trim($vals['name'])) {
444        $desc = $vals['name'];
445    }
446    elseif (isset($vals['filename']) && trim($vals['filename'])) {
447        $desc = $vals['filename'];
448    }
449    elseif (isset($vals['envelope']['subject']) && trim($vals['envelope']['subject'])) {
450        $desc = $vals['envelope']['subject'];
451    }
452    $filename = get_imap_part_name($vals, $id, $part, true);
453    if (!$desc && $filename) {
454        $desc = $filename;
455    }
456    return $desc;
457}}
458
459/*
460 * Get a human readable message size
461 * @param array $vals bodystructure info for this message part
462 * @return string
463 */
464if (!hm_exists('get_imap_size')) {
465function get_imap_size($vals) {
466    if (!array_key_exists('size', $vals) || !$vals['size']) {
467        return '';
468    }
469    $size = intval($vals['size']);
470    switch (true) {
471        case $size > 1000:
472            $size = $size/1000;
473            $label = 'KB';
474            break;
475        case $size > 1000000:
476            $size = $size/1000000;
477            $label = 'MB';
478            break;
479        case $size > 1000000000:
480            $size = $size/1000000000;
481            $label = 'GB';
482            break;
483        default:
484            $label = 'B';
485    }
486    return sprintf('%s %s', round($size, 2), $label);
487}}
488
489/**
490 * Format the message part section of the message view page
491 * @subpackage imap/functions
492 * @param array $struct message structure
493 * @param object $mod Hm_Output_Module
494 * @param string $part currently selected message part id
495 * @param string $dl_link base arguments for a download link
496 * @param int $level indention level
497 * @return string
498 */
499if (!hm_exists('format_msg_part_section')) {
500function format_msg_part_section($struct, $output_mod, $part, $dl_link, $level=0) {
501    $res = '';
502    $simple_view = $output_mod->get('simple_msg_part_view', false);
503    $use_icons = $output_mod->get('use_message_part_icons', false);
504    $mobile = $output_mod->get('is_mobile');
505    if ($mobile) {
506        $simple_view = true;
507    }
508    foreach ($struct as $id => $vals) {
509        if (is_array($vals) && isset($vals['type'])) {
510            $row = format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_link, $use_icons, $simple_view, $mobile);
511            if (!$row) {
512                $level--;
513            }
514            $res .= $row;
515            if (isset($vals['subs'])) {
516                $res .= format_msg_part_section($vals['subs'], $output_mod, $part, $dl_link, ($level + 1));
517            }
518        }
519        else {
520            if (is_array($vals) && count($vals) == 1 && isset($vals['subs'])) {
521                $res .= format_msg_part_section($vals['subs'], $output_mod, $part, $dl_link, $level);
522            }
523        }
524    }
525    return $res;
526}}
527
528/**
529 * Filter out message parts that are not attachments
530 * @param array message structure
531 * @return bool
532 */
533if (!hm_exists('filter_message_part')) {
534function filter_message_part($vals) {
535    if (array_key_exists('disposition', $vals) && is_array($vals['disposition']) && array_key_exists('inline', $vals['disposition'])) {
536        return true;
537    }
538    if (array_key_exists('type', $vals) && $vals['type'] == 'multipart') {
539        return true;
540    }
541    return false;
542}}
543
544/**
545 * Sort callback to sort by internal date
546 * @subpackage imap/functions
547 * @param array $a first message detail
548 * @param array $b second message detail
549 * @return int
550 */
551if (!hm_exists('sort_by_internal_date')) {
552function sort_by_internal_date($a, $b) {
553    if ($a['internal_date'] == $b['internal_date']) return 0;
554    return (strtotime($a['internal_date']) < strtotime($b['internal_date']))? -1 : 1;
555}}
556
557/**
558 * Merge IMAP search results
559 * @subpackage imap/functions
560 * @param array $ids IMAP server ids
561 * @param string $search_type
562 * @param object $session session object
563 * @param object $hm_cache cache object
564 * @param array $folders list of folders to search
565 * @param int $limit max results
566 * @param array $terms list of search terms
567 * @param bool $sent flag to fetch auto-bcc'ed messages
568 * @return array
569 */
570if (!hm_exists('merge_imap_search_results')) {
571function merge_imap_search_results($ids, $search_type, $session, $hm_cache, $folders = array('INBOX'), $limit=0, $terms=array(), $sent=false) {
572    $msg_list = array();
573    $connection_failed = false;
574    $sent_results = array();
575    $status = array();
576    foreach($ids as $index => $id) {
577        $id = intval($id);
578        $cache = Hm_IMAP_List::get_cache($hm_cache, $id);
579        $imap = Hm_IMAP_List::connect($id, $cache);
580        if (imap_authed($imap)) {
581            $server_details = Hm_IMAP_List::dump($id);
582            $folder = $folders[$index];
583            if ($sent) {
584                $sent_folder = $imap->get_special_use_mailboxes('sent');
585                if (array_key_exists('sent', $sent_folder)) {
586                    list($sent_status, $sent_results) = merge_imap_search_results($ids, $search_type, $session, $hm_cache, array($sent_folder['sent']), $limit, $terms, false);
587                    $status = array_merge($status, $sent_status);
588                }
589                if ($folder == 'SPECIAL_USE_CHECK') {
590                    continue;
591                }
592            }
593            if ($imap->select_mailbox($folder)) {
594                $status['imap_'.$id.'_'.bin2hex($folder)] = $imap->folder_state;
595                if (!empty($terms)) {
596                    foreach ($terms as $term) {
597                        if (preg_match('/(?:[^\x00-\x7F])/', $term[1]) === 1) {
598                            $imap->search_charset = 'UTF-8';
599                            break;
600                        }
601                    }
602                    if ($sent) {
603                        $msgs = $imap->search($search_type, false, $terms, array(), true, false, true);
604                    }
605                    else {
606                        $msgs = $imap->search($search_type, false, $terms);
607                    }
608                }
609                else {
610                    $msgs = $imap->search($search_type);
611                }
612                if ($msgs) {
613                    if ($limit) {
614                        rsort($msgs);
615                        $msgs = array_slice($msgs, 0, $limit);
616                    }
617                    foreach ($imap->get_message_list($msgs) as $msg) {
618                        if (array_key_exists('content-type', $msg) && stristr($msg['content-type'], 'multipart/mixed')) {
619                            $msg['flags'] .= ' \Attachment';
620                        }
621                        if (stristr($msg['flags'], 'deleted')) {
622                            continue;
623                        }
624                        $msg['server_id'] = $id;
625                        $msg['folder'] = bin2hex($folder);
626                        $msg['server_name'] = $server_details['name'];
627                        $msg_list[] = $msg;
628                    }
629                }
630            }
631        }
632        else {
633            $connection_failed = true;
634        }
635    }
636    $session->set('imap_folder_status', $status);
637    if ($connection_failed && empty($msg_list)) {
638        return array(array(), false);
639    }
640    if (count($sent_results) > 0) {
641        $msg_list = array_merge($msg_list, $sent_results);
642    }
643    return array($status, $msg_list);
644}}
645
646/**
647 * Replace inline images in an HTML message part
648 * @subpackage imap/functions
649 * @param string $txt HTML
650 * @param string $uid message id
651 * @param array $struct message structure array
652 * @param object $imap IMAP server object
653 */
654if (!hm_exists('add_attached_images')) {
655function add_attached_images($txt, $uid, $struct, $imap) {
656    if (preg_match_all("/src=('|\"|)cid:([^\s'\"]+)/", $txt, $matches)) {
657        $cids = array_pop($matches);
658        foreach ($cids as $id) {
659            $part = $imap->search_bodystructure($struct, array('id' => $id, 'type' => 'image'), true);
660            $part_ids = array_keys($part);
661            $part_id = array_pop($part_ids);
662            $img = $imap->get_message_content($uid, $part_id, false, $part[$part_id]);
663            $txt = str_replace('cid:'.$id, 'data:image/'.$part[$part_id]['subtype'].';base64,'.base64_encode($img), $txt);
664        }
665    }
666    return $txt;
667}}
668
669/**
670 * Check for and do an Oauth2 token reset if needed
671 * @subpackage imap/functions
672 * @param array $server imap server data
673 * @param object $config site config object
674 * @return mixed
675 */
676if (!hm_exists('imap_refresh_oauth2_token')) {
677function imap_refresh_oauth2_token($server, $config) {
678    if ((int) $server['expiration'] <= time()) {
679        $oauth2_data = get_oauth2_data($config);
680        $details = array();
681        if ($server['server'] == 'imap.gmail.com') {
682            $details = $oauth2_data['gmail'];
683        }
684        elseif ($server['server'] == 'imap-mail.outlook.com') {
685            $details = $oauth2_data['outlook'];
686        }
687        if (!empty($details)) {
688            $oauth2 = new Hm_Oauth2($details['client_id'], $details['client_secret'], $details['client_uri']);
689            $result = $oauth2->refresh_token($details['refresh_uri'], $server['refresh_token']);
690            if (array_key_exists('access_token', $result)) {
691                return array(strtotime(sprintf('+%d seconds', $result['expires_in'])), $result['access_token']);
692            }
693        }
694    }
695    return array();
696}}
697
698/**
699 * Copy/Move messages on the same IMAP server
700 * @subpackage imap/functions
701 * @param array $ids list of message ids with server and folder info
702 * @param string $action action type, copy or move
703 * @param object $hm_cache system cache
704 * @param array $dest_path imap id and folder to copy/move to
705 * @return int count of messages moved
706 */
707if (!hm_exists('imap_move_same_server')) {
708function imap_move_same_server($ids, $action, $hm_cache, $dest_path) {
709    $moved = array();
710    $keys = array_keys($ids);
711    $server_id = array_pop($keys);
712    $cache = Hm_IMAP_List::get_cache($hm_cache, $server_id);
713    $imap = Hm_IMAP_List::connect($server_id, $cache);
714    foreach ($ids[$server_id] as $folder => $msgs) {
715        if (imap_authed($imap) && $imap->select_mailbox(hex2bin($folder))) {
716            if ($imap->message_action(strtoupper($action), $msgs, hex2bin($dest_path[2]))) {
717                foreach ($msgs as $msg) {
718                    $moved[]  = sprintf('imap_%s_%s_%s', $server_id, $msg, $folder);
719                }
720            }
721        }
722    }
723    return $moved;
724}}
725
726/**
727 * Copy/Move messages on different IMAP servers
728 * @subpackage imap/functions
729 * @param array $ids list of message ids with server and folder info
730 * @param string $action action type, copy or move
731 * @param array $dest_path imap id and folder to copy/move to
732 * @param object $hm_cache cache interface
733 * @return int count of messages moved
734 */
735if (!hm_exists('imap_move_different_server')) {
736function imap_move_different_server($ids, $action, $dest_path, $hm_cache) {
737    $moved = array();
738    $cache = Hm_IMAP_List::get_cache($hm_cache, $dest_path[1]);
739    $dest_imap = Hm_IMAP_List::connect($dest_path[1], $cache);
740    if ($dest_imap) {
741        foreach ($ids as $server_id => $folders) {
742            $cache = Hm_IMAP_List::get_cache($hm_cache, $server_id);
743            $imap = Hm_IMAP_List::connect($server_id, $cache);
744            foreach ($folders as $folder => $msg_ids) {
745                if (imap_authed($imap) && $imap->select_mailbox(hex2bin($folder))) {
746                    foreach ($msg_ids as $msg_id) {
747                        $detail = $imap->get_message_list(array($msg_id));
748                        if (array_key_exists($msg_id, $detail)) {
749                            if (stristr($detail[$msg_id]['flags'], 'seen')) {
750                                $seen = true;
751                            }
752                            else {
753                                $seen = false;
754                            }
755                        }
756                        $msg = $imap->get_message_content($msg_id, 0);
757                        $msg = str_replace("\r\n", "\n", $msg);
758                        $msg = str_replace("\n", "\r\n", $msg);
759                        $msg = rtrim($msg)."\r\n";
760                        if (!$seen) {
761                            $imap->message_action('UNREAD', array($msg_id));
762                        }
763                        if ($dest_imap->append_start(hex2bin($dest_path[2]), strlen($msg), $seen)) {
764                            $dest_imap->append_feed($msg."\r\n");
765                            if ($dest_imap->append_end()) {
766                                if ($action == 'move') {
767                                    if ($imap->message_action('DELETE', array($msg_id))) {
768                                        $imap->message_action('EXPUNGE', array($msg_id));
769                                    }
770                                }
771                                $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg_id, $folder);
772                            }
773                        }
774                    }
775                }
776            }
777        }
778    }
779    return $moved;
780}}
781
782/**
783 * Group info about move/copy messages
784 * @subpackage imap/functions
785 * @param array $form move copy input
786 * @return array grouped lists of messages to move/copy
787 */
788if (!hm_exists('process_move_to_arguments')) {
789function process_move_to_arguments($form) {
790    $msg_ids = explode(',', $form['imap_move_ids']);
791    $same_server_ids = array();
792    $other_server_ids = array();
793    $dest_path = explode('_', $form['imap_move_to']);
794    if (count($dest_path) == 3 && $dest_path[0] == 'imap' && in_array($form['imap_move_action'], array('move', 'copy'), true)) {
795        foreach ($msg_ids as $msg_id) {
796            $path = explode('_', $msg_id);
797            if (count($path) == 4 && $path[0] == 'imap') {
798                if (sprintf('%s_%s', $path[0], $path[1]) == sprintf('%s_%s', $dest_path[0], $dest_path[1])) {
799                    $same_server_ids[$path[1]][$path[3]][] = $path[2];
800                }
801                else {
802                    $other_server_ids[$path[1]][$path[3]][] = $path[2];
803                }
804            }
805        }
806    }
807    return array($msg_ids, $dest_path, $same_server_ids, $other_server_ids);
808}}
809
810/**
811 * Get a file extension for a mime type
812 * @subpackage imap/functions
813 * @param string $type primary mime type
814 * @param string $subtype secondary mime type
815 * @todo add tons more type conversions!
816 * @return string
817 */
818if (!hm_exists('get_imap_mime_extension')) {
819function get_imap_mime_extension($type, $subtype) {
820    $extension = $subtype;
821    if ($type == 'multipart' || ($type == 'message' && $subtype == 'rfc822')) {
822        $extension = 'eml';
823    }
824    if ($type == 'text') {
825        switch ($subtype) {
826            case 'plain':
827                $extension = 'txt';
828                break;
829            case 'richtext':
830                $extension = 'rtf';
831                break;
832        }
833    }
834    return '.'.$extension;
835}}
836
837/**
838 * Try to find a filename for a message part download
839 * @subpackage imap/functions
840 * @param array $struct message part structure
841 * @param int $uid message number
842 * @param string $part_id message part number
843 * @param bool $no_default don't return a default value
844 * @return string
845 */
846if (!hm_exists('get_imap_part_name')) {
847function get_imap_part_name($struct, $uid, $part_id, $no_default=false) {
848    $extension = get_imap_mime_extension(strtolower($struct['type']), strtolower($struct['subtype']));
849    if (array_key_exists('file_attributes', $struct) && is_array($struct['file_attributes']) &&
850        array_key_exists('attachment', $struct['file_attributes']) && is_array($struct['file_attributes']['attachment'])) {
851        for ($i=0;$i<count($struct['file_attributes']['attachment']);$i++) {
852            if (strtolower(trim($struct['file_attributes']['attachment'][$i])) == 'filename') {
853                if (array_key_exists(($i+1), $struct['file_attributes']['attachment'])) {
854                    return trim($struct['file_attributes']['attachment'][($i+1)]);
855                }
856            }
857        }
858    }
859
860    if (array_key_exists('disposition', $struct) && is_array($struct['disposition']) && array_key_exists('attachment', $struct['disposition'])) {
861        for ($i=0;$i<count($struct['disposition']['attachment']);$i++) {
862            if (strtolower(trim($struct['disposition']['attachment'][$i])) == 'filename') {
863                if (array_key_exists(($i+1), $struct['disposition']['attachment'])) {
864                    return trim($struct['disposition']['attachment'][($i+1)]);
865                }
866            }
867        }
868    }
869
870    if (array_key_exists('attributes', $struct) && is_array($struct['attributes']) && array_key_exists('name', $struct['attributes'])) {
871        return trim($struct['attributes']['name']);
872    }
873    if (array_key_exists('description', $struct) && trim($struct['description'])) {
874        return trim(str_replace(array("\n", ' '), '_', $struct['description'])).$extension;
875    }
876    if (array_key_exists('name', $struct) && trim($struct['name'])) {
877        return trim($struct['name']);
878    }
879    if ($no_default) {
880        return '';
881    }
882    return 'message_'.$uid.'_part_'.$part_id.$extension;
883}}
884
885/**
886 * @subpackage imap/functions
887 */
888if (!hm_exists('clear_existing_reply_details')) {
889function clear_existing_reply_details($session) {
890    foreach ($session->dump() as $name => $val) {
891        if (substr($name, 0, 19) == 'reply_details_imap_') {
892            $session->del($name);
893        }
894    }
895}}
896
897/**
898 * @subpackage imap/functions
899 * @param object $imap imap library object
900 * @return bool
901 */
902if (!hm_exists('imap_authed')) {
903function imap_authed($imap) {
904    return is_object($imap) && ($imap->get_state() == 'authenticated' || $imap->get_state() == 'selected');
905}}
906
907/**
908 * @subpackage imap/functions
909 */
910if (!hm_exists('process_sort_arg')) {
911function process_sort_arg($sort) {
912    if (!$sort) {
913        return array('ARRIVAL', true);
914    }
915    $rev = false;
916    if (substr($sort, 0, 1) == '-') {
917        $rev = true;
918        $sort = substr($sort, 1);
919    }
920    $sort = strtoupper($sort);
921    if ($sort == 'ARRIVAL' || $sort == 'DATE') {
922        $rev = $rev ? false : true;
923    }
924    return array($sort, $rev);
925}}
926
927/**
928 * @subpackage imap/functions
929 */
930if (!hm_exists('imap_server_type')) {
931function imap_server_type($id) {
932    $type = 'IMAP';
933    $details = Hm_IMAP_List::dump($id);
934    if (is_array($details) && array_key_exists('type', $details)) {
935        $type = strtoupper($details['type']);
936    }
937    return $type;
938}}
939
940/**
941 * @subpackage imap/functions
942 */
943if (!hm_exists('get_list_headers')) {
944function get_list_headers($headers) {
945    $res = array();
946    $list_headers = array('list-archive', 'list-unsubscribe',
947        'list-subscribe', 'list-archive', 'list-post', 'list-help');
948    foreach (lc_headers($headers) as $name => $val) {
949        if (in_array($name, $list_headers, true)) {
950            $res[substr($name, 5)] = process_list_fld($val);
951        }
952    }
953    return $res;
954}}
955
956/**
957 * @subpackage imap/functions
958 */
959if (!hm_exists('process_list_fld')) {
960function process_list_fld($fld) {
961    $res = array('links' => array(), 'email' => array(), 'values' => array());
962    foreach (explode(',', $fld) as $val) {
963        $val = trim(str_replace(array('<', '>'), '', $val));
964        if (preg_match("/^http/", $val)) {
965            $res['links'][] = $val;
966        }
967        elseif (preg_match("/^mailto/", $val)) {
968            $res['email'][] = substr($val, 7);
969        }
970        else {
971            $res['values'][] = $val;
972        }
973    }
974    return $res;
975}}
976
977/**
978 * @subpackage imap/functions
979 */
980if (!hm_exists('format_imap_envelope')) {
981function format_imap_envelope($env, $mod) {
982    $env = lc_headers($env);
983    $res = '<table class="imap_envelope"><colgroup><col class="header_name_col"><col class="header_val_col"></colgroup>';
984    if (array_key_exists('subject', $env) && trim($env['subject'])) {
985        $res .= '<tr class="header_subject"><th colspan="2">'.$mod->html_safe($env['subject']).
986            '</th></tr>';
987    }
988
989    foreach ($env as $name => $val) {
990        if (in_array($name, array('date', 'from', 'to', 'message-id'), true)) {
991            $res .= '<tr><th>'.$mod->html_safe(ucfirst($name)).'</th>'.
992                '<td>'.$mod->html_safe($val).'</td></tr>';
993        }
994    }
995    $res .= '</table>';
996    return $res;
997}}
998
999/**
1000 * @subpackage imap/functions
1001 */
1002if (!hm_exists('format_list_headers')) {
1003function format_list_headers($mod) {
1004    $res = '<tr><th>'.$mod->trans('List').'</th><td>';
1005    $sections = array();
1006    foreach ($mod->get('list_headers') as $name => $vals) {
1007        if (count($vals['email']) > 0 || count($vals['links']) > 0) {
1008            $sources = array();
1009            $section = ' '.$mod->html_safe($name).': ';
1010            foreach ($vals['email'] as $v) {
1011                $sources[] = '<a href="?page=compose&compose_to='.urlencode($mod->html_safe($v)).
1012                    '">'.$mod->trans('email').'</a>';
1013            }
1014            foreach ($vals['links'] as $v) {
1015                $sources[] = '<a href="'.$mod->html_safe($v).'">'.$mod->trans('link').'</a>';
1016            }
1017            $section .= implode(', ', $sources);
1018            $sections[] = $section;
1019        }
1020    }
1021    $res .= implode(' | ', $sections).'</td></tr>';
1022    return $res;
1023}}
1024
1025/**
1026 * @subpackage imap/functions
1027 */
1028if (!hm_exists('decode_folder_str')) {
1029function decode_folder_str($folder) {
1030    $folder_name = false;
1031    $parts = explode('_', $folder, 3);
1032    if (count($parts) == 3) {
1033        $folder_name = hex2bin($parts[2]);
1034    }
1035    return $folder_name;
1036}}
1037
1038/**
1039 * @subpackage imap/functions
1040 */
1041if (!hm_exists('prep_folder_name')) {
1042function prep_folder_name($imap, $folder, $decode_folder=false, $parent=false) {
1043    if ($parent && $decode_folder) {
1044        $parent = decode_folder_str($parent);
1045    }
1046    if ($decode_folder) {
1047        $folder = decode_folder_str($folder);
1048    }
1049    $ns = get_personal_ns($imap);
1050    if (!$folder) {
1051        return false;
1052    }
1053    if ($parent && !$ns['delim']) {
1054        return false;
1055    }
1056    if ($parent) {
1057        $folder = sprintf('%s%s%s', $parent, $ns['delim'], $folder);
1058    }
1059    if ($folder && $ns['prefix'] && substr($folder, 0, strlen($ns['prefix'])) !== $ns['prefix']) {
1060        $folder = sprintf('%s%s', $ns['prefix'], $folder);
1061    }
1062    return $folder;
1063}}
1064
1065/**
1066 * @subpackage imap/functions
1067 */
1068if (!hm_exists('get_personal_ns')) {
1069function get_personal_ns($imap) {
1070    $namespaces = $imap->get_namespaces();
1071    foreach ($namespaces as $ns) {
1072        if ($ns['class'] == 'personal') {
1073            return $ns;
1074        }
1075    }
1076    return array(
1077        'prefix' => false,
1078        'delim'=> false
1079    );
1080}}
1081
1082