1: <?php
2:
3: /**
4: * IMAP modules
5: * @package modules
6: * @subpackage imap
7: */
8:
9: if (!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: */
19: if (!hm_exists('imap_sources')) {
20: function imap_sources($mod, $folder = 'sent') {
21: $inbox = $mod->user_config->get('smtp_auto_bcc_setting', DEFAULT_SMTP_AUTO_BCC);
22: $sources = array();
23: $folder = $folder == 'drafts' ? 'draft': $folder;
24: foreach (Hm_IMAP_List::dump() as $index => $vals) {
25: if (array_key_exists('hide', $vals) && $vals['hide']) {
26: continue;
27: }
28: $folders = get_special_folders($mod, $index);
29: if (array_key_exists($folder, $folders) && $folders[$folder]) {
30: $sources[] = array('folder' => bin2hex($folders[$folder]), 'folder_name' => $folders[$folder], 'type' => $vals['type'] ?? 'imap', 'name' => $vals['name'], 'id' => $index);
31: }
32: elseif ($inbox) {
33: $sources[] = array('folder' => bin2hex('INBOX'), 'folder_name' => 'INBOX', 'type' => $vals['type'] ?? 'imap', 'name' => $vals['name'], 'id' => $index);
34: }
35: elseif ($folder=="snoozed"){
36: $sources[] = array('folder' => bin2hex('Snoozed'), 'folder_name' => 'Snoozed', 'type' => $vals['type'] ?? 'imap','name' => $vals['name'],'id' => $index);
37: }
38: else {
39: $sources[] = array('folder' => bin2hex('SPECIAL_USE_CHECK'), 'folder_name' => 'SPECIAL_USE_CHECK', 'nodisplay' => true, 'type' => $vals['type'] ?? 'imap', 'name' => $vals['name'], 'id' => $index);
40: }
41: }
42: return $sources;
43: }}
44:
45: /**
46: * Build a source list
47: * @subpackage imap/functions
48: * @param array $custom user specific assignments
49: * @return array
50: */
51: if (!hm_exists('imap_data_sources')) {
52: function imap_data_sources($custom=array()) {
53: $sources = array();
54: foreach (Hm_IMAP_List::dump() as $index => $vals) {
55: if (array_key_exists('hide', $vals) && $vals['hide']) {
56: continue;
57: }
58: if (!array_key_exists('user', $vals)) {
59: continue;
60: }
61: $mailbox = Hm_IMAP_List::get_mailbox_without_connection($vals);
62: $folder = $mailbox->get_folder_name('INBOX');
63: $sieve = ! empty($vals['sieve_config_host']);
64: $sources[] = array('folder' => bin2hex($folder), 'folder_name' => $folder, 'type' => $vals['type'] ?? 'imap', 'name' => $vals['name'], 'id' => $index, 'sieve' => $sieve);
65: }
66: foreach ($custom as $path => $type) {
67: $parts = explode('_', $path, 3);
68: $remove_id = false;
69:
70: if ($type == 'add') {
71: $details = Hm_IMAP_List::dump($parts[1]);
72: if ($details) {
73: $folder_name = $parts[2];
74: if (! empty($details['type']) && $details['type'] == 'ews') {
75: $mailbox = Hm_IMAP_List::get_connected_mailbox($details['id']);
76: if ($mailbox && $mailbox->authed()) {
77: $folder_name = $mailbox->get_folder_name(hex2bin($folder_name));
78: }
79: }
80: $sources[] = array('folder' => $parts[2], 'folder_name' => $folder_name, 'type' => $details['type'] ?? 'imap', 'name' => $details['name'], 'id' => $parts[1]);
81: }
82: }
83: elseif ($type == 'remove') {
84: foreach ($sources as $index => $vals) {
85: if ($vals['folder'] == $parts[2] && $vals['id'] == $parts[1]) {
86: $remove_id = $index;
87: break;
88: }
89: }
90: if ($remove_id !== false) {
91: unset($sources[$remove_id]);
92: }
93: }
94: }
95: return $sources;
96: }}
97:
98: /**
99: * Prepare and format message list data
100: * @subpackage imap/functions
101: * @param array $msgs list of message headers to format
102: * @param object $mod Hm_Output_Module
103: * @return void
104: */
105: if (!hm_exists('prepare_imap_message_list')) {
106: function prepare_imap_message_list($msgs, $mod, $type) {
107: $style = $mod->get('news_list_style') ? 'news' : 'email';
108: if ($mod->get('is_mobile')) {
109: $style = 'news';
110: }
111: $res = format_imap_message_list($msgs, $mod, $type, $style);
112: $mod->out('formatted_message_list', $res);
113: }}
114:
115: /**
116: * Build HTML for a list of IMAP folders
117: * @subpackage imap/functions
118: * @param array $folders list of folder data
119: * @param mixed $id IMAP server id
120: * @param object $mod Hm_Output_Module
121: * @return string
122: */
123: if (!hm_exists('format_imap_folder_section')) {
124: function format_imap_folder_section($folders, $id, $output_mod, $with_input = false, $can_share_folders = false) {
125: $results = '<ul class="inner_list">';
126: $manage = $output_mod->get('imap_folder_manage_link');
127:
128: foreach ($folders as $folder_name => $folder) {
129: $folder_name = bin2hex($folder_name);
130: $results .= '<li class="'. ($folder['children'] ? 'm-has-children ' : '') .'imap_'.$id.'_'.$output_mod->html_safe($folder_name).'" data-number-children="'.$output_mod->html_safe($folder['number_of_children']).'">';
131:
132: if ($folder['children']) {
133: $results .= '<div class="m-has-children-wrapper"><a href="#" class="imap_folder_link expand_link d-inline-flex" data-target="imap_'.$id.'_'.$output_mod->html_safe($folder_name).'"><i class="bi bi-plus-circle-fill"></i></a>';
134: }
135: else {
136: $results .= '<i class="bi bi-folder2-open"></i> ';
137: }
138: if (!$folder['noselect']) {
139: if (!$folder['clickable']) {
140: $attrs = 'tabindex="0"';
141: if (!$with_input && isset($folder['subscribed']) && !$folder['subscribed']) {
142: $attrs .= ' class="folder-disabled"';
143: }
144: } else {
145: $attrs = 'id="main-link" data-id="imap_'.$id.'_'.$output_mod->html_safe($folder_name).
146: '" href="?page=message_list&amp;list_path='.
147: urlencode('imap_'.$id.'_'.$output_mod->html_safe($folder_name)).'"';
148: }
149: if (mb_strlen($folder['basename'])>15) {
150: $results .= '<a ' . $attrs .
151: ' title="'.$output_mod->html_safe($folder['basename']).
152: '">'.$output_mod->html_safe(mb_substr($folder['basename'],0,15)).'...</a>';
153: }
154: else {
155: $results .= '<a ' . $attrs. '>'.$output_mod->html_safe($folder['basename']).'</a>';
156: }
157: }
158: else {
159: $results .= $output_mod->html_safe($folder['basename']);
160: }
161: if ($with_input) {
162: $results .= '<input type="checkbox" value="1" class="folder_subscription" id="'.$output_mod->html_safe($folder_name).'" name="'.$folder_name.'" '.($folder['subscribed']? 'checked="checked"': '').($folder['special']? ' disabled="disabled"': '').' />';
163: }
164: $results .= '<span class="unread_count unread_imap_'.$id.'_'.$output_mod->html_safe($folder_name).'"></span>';
165: if($folder['children']) {
166: $results .= '</div>';
167: }
168: if($can_share_folders) {
169: $results .= '<div class="dropdown"><a href="#" class="action-link" data-bs-toggle="dropdown" aria-expanded="false"><i class="icon bi bi-three-dots-vertical"></i></a><ul class="dropdown-menu dropdown-menu"><li data-id="'.$id.'" data-folder-uid="'.$output_mod->html_safe($folder_name).'" data-folder="'.$output_mod->html_safe($folder['basename']).'"><a href="#" class="dropdown-item share"><i class="icon bi bi-share"></i> Share</a></ul></div>';
170: }
171: $results .= '</li>';
172: }
173: if ($manage) {
174: $results .= '<li class="manage_folders_li"><i class="bi bi-gear-wide me-1"></i><a class="manage_folder_link" href="'.$manage.'">'.$output_mod->trans('Manage Folders').'</a></li>';
175: }
176: $f = $output_mod->get('folder', '');
177: $quota = $output_mod->get('quota');
178: $quota_max = $output_mod->get('quota_max');
179: if (!$f && $quota) {
180: $results .= '<li class="quota_info"><div class="progress bg-secondary border"><div class="progress-bar bg-light" style="width:'.$quota.'%"></div></div>'.$quota.'% used on '.$quota_max.' MB</li>';
181: }
182:
183: $results .= '</ul>';
184: return $results;
185: }}
186:
187: /**
188: * Format a from/to field for message list display
189: * @subpackage imap/functions
190: * @param string $fld field to format
191: * @return string
192: */
193: if (!hm_exists('format_imap_from_fld')) {
194: function format_imap_from_fld($fld) {
195: $res = array();
196: foreach (process_address_fld($fld) as $vals) {
197: if (trim($vals['label'])) {
198: $res[] = $vals['label'];
199: }
200: elseif (trim($vals['email'])) {
201: $res[] = $vals['email'];
202: }
203: }
204: return implode(', ', $res);
205: }}
206:
207: /**
208: * Format a list of message headers
209: * @subpackage imap/functions
210: * @param array $msg_list list of message headers
211: * @param object $mod Hm_Output_Module
212: * @param mixed $parent_list parent list id
213: * @param string $style list style (email or news)
214: * @return array
215: */
216: if (!hm_exists('format_imap_message_list')) {
217: function format_imap_message_list($msg_list, $output_module, $parent_list=false, $style='email') {
218: $res = array();
219: if ($msg_list === array(false)) {
220: return $msg_list;
221: }
222: $show_icons = $output_module->get('msg_list_icons');
223: $list_page = $output_module->get('list_page', 0);
224: $list_sort = $output_module->get('list_sort', $output_module->get('default_sort_order'));
225: $list_filter = $output_module->get('list_filter');
226: $list_keyword = $output_module->get('list_keyword');
227: foreach($msg_list as $msg) {
228: $row_class = 'email';
229: $icon = 'env_open';
230: if (!$parent_list) {
231: $parent_value = sprintf('imap_%s_%s', $msg['server_id'], $msg['folder']);
232: }
233: else {
234: $parent_value = $parent_list;
235: }
236: $id = sprintf("imap_%s_%s_%s", $msg['server_id'], $msg['uid'], $msg['folder']);
237: if (!trim($msg['subject'])) {
238: $msg['subject'] = '[No Subject]';
239: }
240: $subject = $msg['subject'];
241: $preview_msg = "";
242: if (isset($msg['preview_msg'])) {
243: $preview_msg = $msg['preview_msg'];
244: }
245:
246: if ($parent_list == 'sent') {
247: $icon = 'sent';
248: $from = $msg['to'];
249: }
250: else {
251: $from = $msg['from'];
252: }
253: $from = format_imap_from_fld(is_array($from) ? implode(', ', $from) : $from);
254: $nofrom = '';
255: if (!trim($from)) {
256: $from = '[No From]';
257: $nofrom = ' nofrom';
258: }
259: $is_snoozed = !empty($msg['x_snoozed']) && hex2bin($msg['folder']) == 'Snoozed';
260: $is_scheduled = !empty($msg['x_schedule']) && hex2bin($msg['folder']) == 'Scheduled';
261: if ($is_snoozed) {
262: $snooze_header = parse_delayed_header('X-Snoozed: '.$msg['x_snoozed'], 'X-Snoozed');
263: $date = $snooze_header['until'];
264: $timestamp = strtotime($date);
265: } elseif ($is_scheduled) {
266: $date = $msg['x_schedule'];
267: $timestamp = strtotime($date);
268: } else {
269: if ($list_sort == 'date') {
270: $date_field = 'date';
271: } else {
272: $date_field = 'internal_date';
273: }
274: $date = translate_time_str(human_readable_interval($msg[$date_field]), $output_module);
275: $timestamp = strtotime($msg[$date_field]);
276: }
277:
278: $flags = array();
279: if (!mb_stristr($msg['flags'], 'seen')) {
280: $flags[] = 'unseen';
281: if ($icon != 'sent') {
282: $icon = 'env_closed';
283: }
284: }
285: else {
286: $row_class .= ' seen';
287: }
288: if (trim($msg['x_auto_bcc']) === 'cypht') {
289: $from = preg_replace("/(\<.+\>)/U", '', $msg['to']);
290: $icon = 'sent';
291: }
292: foreach (array('attachment', 'deleted', 'flagged', 'answered', 'draft') as $flag) {
293: if (mb_stristr($msg['flags'], $flag)) {
294: $flags[] = $flag;
295: }
296: }
297: $source = $msg['server_name'];
298: $row_class .= ' '.str_replace(' ', '_', $source);
299: $row_class .= ' '.implode(' ', $flags);
300: if ($msg['folder'] && strtolower(hex2bin($msg['folder'])) != 'inbox') {
301: $source .= '-'.preg_replace("/^INBOX.{1}/", '', hex2bin($msg['folder']));
302: }
303: $url = '?page=message&uid='.$msg['uid'].'&list_path='.sprintf('imap_%s_%s', $msg['server_id'], $msg['folder']).'&list_parent='.$parent_value;
304: if ($list_page) {
305: $url .= '&list_page='.$output_module->html_safe($list_page);
306: }
307: if ($list_sort) {
308: $url .= '&sort='.$output_module->html_safe($list_sort);
309: }
310: if ($list_filter) {
311: $url .= '&filter='.$output_module->html_safe($list_filter);
312: }
313: if ($list_keyword) {
314: $url .= '&keyword='.$output_module->html_safe($list_keyword);
315: }
316: if (!$show_icons) {
317: $icon = false;
318: }
319:
320: //if (in_array('draft', $flags)) {
321: // $url = '?page=compose&list_path='.sprintf('imap_%s_%s', $msg['server_id'], $msg['folder']).'&uid='.$msg['uid'].'&imap_draft=1';
322: //}
323:
324: $msgId = $msg['message_id'] ?? '';
325: $inReplyTo = $msg['in_reply_to'] ?? '';
326:
327: if ($msgId) {
328: $msgId = str_replace(['<', '>'], '', trim($msgId));
329: }
330: if ($inReplyTo) {
331: $inReplyTo = str_replace(['<', '>'], '', trim($inReplyTo));
332: }
333:
334: if ($style == 'news') {
335: $res[$id] = message_list_row(array(
336: array('checkbox_callback', $id),
337: array('icon_callback', $flags),
338: array('subject_callback', $subject, $url, $flags, $icon, $preview_msg),
339: array('safe_output_callback', 'source', $source),
340: array('safe_output_callback', 'from'.$nofrom, $from, null, str_replace(array($from, '<', '>'), '', $msg['from'])),
341: array('date_callback', $date, $timestamp, $is_snoozed || $is_scheduled),
342: array('dates_holders_callback', $msg['internal_date'], $msg['date']),
343: ),
344: $id,
345: $style,
346: $output_module,
347: $row_class,
348: $msgId,
349: $inReplyTo
350: );
351: }
352: else {
353: $res[$id] = message_list_row(array(
354: array('checkbox_callback', $id),
355: array('safe_output_callback', 'source', $source, $icon),
356: array('safe_output_callback', 'from'.$nofrom, $from, null, str_replace(array($from, '<', '>'), '', $msg['from'])),
357: array('subject_callback', $subject, $url, $flags, null, $preview_msg),
358: array('date_callback', $date, $timestamp, $is_snoozed || $is_scheduled),
359: array('icon_callback', $flags),
360: array('dates_holders_callback', $msg['internal_date'], $msg['date']),
361: ),
362: $id,
363: $style,
364: $output_module,
365: $row_class,
366: $msgId,
367: $inReplyTo
368: );
369: }
370: }
371: return $res;
372: }}
373:
374: /**
375: * Process message ids
376: * @subpackage imap/functions
377: * @param array $ids list of ids
378: * @return array
379: */
380: if (!hm_exists('process_imap_message_ids')) {
381: function process_imap_message_ids($ids) {
382: $res = array();
383: foreach (explode(',', $ids) as $id) {
384: if (preg_match("/imap_(\S+)_(\S+)_(\S+)$/", $id, $matches)) {
385: $server = $matches[1];
386: $uid = $matches[2];
387: $folder = $matches[3];
388: if (!isset($res[$server])) {
389: $res[$server] = array();
390: }
391: if (!isset($res[$server][$folder])) {
392: $res[$server][$folder] = array();
393: }
394: $res[$server][$folder][] = $uid;
395: }
396: }
397: return $res;
398: }}
399:
400: /**
401: * Format a message part row
402: * @subpackage imap/functions
403: * @param string $id message identifier
404: * @param array $vals details of the message
405: * @param object $mod Hm_Output_Module
406: * @param int $level indention level
407: * @param string $part currently selected part
408: * @param string $dl_args base arguments for a download link URL
409: * @param bool $use_icons flag to enable/disable message part icons
410: * @param bool $simmple_view flag to hide complex message structure
411: * @param bool $mobile flag to indicate a mobile browser
412: * @return string
413: */
414: if (!hm_exists('format_msg_part_row')) {
415: function format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_args, $at_args, $use_icons=false, $simple_view=false, $mobile=false) {
416: $allowed = array(
417: 'textplain',
418: 'texthtml',
419: 'messagedisposition-notification',
420: 'messagedelivery-status',
421: 'messagerfc822-headers',
422: 'textcsv',
423: 'textcss',
424: 'textunknown',
425: 'textx-vcard',
426: 'textcalendar',
427: 'textx-vcalendar',
428: 'textx-sql',
429: 'textx-comma-separated-values',
430: 'textenriched',
431: 'textrfc822-headers',
432: 'textx-diff',
433: 'textx-patch',
434: 'applicationpgp-signature',
435: 'applicationx-httpd-php',
436: 'imagepng',
437: 'imagesvg+xml',
438: 'imagejpg',
439: 'imagejpeg',
440: 'imagepjpeg',
441: 'imagegif',
442: );
443: $icons = array(
444: 'text' => 'doc',
445: 'image' => 'camera',
446: 'application' => 'save',
447: 'multipart' => 'folder',
448: 'audio' => 'audio',
449: 'video' => 'monitor',
450: 'binary' => 'save',
451:
452: 'textx-vcard' => 'calendar',
453: 'textcalendar' => 'calendar',
454: 'textx-vcalendar' => 'calendar',
455: 'applicationics' => 'calendar',
456: 'multipartdigest' => 'spreadsheet',
457: 'applicationpgp-keys' => 'key',
458: 'applicationpgp-signature' => 'key',
459: 'multipartsigned' => 'lock',
460: 'messagerfc822' => 'env_open',
461: 'octetstream' => 'paperclip',
462: );
463: $hidden_parts= array(
464: 'multipartdigest',
465: 'multipartsigned',
466: 'multipartmixed',
467: 'messagerfc822',
468: );
469: $lc_type = mb_strtolower($vals['type']).mb_strtolower($vals['subtype']);
470: if ($simple_view) {
471: if (filter_message_part($vals)) {
472: return '';
473: }
474: if (in_array($lc_type, $hidden_parts, true)) {
475: return '';
476: }
477: }
478: if ($level > 6) {
479: $class = 'row_indent_max';
480: }
481: else {
482: $class = 'row_indent_'.$level;
483: }
484: $desc = get_part_desc($vals, $id, $part);
485: $size = get_imap_size($vals);
486: $res = '<tr';
487: if ($id == $part) {
488: $res .= ' class="selected_part"';
489: }
490: $res .= '><td><div class="'.$class.'">';
491: $icon = false;
492: if ($use_icons && array_key_exists($lc_type, $icons)) {
493: $icon = $icons[$lc_type];
494: }
495: elseif ($use_icons && array_key_exists(mb_strtolower($vals['type']), $icons)) {
496: $icon = $icons[mb_strtolower($vals['type'])];
497: }
498: if ($icon) {
499: $res .= '<i class="bi bi-file-plus-fill msg_part_icon"></i> ';
500: }
501: else {
502: $res .= '<i class="bi bi-file-plus-fill msg_part_icon msg_part_placeholder"></i> ';
503: }
504: if (in_array($lc_type, $allowed, true)) {
505: $res .= '<a href="#" class="msg_part_link" data-message-part="'.$output_mod->html_safe($id).'">'.$output_mod->html_safe(mb_strtolower($vals['type'])).
506: ' / '.$output_mod->html_safe(mb_strtolower($vals['subtype'])).'</a>';
507: }
508: else {
509: $res .= $output_mod->html_safe(mb_strtolower($vals['type'])).' / '.$output_mod->html_safe(mb_strtolower($vals['subtype']));
510: }
511: if ($mobile) {
512: $res .= '<div class="part_size">'.$output_mod->html_safe($size);
513: $res .= '</div><div class="part_desc">'.$output_mod->html_safe(decode_fld($desc)).'</div>';
514: $res .= '<div class="download_link"><a href="#" data-src="?'.$dl_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'">'.$output_mod->trans('Download').'</a></div></td>';
515: }
516: else {
517: $res .= '</td><td class="part_size">'.$output_mod->html_safe($size);
518: if (!$simple_view) {
519: $res .= '</td><td class="part_encoding">'.(isset($vals['encoding']) ? $output_mod->html_safe(mb_strtolower($vals['encoding'])) : '').
520: '</td><td class="part_charset">'.(isset($vals['attributes']['charset']) && trim($vals['attributes']['charset']) ? $output_mod->html_safe(mb_strtolower($vals['attributes']['charset'])) : '');
521: }
522: $res .= '</td><td class="part_desc">'.$output_mod->html_safe(decode_fld($desc)).'</td>';
523: $res .= '<td class="download_link"><a href="#" data-src="?'.$dl_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'">'.$output_mod->trans('Download').'</a></td>';
524: }
525: if ($output_mod->get('allow_delete_attachment') && isset($vals['file_attributes']['attachment'])) {
526: $res .= '<td><a href="#" data-src="?'.$at_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'" class="remove_attachment">'.$output_mod->trans('Remove').'</a></td>';
527: }
528: $res .= '</tr>';
529: return $res;
530: }}
531:
532: /*
533: * Returns the modified message
534: * @param int $attachment_id from get_attachment_id_for_mail_parser
535: * @param string $msg raw message
536: * @return string
537: */
538: if (!hm_exists('remove_attachment')) {
539: function remove_attachment($att_id, $msg) {
540: $message = Message::from($msg, false);
541: $message->removeAttachmentPart($att_id);
542:
543: return (string) $message;
544: }
545: }
546:
547: /*
548: * ZBateson\MailMimeParser uses 0-based index for attachments
549: * Which mixes embedded images and attachments
550: * @param array $struct Imap structure
551: * @param string $part_id message part
552: * @return int
553: */
554: if (!hm_exists('get_attachment_id_for_mail_parser')) {
555: function get_attachment_id_for_mail_parser($struct, $part_id) {
556: $count = -1;
557: $id = false;
558: foreach ($struct[0]['subs'] as $key => $sub) {
559: if (! empty($sub['file_attributes'])) {
560: $count++;
561: if ($key == $part_id && isset($sub['file_attributes']['attachment'])) {
562: $id = $count;
563: break;
564: }
565: }
566: }
567: return $id;
568: }
569: }
570:
571: /*
572: * Find a message part description/filename
573: * @param array $vals bodystructure info for this message part
574: * @param int $uid message number
575: * @param string $part_id message part number
576: * @return string
577: */
578: if (!hm_exists('get_part_desc')) {
579: function get_part_desc($vals, $id, $part) {
580: $desc = '';
581: if (isset($vals['description']) && trim($vals['description'])) {
582: $desc = $vals['description'];
583: }
584: elseif (isset($vals['name']) && trim($vals['name'])) {
585: $desc = $vals['name'];
586: }
587: elseif (isset($vals['filename']) && trim($vals['filename'])) {
588: $desc = $vals['filename'];
589: }
590: elseif (isset($vals['envelope']['subject']) && trim($vals['envelope']['subject'])) {
591: $desc = $vals['envelope']['subject'];
592: }
593: $filename = get_imap_part_name($vals, $id, $part, true);
594: if (!$desc && $filename) {
595: $desc = $filename;
596: }
597: return $desc;
598: }}
599:
600: /*
601: * Get a human readable message size
602: * @param array $vals bodystructure info for this message part
603: * @return string
604: */
605: if (!hm_exists('get_imap_size')) {
606: function get_imap_size($vals) {
607: if (!array_key_exists('size', $vals) || !$vals['size']) {
608: return '';
609: }
610: $size = intval($vals['size']);
611: switch (true) {
612: case $size > 1000:
613: $size = $size/1000;
614: $label = 'KB';
615: break;
616: case $size > 1000000:
617: $size = $size/1000000;
618: $label = 'MB';
619: break;
620: case $size > 1000000000:
621: $size = $size/1000000000;
622: $label = 'GB';
623: break;
624: default:
625: $label = 'B';
626: }
627: return sprintf('%s %s', round($size, 2), $label);
628: }}
629:
630: /**
631: * Format the message part section of the message view page
632: * @subpackage imap/functions
633: * @param array $struct message structure
634: * @param object $mod Hm_Output_Module
635: * @param string $part currently selected message part id
636: * @param string $dl_link base arguments for a download link
637: * @param int $level indention level
638: * @return string
639: */
640: if (!hm_exists('format_msg_part_section')) {
641: function format_msg_part_section($struct, $output_mod, $part, $dl_link, $at_link, $level=0) {
642: $res = '';
643: $simple_view = $output_mod->get('simple_msg_part_view', false);
644: $use_icons = $output_mod->get('use_message_part_icons', false);
645: $mobile = $output_mod->get('is_mobile');
646: if ($mobile) {
647: $simple_view = true;
648: }
649:
650: if(!$simple_view){
651: foreach ($struct as $id => $vals) {
652: if (is_array($vals) && isset($vals['type'])) {
653: $row = format_msg_part_row($id, $vals, $output_mod, $level, $part, $dl_link, $at_link, $use_icons, $simple_view, $mobile);
654: if (!$row) {
655: $level--;
656: }
657: $res .= $row;
658: if (isset($vals['subs'])) {
659: $res .= format_msg_part_section($vals['subs'], $output_mod, $part, $dl_link, $at_link, ($level + 1));
660: }
661: }
662: else {
663: if (is_array($vals) && count($vals) == 1 && isset($vals['subs'])) {
664: $res .= format_msg_part_section($vals['subs'], $output_mod, $part, $dl_link, $at_link, $level);
665: }
666: }
667: }
668: }else{
669: $res = format_attachment($struct, $output_mod, $part, $dl_link, $at_link);
670: }
671: return $res;
672: }}
673:
674: function format_attachment($struct, $output_mod, $part, $dl_args, $at_args) {
675: $res = '';
676:
677: foreach ($struct as $id => $vals) {
678: if(is_array($vals) && isset($vals['type']) && $vals['type'] != 'multipart' && isset($vals['file_attributes']) && !empty($vals['file_attributes'])) {
679: $size = get_imap_size($vals);
680: $desc = get_part_desc($vals, $id, $part);
681:
682: $res .= '<tr><td class="part_desc" colspan="4">'.$output_mod->html_safe(decode_fld($desc)).'</td>';
683: $res .= '</td><td class="part_size">'.$output_mod->html_safe($size).'</td>';
684:
685: $res .= '<td class="download_link"><a href="#" data-src="?'.$dl_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'">'.$output_mod->trans('Download').'</a></td>';
686: if ($output_mod->get('allow_delete_attachment') && isset($vals['file_attributes']['attachment'])) {
687: $res .= '<td><a href="?'.$at_args.'&amp;imap_msg_part='.$output_mod->html_safe($id).'" class="remove_attachment">'.$output_mod->trans('Remove').'</a></td></tr>';
688: }
689: }
690:
691: if(is_array($vals) && isset($vals['subs'])) {
692: $sub_res = format_attachment($vals['subs'], $output_mod, $part, $dl_args, $at_args);
693: $res =$sub_res;
694: }
695: }
696:
697: return $res;
698: }
699:
700: /**
701: * Format the attached images section
702: * @subpackage imap/functions
703: * @param array $struct message structure
704: * @param object $output_mod Hm_Output_Module
705: * @param string $dl_link base arguments for a download link
706: * @return string
707: */
708: if (!hm_exists('format_attached_image_section')) {
709: function format_attached_image_section($struct, $output_mod, $dl_link) {
710: $res = '';
711: $isThereAnyImg = false;
712: foreach ($struct as $id => $vals) {
713: if ($vals['type'] === 'image') {
714: $res .= '<div class="col-6 col-md-3">
715: <img class="attached_image img-fluid"
716: src="?' . $dl_link . '&amp;imap_msg_part=' . $output_mod->html_safe($id) . '" >
717: </div>';
718: $isThereAnyImg = true;
719: }
720: if (isset($vals['subs'])) {
721: $res .= format_attached_image_section($vals['subs'], $output_mod, $dl_link);
722: }
723: }
724:
725: if ($isThereAnyImg) {
726: $res = '<div class="container-fluid"><div class="row text-center attached_image_box">' . $res . '</div></div>';
727: }
728:
729: return $res;
730: }}
731:
732: /**
733: * Filter out message parts that are not attachments
734: * @param array message structure
735: * @return bool
736: */
737: if (!hm_exists('filter_message_part')) {
738: function filter_message_part($vals) {
739: if (array_key_exists('disposition', $vals) && is_array($vals['disposition']) && array_key_exists('inline', $vals['disposition'])) {
740: return true;
741: }
742: if (array_key_exists('type', $vals) && $vals['type'] == 'multipart') {
743: return true;
744: }
745: return false;
746: }}
747:
748: /**
749: * Sort callback to sort by internal date
750: * @subpackage imap/functions
751: * @param array $a first message detail
752: * @param array $b second message detail
753: * @return int
754: */
755: if (!hm_exists('sort_by_internal_date')) {
756: function sort_by_internal_date($a, $b) {
757: if ($a['internal_date'] == $b['internal_date']) return 0;
758: return (strtotime($a['internal_date']) < strtotime($b['internal_date']))? -1 : 1;
759: }}
760:
761: /**
762: * Merge IMAP search results
763: * @subpackage imap/functions
764: * @param array $ids IMAP server ids
765: * @param string $search_type
766: * @param object $session session object
767: * @param object $hm_cache cache object
768: * @param array $folders list of folders to search
769: * @param int $limit max results
770: * @param array $terms list of search terms
771: * @param bool $sent flag to fetch auto-bcc'ed messages
772: * @return array
773: */
774: if (!hm_exists('merge_imap_search_results')) {
775: function merge_imap_search_results($ids, $search_type, $session, $hm_cache, $folders = array('INBOX'), $limit=0, $terms=array(), $sent=false) {
776: $msg_list = array();
777: $connection_failed = false;
778: $sent_results = array();
779: $status = array();
780: foreach($ids as $index => $id) {
781: $mailbox = Hm_IMAP_List::get_connected_mailbox($id, $hm_cache);
782: if ($mailbox && $mailbox->authed()) {
783: $server_details = Hm_IMAP_List::dump($id);
784: $folder = $folders[$index];
785: if ($sent) {
786: $sent_folder = $mailbox->get_special_use_mailboxes('sent');
787: if (array_key_exists('sent', $sent_folder)) {
788: list($sent_status, $sent_results) = merge_imap_search_results($ids, $search_type, $session, $hm_cache, array($sent_folder['sent']), $limit, $terms, false);
789: $status = array_merge($status, $sent_status);
790: }
791: if ($folder == 'SPECIAL_USE_CHECK') {
792: continue;
793: }
794: }
795: if (!empty($terms)) {
796: foreach ($terms as $term) {
797: if (preg_match('/(?:[^\x00-\x7F])/', $term[1]) === 1) {
798: $mailbox->set_search_charset('UTF-8');
799: break;
800: }
801: }
802: if ($sent) {
803: $msgs = $mailbox->search($folder, $search_type, $terms, null, null, true, false, true);
804: }
805: else {
806: $msgs = $mailbox->search($folder, $search_type, $terms);
807: }
808: }
809: else {
810: $msgs = $mailbox->search($folder, $search_type);
811: }
812: if ($msgs) {
813: if ($limit) {
814: rsort($msgs);
815: $msgs = array_slice($msgs, 0, $limit);
816: }
817: foreach ($mailbox->get_message_list($folder, $msgs) as $msg) {
818: if (array_key_exists('content-type', $msg) && mb_stristr($msg['content-type'], 'multipart/mixed')) {
819: $msg['flags'] .= ' \Attachment';
820: }
821: if (mb_stristr($msg['flags'], 'deleted')) {
822: continue;
823: }
824: $msg['server_id'] = $id;
825: $msg['folder'] = bin2hex($folder);
826: $msg['server_name'] = $server_details['name'];
827: $msg_list[] = $msg;
828: }
829: }
830: $status['imap_'.$id.'_'.bin2hex($folder)] = $mailbox->get_folder_state();
831: }
832: else {
833: $connection_failed = true;
834: }
835: }
836: $session->set('imap_folder_status', $status);
837: if ($connection_failed && empty($msg_list)) {
838: return array(array(), false);
839: }
840: if (count($sent_results) > 0) {
841: $msg_list = array_merge($msg_list, $sent_results);
842: }
843:
844: usort($msg_list, function($a, $b) {
845: return strtotime($b['internal_date']) - strtotime($a['internal_date']);
846: });
847:
848: return array($status, $msg_list);
849: }}
850:
851: /**
852: * Replace inline images in an HTML message part
853: * @subpackage imap/functions
854: * @param string $txt HTML
855: * @param string $uid message id
856: * @param array $struct message structure array
857: * @param object $imap IMAP server object
858: */
859: if (!hm_exists('add_attached_images')) {
860: function add_attached_images($txt, $uid, $struct, $imap) {
861: if (preg_match_all("/src=('|\"|)cid:([^\s'\"]+)/", $txt, $matches)) {
862: $cids = array_pop($matches);
863: foreach ($cids as $id) {
864: $part = $imap->search_bodystructure($struct, array('id' => $id, 'type' => 'image'), true);
865: $part_ids = array_keys($part);
866: $part_id = array_pop($part_ids);
867: $img = $imap->get_message_content($uid, $part_id, false, $part[$part_id]);
868: $txt = str_replace('cid:'.$id, 'data:image/'.$part[$part_id]['subtype'].';base64,'.base64_encode($img), $txt);
869: }
870: }
871: return $txt;
872: }}
873:
874: /**
875: * Check for and do an Oauth2 token reset if needed
876: * @subpackage imap/functions
877: * @param array $server imap server data
878: * @param object $config site config object
879: * @return mixed
880: */
881: if (!hm_exists('imap_refresh_oauth2_token')) {
882: function imap_refresh_oauth2_token($server, $config) {
883: if ((int) $server['expiration'] <= time()) {
884: $oauth2_data = get_oauth2_data($config);
885: $details = array();
886: if ($server['server'] == 'imap.gmail.com') {
887: $details = $oauth2_data['gmail'];
888: }
889: elseif ($server['server'] == 'imap-mail.outlook.com') {
890: $details = $oauth2_data['outlook'];
891: }
892: elseif ($server['server'] == 'imap-mail.office365.com') {
893: $details = $oauth2_data['office365'];
894: }
895: if (!empty($details)) {
896: $oauth2 = new Hm_Oauth2($details['client_id'], $details['client_secret'], $details['client_uri']);
897: $result = $oauth2->refresh_token($details['refresh_uri'], $server['refresh_token']);
898: if (array_key_exists('access_token', $result)) {
899: return array(strtotime(sprintf('+%d seconds', $result['expires_in'])), $result['access_token']);
900: }
901: }
902: }
903: return array();
904: }}
905:
906: /**
907: * Copy/Move messages on the same IMAP server
908: * @subpackage imap/functions
909: * @param array $ids list of message ids with server and folder info
910: * @param string $action action type, copy or move
911: * @param object $hm_cache system cache
912: * @param array $dest_path imap id and folder to copy/move to
913: * @return int count of messages moved
914: */
915: if (!hm_exists('imap_move_same_server')) {
916: function imap_move_same_server($ids, $action, $hm_cache, $dest_path, $screen_emails=false) {
917: $moved = [];
918: $responses = [];
919: $keys = array_keys($ids);
920: $server_id = array_pop($keys);
921: $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $hm_cache);
922: if ($mailbox && $mailbox->authed()) {
923: foreach ($ids[$server_id] as $folder => $msgs) {
924: if ($screen_emails) {
925: foreach ($msgs as $msg) {
926: $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg, $folder);
927: $email = current(array_column(process_address_fld($mailbox->get_message_headers(hex2bin($folder), $msg)['From']), "email"));
928: $uids = $mailbox->search(hex2bin($folder), 'ALL', array(array('FROM', $email)));
929: foreach ($uids as $uid) {
930: $result = $mailbox->message_action(hex2bin($folder), mb_strtoupper($action), $uid, hex2bin($dest_path[2]));
931: if ($result['status']) {
932: $response = $result['responses'][0];
933: $responses[] = [
934: 'oldUid' => $uid,
935: 'newUid' => $response['newUid'],
936: 'oldFolder' => hex2bin($folder),
937: 'newFolder' => hex2bin($dest_path[2]),
938: 'oldServer' => $server_id,
939: ];
940: $moved[] = sprintf('imap_%s_%s_%s', $server_id, $uid, $folder);
941: }
942: }
943: }
944: } else {
945: $result = $mailbox->message_action(hex2bin($folder), mb_strtoupper($action), $msgs, hex2bin($dest_path[2]));
946: if ($result['status']) {
947: foreach ($msgs as $index => $msg) {
948: $response = $result['responses'][$index];
949: $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg, $folder);
950: $responses[] = [
951: 'oldUid' => $msg,
952: 'newUid' => $response['newUid'],
953: 'oldFolder' => hex2bin($folder),
954: 'newFolder' => hex2bin($dest_path[2]),
955: 'oldServer' => $server_id,
956: ];
957: }
958: }
959: }
960:
961: }
962: }
963: return ['moved' => $moved, 'responses' => $responses];
964: }}
965:
966: /**
967: * Copy/Move messages on different IMAP servers
968: * @subpackage imap/functions
969: * @param array $ids list of message ids with server and folder info
970: * @param string $action action type, copy or move
971: * @param array $dest_path imap id and folder to copy/move to
972: * @param object $hm_cache cache interface
973: * @return int count of messages moved
974: */
975: if (!hm_exists('imap_move_different_server')) {
976: function imap_move_different_server($ids, $action, $dest_path, $hm_cache) {
977: $moved = [];
978: $responses = [];
979: $dest_mailbox = Hm_IMAP_List::get_connected_mailbox($dest_path[1], $hm_cache);
980: if ($dest_mailbox && $dest_mailbox->authed()) {
981: foreach ($ids as $server_id => $folders) {
982: $mailbox = Hm_IMAP_List::get_connected_mailbox($server_id, $hm_cache);
983: if ($mailbox && $mailbox->authed()) {
984: foreach ($folders as $folder => $msg_ids) {
985: foreach ($msg_ids as $msg_id) {
986: $detail = $mailbox->get_message_list(hex2bin($folder), array($msg_id));
987: if (array_key_exists($msg_id, $detail)) {
988: if (mb_stristr($detail[$msg_id]['flags'], 'seen')) {
989: $seen = true;
990: }
991: else {
992: $seen = false;
993: }
994: }
995: $msg = $mailbox->get_message_content(hex2bin($folder), $msg_id);
996: $msg = str_replace("\r\n", "\n", $msg);
997: $msg = str_replace("\n", "\r\n", $msg);
998: $msg = rtrim($msg)."\r\n";
999: if (!$seen) {
1000: $mailbox->message_action(hex2bin($folder), 'UNREAD', array($msg_id));
1001: }
1002: if ($uid = $dest_mailbox->store_message(hex2bin($dest_path[2]), $msg, $seen)) {
1003: if ($action == 'move') {
1004: $deleteResult = $mailbox->message_action(hex2bin($folder), 'DELETE', array($msg_id));
1005: if ($deleteResult['status']) {
1006: $mailbox->message_action(hex2bin($folder), 'EXPUNGE', array($msg_id));
1007: }
1008: }
1009: $moved[] = sprintf('imap_%s_%s_%s', $server_id, $msg_id, $folder);
1010: $responses[] = [
1011: 'oldUid' => $msg_id,
1012: 'newUid' => $uid,
1013: 'oldFolder' => hex2bin($folder),
1014: 'newFolder' => hex2bin($dest_path[2]),
1015: 'oldServer' => $server_id,
1016: 'newServer' => $dest_path[1],
1017: ];
1018: }
1019: }
1020: }
1021: }
1022: }
1023: }
1024: return ['moved' => $moved, 'responses' => $responses];
1025: }}
1026:
1027: /**
1028: * Group info about move/copy messages
1029: * @subpackage imap/functions
1030: * @param array $form move copy input
1031: * @return array grouped lists of messages to move/copy
1032: */
1033: if (!hm_exists('process_move_to_arguments')) {
1034: function process_move_to_arguments($form) {
1035: $msg_ids = explode(',', $form['imap_move_ids']);
1036: $same_server_ids = array();
1037: $other_server_ids = array();
1038: $dest_path = explode('_', $form['imap_move_to']);
1039: if (count($dest_path) == 3 && $dest_path[0] == 'imap' && in_array($form['imap_move_action'], array('move', 'copy'), true)) {
1040: foreach ($msg_ids as $msg_id) {
1041: $path = explode('_', $msg_id);
1042: if (count($path) == 4 && $path[0] == 'imap') {
1043: if (sprintf('%s_%s', $path[0], $path[1]) == sprintf('%s_%s', $dest_path[0], $dest_path[1])) {
1044: $same_server_ids[$path[1]][$path[3]][] = $path[2];
1045: }
1046: else {
1047: $other_server_ids[$path[1]][$path[3]][] = $path[2];
1048: }
1049: }
1050: }
1051: }
1052: return array($msg_ids, $dest_path, $same_server_ids, $other_server_ids);
1053: }}
1054:
1055: /**
1056: * Get a file extension for a mime type
1057: * @subpackage imap/functions
1058: * @param string $type primary mime type
1059: * @param string $subtype secondary mime type
1060: * @todo add tons more type conversions!
1061: * @return string
1062: */
1063: if (!hm_exists('get_imap_mime_extension')) {
1064: function get_imap_mime_extension($type, $subtype) {
1065: $extension = $subtype;
1066: if ($type == 'multipart' || ($type == 'message' && $subtype == 'rfc822')) {
1067: $extension = 'eml';
1068: }
1069: if ($type == 'text') {
1070: switch ($subtype) {
1071: case 'plain':
1072: $extension = 'txt';
1073: break;
1074: case 'richtext':
1075: $extension = 'rtf';
1076: break;
1077: }
1078: }
1079: return '.'.$extension;
1080: }}
1081:
1082: /**
1083: * Try to find a filename for a message part download
1084: * @subpackage imap/functions
1085: * @param array $struct message part structure
1086: * @param int $uid message number
1087: * @param string $part_id message part number
1088: * @param bool $no_default don't return a default value
1089: * @return string
1090: */
1091: if (!hm_exists('get_imap_part_name')) {
1092: function get_imap_part_name($struct, $uid, $part_id, $no_default=false) {
1093: $extension = get_imap_mime_extension(mb_strtolower($struct['type']), mb_strtolower($struct['subtype']));
1094: if (array_key_exists('file_attributes', $struct) && is_array($struct['file_attributes']) &&
1095: array_key_exists('attachment', $struct['file_attributes']) && is_array($struct['file_attributes']['attachment'])) {
1096: for ($i=0;$i<count($struct['file_attributes']['attachment']);$i++) {
1097: if (mb_strtolower(trim($struct['file_attributes']['attachment'][$i])) == 'filename') {
1098: if (array_key_exists(($i+1), $struct['file_attributes']['attachment'])) {
1099: return trim($struct['file_attributes']['attachment'][($i+1)]);
1100: }
1101: }
1102: }
1103: }
1104:
1105: if (array_key_exists('disposition', $struct) && is_array($struct['disposition']) && array_key_exists('attachment', $struct['disposition']) && is_array($struct['disposition']['attachment'])) {
1106: for ($i=0;$i<count($struct['disposition']['attachment']);$i++) {
1107: if (mb_strtolower(trim($struct['disposition']['attachment'][$i])) == 'filename') {
1108: if (array_key_exists(($i+1), $struct['disposition']['attachment'])) {
1109: return trim($struct['disposition']['attachment'][($i+1)]);
1110: }
1111: }
1112: }
1113: }
1114:
1115: if (array_key_exists('attributes', $struct) && is_array($struct['attributes']) && array_key_exists('name', $struct['attributes'])) {
1116: return trim($struct['attributes']['name']);
1117: }
1118: if (array_key_exists('description', $struct) && trim($struct['description'])) {
1119: return trim(str_replace(array("\n", ' '), '_', $struct['description'])).$extension;
1120: }
1121: if (array_key_exists('name', $struct) && trim($struct['name'])) {
1122: return trim($struct['name']);
1123: }
1124: if ($no_default) {
1125: return '';
1126: }
1127: return 'message_'.$uid.'_part_'.$part_id.$extension;
1128: }}
1129:
1130: /**
1131: * @subpackage imap/functions
1132: */
1133: if (!hm_exists('clear_existing_reply_details')) {
1134: function clear_existing_reply_details($session) {
1135: $msgs = array();
1136: $max = 20;
1137: foreach ($session->dump() as $name => $val) {
1138: if (mb_substr($name, 0, 19) == 'reply_details_imap_') {
1139: $msgs[$name] = $val['ts'];
1140: }
1141: }
1142: arsort($msgs, SORT_NUMERIC);
1143: if (count($msgs) <= $max) {
1144: return ;
1145: }
1146: foreach (array_slice($msgs, $max) as $name) {
1147: $session->del($name);
1148: }
1149: }}
1150:
1151: /**
1152: * @subpackage imap/functions
1153: */
1154: if (!hm_exists('process_sort_arg')) {
1155: function process_sort_arg($sort, $default = 'arrival') {
1156: if (!$sort) {
1157: $default = mb_strtoupper($default);
1158: return array($default, true);
1159: }
1160: $rev = false;
1161: if (mb_substr($sort, 0, 1) == '-') {
1162: $rev = true;
1163: $sort = mb_substr($sort, 1);
1164: }
1165: $sort = mb_strtoupper($sort);
1166: if ($sort == 'ARRIVAL' || $sort == 'DATE') {
1167: $rev = $rev ? false : true;
1168: }
1169: return array($sort, $rev);
1170: }}
1171:
1172: /**
1173: * @subpackage imap/functions
1174: */
1175: if (!hm_exists('search_since_based_on_setting')) {
1176: function search_since_based_on_setting($config) {
1177: if ($config->get('default_sort_order_setting', 'arrival') === 'arrival') {
1178: return 'SINCE';
1179: } else {
1180: return 'SENTSINCE';
1181: }
1182: }}
1183:
1184:
1185: /**
1186: * @subpackage imap/functions
1187: */
1188: if (!hm_exists('imap_server_type')) {
1189: function imap_server_type($id) {
1190: $type = 'IMAP';
1191: $details = Hm_IMAP_List::dump($id);
1192: if (is_array($details) && array_key_exists('type', $details)) {
1193: $type = mb_strtoupper($details['type']);
1194: }
1195: return $type;
1196: }}
1197:
1198: /**
1199: * @subpackage imap/functions
1200: */
1201: if (!hm_exists('get_list_headers')) {
1202: function get_list_headers($headers) {
1203: $res = array();
1204: $list_headers = array('list-archive', 'list-unsubscribe',
1205: 'list-subscribe', 'list-archive', 'list-post', 'list-help');
1206: foreach (lc_headers($headers) as $name => $val) {
1207: if (in_array($name, $list_headers, true)) {
1208: $res[mb_substr($name, 5)] = process_list_fld($val);
1209: }
1210: }
1211: return $res;
1212: }}
1213:
1214: /**
1215: * @subpackage imap/functions
1216: */
1217: if (!hm_exists('process_list_fld')) {
1218: function process_list_fld($fld) {
1219: $res = array('links' => array(), 'email' => array(), 'values' => array());
1220: $fld = is_array($fld) ? implode(" ", $fld) : $fld;
1221: foreach (explode(',', $fld) as $val) {
1222: $val = trim(str_replace(array('<', '>'), '', $val));
1223: if (preg_match("/^http/", $val)) {
1224: $res['links'][] = $val;
1225: }
1226: elseif (preg_match("/^mailto/", $val)) {
1227: $res['email'][] = mb_substr($val, 7);
1228: }
1229: else {
1230: $res['values'][] = $val;
1231: }
1232: }
1233: return $res;
1234: }}
1235:
1236: /**
1237: * @subpackage imap/functions
1238: */
1239: if (!hm_exists('format_imap_envelope')) {
1240: function format_imap_envelope($env, $mod) {
1241: $env = lc_headers($env);
1242: $res = '<div class="imap_envelope d-flex flex-column border-bottom border-2 border-secondary-subtle pb-3 mb-3">';
1243:
1244: // Subject header (full width, centered)
1245: if (array_key_exists('subject', $env) && trim($env['subject'])) {
1246: $res .= '<div class="header_subject d-flex justify-content-center"><h5 class="text-center mb-0 fw-bold">'.$mod->html_safe($env['subject']).'</h5></div>';
1247: }
1248:
1249: // Other envelope headers
1250: foreach ($env as $name => $val) {
1251: if (in_array($name, array('date', 'from', 'to', 'message-id'), true)) {
1252: $res .= '<div class="d-flex align-items-center py-1"><span class="fw-semibold me-2 text-nowrap">'.$mod->html_safe(ucfirst($name)).':</span><span class="text-break">'.$mod->html_safe($val).'</span></div>';
1253: }
1254: }
1255: $res .= '</div>';
1256: return $res;
1257: }}
1258:
1259: /**
1260: * @subpackage imap/functions
1261: */
1262: if (!hm_exists('format_list_headers')) {
1263: function format_list_headers($mod) {
1264: $res = '<div class="row g-0 py-1 small_header">';
1265: $res .= '<div class="col-md-2 col-12"><span class="text-muted">'.$mod->trans('List').'</span></div>';
1266: $res .= '<div class="col-md-10 col-12">';
1267: $sections = array();
1268: foreach ($mod->get('list_headers') as $name => $vals) {
1269: if (count($vals['email']) > 0 || count($vals['links']) > 0) {
1270: $sources = array();
1271: $section = '<div><p class="mb-1">'.$mod->html_safe($name).':</p>';
1272: foreach ($vals['email'] as $v) {
1273: $sources[] = '<a href="?page=compose&compose_to='.urlencode($mod->html_safe($v)).
1274: '&compose_from='.$mod->get('msg_headers')['Delivered-To'].
1275: '" class="text-decoration-none">'.$mod->trans('email').'</a>';
1276: }
1277: foreach ($vals['links'] as $v) {
1278: $sources[] = '<a href="'.$mod->html_safe($v).'" class="text-decoration-none">'.$mod->trans('link').'</a>';
1279: }
1280: $section .= implode(', ', $sources).'</div>';
1281: $sections[] = $section;
1282: }
1283: }
1284: $res .= implode(' | ', $sections).'</div></div>';
1285: return $res;
1286: }}
1287:
1288: /**
1289: * @subpackage imap/functions
1290: */
1291: if (!hm_exists('decode_folder_str')) {
1292: function decode_folder_str($folder) {
1293: $folder_name = false;
1294: $parts = explode('_', $folder, 3);
1295: if (count($parts) == 3) {
1296: $folder_name = hex2bin($parts[2]);
1297: }
1298: return $folder_name;
1299: }}
1300:
1301: /**
1302: * @subpackage imap/functions
1303: */
1304: if (!hm_exists('prep_folder_name')) {
1305: function prep_folder_name($imap, $folder, $decode_folder=false, $parent=false) {
1306: if ($parent && $decode_folder) {
1307: $parent = decode_folder_str($parent);
1308: }
1309: if ($decode_folder) {
1310: $folder = decode_folder_str($folder);
1311: }
1312: $ns = get_personal_ns($imap);
1313: if (!$folder) {
1314: return false;
1315: }
1316: if ($parent && !$ns['delim']) {
1317: return false;
1318: }
1319: if ($parent) {
1320: $folder = sprintf('%s%s%s', $parent, $ns['delim'], $folder);
1321: }
1322: if ($folder && $ns['prefix'] && mb_substr($folder, 0, mb_strlen($ns['prefix'])) !== $ns['prefix']) {
1323: $folder = sprintf('%s%s', $ns['prefix'], $folder);
1324: }
1325: return $folder;
1326: }}
1327:
1328: /**
1329: * @subpackage imap/functions
1330: */
1331: if (!hm_exists('get_personal_ns')) {
1332: function get_personal_ns($imap) {
1333: $namespaces = $imap->get_namespaces();
1334: foreach ($namespaces as $ns) {
1335: if ($ns['class'] == 'personal') {
1336: return $ns;
1337: }
1338: }
1339: return array(
1340: 'prefix' => false,
1341: 'delim'=> false
1342: );
1343: }}
1344:
1345: /**
1346: * @subpackage imap/functions
1347: */
1348: if (!hm_exists('get_request_params')) {
1349: function get_request_params($request) {
1350: $server_id = NULL;
1351: $uid = NULL;
1352: $folder = NULL;
1353: $msg_id = NULL;
1354:
1355: if (array_key_exists('uid', $request) && $request['uid']) {
1356: $uid = $request['uid'];
1357: }
1358: if (array_key_exists('list_path', $request) && preg_match("/^imap_(\w+)_(.+)/", $request['list_path'], $matches)) {
1359: $server_id = $matches[1];
1360: $folder = hex2bin($matches[2]);
1361: }
1362: if (array_key_exists('imap_msg_part', $request) && preg_match("/^[0-9\.]+$/", $request['imap_msg_part'])) {
1363: $msg_id = preg_replace("/^0.{1}/", '', $request['imap_msg_part']);
1364: }
1365:
1366: return [$server_id, $uid, $folder, $msg_id];
1367: }}
1368:
1369: if (!hm_exists('snooze_message')) {
1370: function snooze_message($mailbox, $msg_id, $folder, $snooze_tag) {
1371: if (!$snooze_tag) {
1372: $mailbox->message_action($folder, 'UNREAD', array($msg_id));
1373: }
1374: $msg = $mailbox->get_message_content($folder, $msg_id);
1375: preg_match("/^X-Snoozed:.*(\r?\n[ \t]+.*)*\r?\n?/im", $msg, $matches);
1376: if (count($matches)) {
1377: $msg = str_replace($matches[0], '', $msg);
1378: $old_folder = parse_delayed_header($matches[0], 'X-Snoozed')['from'];
1379: }
1380: if ($snooze_tag) {
1381: $from = $old_folder ?? $folder;
1382: $msg = "$snooze_tag;\n \tfrom $from\n".$msg;
1383: }
1384: $msg = str_replace("\r\n", "\n", $msg);
1385: $msg = str_replace("\n", "\r\n", $msg);
1386: $msg = rtrim($msg)."\r\n";
1387:
1388: $res = false;
1389: $snooze_folder = 'Snoozed';
1390: if ($snooze_tag) {
1391: $status = $mailbox->get_folder_status($snooze_folder);
1392: if (! count($status)) {
1393: $snooze_folder = $mailbox->create_folder($snooze_folder);
1394: } else {
1395: $snooze_folder = $status['id'];
1396: }
1397: if ($mailbox->store_message($snooze_folder, $msg)) {
1398: $deleteResult = $mailbox->message_action($folder, 'DELETE', array($msg_id));
1399: if ($deleteResult['status']) {
1400: $mailbox->message_action($folder, 'EXPUNGE', array($msg_id));
1401: $res = true;
1402: }
1403: }
1404: } else {
1405: $snooze_headers = parse_delayed_header($matches[0], 'X-Snoozed');
1406: $original_folder = $snooze_headers['from'];
1407: if ($mailbox->store_message($original_folder, $msg)) {
1408: $deleteResult = $mailbox->message_action($snooze_folder, 'DELETE', array($msg_id));
1409: if ($deleteResult['status']) {
1410: $mailbox->message_action($snooze_folder, 'EXPUNGE', array($msg_id));
1411: $res = true;
1412: }
1413: }
1414: }
1415: return $res;
1416: }}
1417:
1418: /**
1419: * @subpackage imap/functions
1420: */
1421: if (!hm_exists('parse_snooze_header')) {
1422: function parse_snooze_header($snooze_header)
1423: {
1424: $snooze_header = str_replace('X-Snoozed: ', '', $snooze_header);
1425: $result = [];
1426: foreach (explode(';', $snooze_header) as $kv)
1427: {
1428: $kv = trim($kv);
1429: $spacePos = mb_strpos($kv, ' ');
1430: if ($spacePos > 0) {
1431: $result[rtrim(mb_substr($kv, 0, $spacePos), ':')] = trim(mb_substr($kv, $spacePos+1));
1432: } else {
1433: $result[$kv] = true;
1434: }
1435: }
1436: return $result;
1437: }}
1438:
1439: /**
1440: * @subpackage imap/functions
1441: */
1442: if (!hm_exists('get_snooze_date')) {
1443: function get_snooze_date($format, $only_label = false) {
1444: if ($format == 'later_in_day') {
1445: $date_string = 'today 18:00';
1446: $label = 'Later in the day';
1447: } elseif ($format == 'tomorrow') {
1448: $date_string = '+1 day 08:00';
1449: $label = 'Tomorrow';
1450: } elseif ($format == 'next_weekend') {
1451: $date_string = 'next Saturday 08:00';
1452: $label = 'Next weekend';
1453: } elseif ($format == 'next_week') {
1454: $date_string = 'next week 08:00';
1455: $label = 'Next week';
1456: } elseif ($format == 'next_month') {
1457: $date_string = 'next month 08:00';
1458: $label = 'Next month';
1459: } else {
1460: $date_string = $format;
1461: $label = 'Certain date';
1462: }
1463: $time = strtotime($date_string);
1464: if ($only_label) {
1465: return [$label, date('D, H:i', $time)];
1466: }
1467: return date('D, d M Y H:i', $time);
1468: }}
1469:
1470: /**
1471: * @subpackage imap/functions
1472: */
1473: if (!hm_exists('snooze_formats')) {
1474: function snooze_formats() {
1475: $values = array(
1476: 'tomorrow',
1477: 'next_weekend',
1478: 'next_week',
1479: 'next_month'
1480: );
1481: if (date('H') <= 16) {
1482: array_push($values, 'later_in_day');
1483: }
1484: return $values;
1485: }}
1486:
1487: /**
1488: * @subpackage imap/functions
1489: */
1490: if (!hm_exists('snooze_dropdown')) {
1491: function snooze_dropdown($output, $unsnooze = false) {
1492: $values = nexter_formats();
1493:
1494: $txt = '<div class="dropdown d-inline-block">
1495: <a class="hlink text-decoration-none btn btn-sm btn-outline-secondary dropdown-toggle" id="dropdownMenuSnooze" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-bs-auto-close="outside">'.$output->trans('Snooze').'</a>
1496: <ul class="dropdown-menu" aria-labelledby="dropdownMenuSnooze">';
1497: foreach ($values as $format) {
1498: $labels = get_scheduled_date($format, true);
1499: $txt .= '<li><a href="#" class="nexter_date_helper_snooze dropdown-item gap-5" data-value="'.$format.'"><span>'.$output->trans($labels[0]).'</span> <span class="text-end">'.$labels[1].'</span></a></li>';
1500: }
1501: $txt .= '<li><hr class="dropdown-divider"></li>';
1502: $txt .= '<li><label for="nexter_input_date_snooze" class="nexter_date_picker_snooze dropdown-item cursor-pointer">'.$output->trans('Pick a date').'</label>';
1503: $txt .= '<input id="nexter_input_date_snooze" type="datetime-local" min="'.date('Y-m-d\Th:m').'" class="nexter_input_date_snooze" style="visibility: hidden; position: absolute; height: 0;">';
1504: $txt .= '<input class="nexter_input_snooze" style="display:none;"></li>';
1505: if ($unsnooze) {
1506: $txt .= '<a href="#" data-value="unsnooze" class="unsnooze nexter_date_helper_snooze dropdown-item"">'.$output->trans('Unsnooze').'</a>';
1507: }
1508: $txt .= '</ul></div>';
1509:
1510: return $txt;
1511: }}
1512:
1513: if (!hm_exists('tags_dropdown')) {
1514: function tags_dropdown($context, $headers) {
1515: $folders = $context->get('tags', array());
1516: $txt = '<div class="dropdown d-inline-block">
1517: <a class="hlink text-decoration-none btn btn-sm btn-outline-secondary dropdown-toggle" id="dropdownMenuTag" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">'.$context->trans('Tags').'</a>
1518: <ul class="dropdown-menu" aria-labelledby="dropdownMenuTag">';
1519:
1520: $tags = !empty($headers['X-Cypht-Tags']) ? explode(',', $headers['X-Cypht-Tags']) : array();
1521: foreach ($folders as $folder) {
1522: $tag = $folder['name'];
1523: $is_checked = in_array($folder['id'], array_map('trim', $tags));
1524: $txt .= '<li class="d-flex dropdown-item gap-2">';
1525: $txt .= '<input class="form-check-input me-1 label-checkbox" type="checkbox" value="" aria-label="..." data-id="'.$folder['id'].'" '.($is_checked ? 'checked' : '').'>';
1526: $txt .= '<span>'.$context->trans($tag).'</span>';
1527: $txt .= '</li>';
1528: }
1529: $txt .= '</ul></div>';
1530:
1531: return $txt;
1532: }}
1533:
1534: /**
1535: * @subpackage imap/functions
1536: */
1537: if (!hm_exists('forward_dropdown')) {
1538: function forward_dropdown($output,$reply_args) {
1539: $txt = '<div class="dropdown d-inline-block">
1540: <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" id="dropdownMenuForward" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">'.$output->trans('Forward').'</button>
1541: <ul class="dropdown-menu" aria-labelledby="dropdownMenuForward">';
1542: $txt .= '<li><a href="?page=compose&amp;forward_as_attachment=1'.$reply_args.'" class="forward_link hlink dropdown-item d-flex justify-content-between gap-5 text-decoration-none" ><span>'.$output->trans('Forward as message attachment').'</a></li>';
1543: $txt .= '<li><a href="?page=compose&amp;forward=1'.$reply_args.'" class="forward_link hlink dropdown-item d-flex justify-content-between gap-5 text-decoration-none"><span>'.$output->trans('Edit as new message').'</a></li>';
1544: $txt .= '</ul></div>';
1545: return $txt;
1546: }
1547: }
1548:
1549: if (!hm_exists('connect_to_imap_server')) {
1550: function connect_to_imap_server($address, $name, $port, $user, $pass, $tls, $imap_sieve_host, $enableSieve, $type, $context, $hidden = false, $server_id = false, $sieve_tls = false, $show_errors = true) {
1551: $imap_list = array(
1552: 'name' => $name,
1553: 'server' => $address,
1554: 'type' => $type,
1555: 'hide' => $hidden,
1556: 'port' => $port,
1557: 'user' => $user,
1558: 'tls' => $tls);
1559:
1560: if (!$server_id || ($server_id && $pass)) {
1561: $imap_list['pass'] = $pass;
1562: }
1563:
1564: if ($type === 'jmap') {
1565: $imap_list['port'] = false;
1566: $imap_list['tls'] = false;
1567: }
1568:
1569: if ($enableSieve && $imap_sieve_host) {
1570: $imap_list['sieve_config_host'] = $imap_sieve_host;
1571: $imap_list['sieve_tls'] = $sieve_tls;
1572: }
1573: if ($server_id) {
1574: if (Hm_IMAP_List::edit($server_id, $imap_list)) {
1575: $imap_server_id = $server_id;
1576: } else {
1577: return;
1578: }
1579: } else {
1580: $imap_server_id = Hm_IMAP_List::add($imap_list);
1581: if ($type != 'ews' && ! can_save_last_added_server('Hm_IMAP_List', $user)) {
1582: return;
1583: }
1584: }
1585:
1586: $server = Hm_IMAP_List::get($imap_server_id, true);
1587:
1588: if ($enableSieve &&
1589: $imap_sieve_host &&
1590: $context->module_is_supported('sievefilters') &&
1591: $context->user_config->get('enable_sieve_filter_setting', DEFAULT_ENABLE_SIEVE_FILTER)) {
1592: try {
1593:
1594: include_once APP_PATH.'modules/sievefilters/hm-sieve.php';
1595: $sieveClientFactory = new Hm_Sieve_Client_Factory();
1596: $client = $sieveClientFactory->init(null, $server, $context->module_is_supported('nux'));
1597:
1598: if (!$client && $show_errors) {
1599: Hm_Msgs::add("Failed to authenticate to the Sieve host", "warning");
1600: }
1601: } catch (Exception $e) {
1602: if ($show_errors) {
1603: Hm_Msgs::add("Failed to authenticate to the Sieve host", "warning");
1604: }
1605: if (! $server_id) {
1606: Hm_IMAP_List::del($imap_server_id);
1607: }
1608: return;
1609: }
1610: }
1611:
1612: $mailbox = Hm_IMAP_List::connect($imap_server_id, false);
1613:
1614: if ($mailbox->authed()) {
1615: return $imap_server_id;
1616: } else {
1617: Hm_IMAP_List::del($imap_server_id);
1618: if ($show_errors) {
1619: Hm_Msgs::add('Authentication failed', 'warning');
1620: }
1621: return null;
1622: }
1623: }
1624: }
1625:
1626: if (!hm_exists('save_sent_msg')) {
1627: function save_sent_msg($handler, $imap_id, $mailbox, $imap_details, $msg, $msg_id, $show_errors = true) {
1628: $specials = get_special_folders($handler, $imap_id);
1629: $sent_folder = false;
1630: if (array_key_exists('sent', $specials) && $specials['sent']) {
1631: $sent_folder = $specials['sent'];
1632: }
1633:
1634: if (!$sent_folder) {
1635: $auto_sent = $mailbox->get_special_use_mailboxes('sent');
1636: if (!array_key_exists('sent', $auto_sent)) {
1637: return;
1638: }
1639: $sent_folder = $auto_sent['sent'];
1640: }
1641: if (!$sent_folder) {
1642: Hm_Debug::add(sprintf("Unable to save sent message, no sent folder for server %s %s", $mailbox->server_type(), $imap_details['server']));
1643: }
1644: $uid = null;
1645: if ($sent_folder) {
1646: Hm_Debug::add(sprintf("Attempting to save sent message for server %s in folder %s", $mailbox->server_type(), $imap_details['server'], $sent_folder));
1647: $uid = $mailbox->store_message($sent_folder, $msg);
1648: if (! $uid) {
1649: Hm_Msgs::add('ERRAn error occurred saving the sent message');
1650: }
1651: }
1652: return [$uid, $sent_folder];
1653: }}
1654:
1655: if (!hm_exists('is_imap_archive_folder')) {
1656: function is_imap_archive_folder($server_id, $user_config, $current_folder) {
1657: $special_folders = $user_config->get('special_imap_folders', array());
1658:
1659: if (isset($special_folders[$server_id]['archive'])) {
1660: $archive_folder = $special_folders[$server_id]['archive'];
1661: if (bin2hex($archive_folder) == $current_folder) {
1662: return true;
1663: }
1664: }
1665:
1666: return false;
1667: }}
1668: