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&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.'&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.'&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