├── README.md ├── .gitignore ├── application ├── config │ └── imap.php └── libraries │ └── Imap.php └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # IMAP Libray to CodeIgniter 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !application/config/imap.php 3 | !application/libraries/Imap.php 4 | !README.md 5 | !LICENSE 6 | -------------------------------------------------------------------------------- /application/config/imap.php: -------------------------------------------------------------------------------- 1 | 'INBOX', 13 | 'sent' => 'Sent', 14 | 'trash' => 'Trash', 15 | 'spam' => 'Spam', 16 | 'drafts' => 'Drafts', 17 | ]; 18 | 19 | $config['expunge_on_disconnect'] = false; 20 | 21 | $config['cache'] = [ 22 | 'active' => false, 23 | 'adapter' => 'file', 24 | 'backup' => 'file', 25 | 'key_prefix' => 'imap:', 26 | 'ttl' => 60, 27 | ]; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Natan Felles 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of codeigniter-imap nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /application/libraries/Imap.php: -------------------------------------------------------------------------------- 1 | CI =& get_instance(); 71 | 72 | if (! empty($config)) 73 | { 74 | $this->config = $config; 75 | //$this->connect(); 76 | } 77 | } 78 | 79 | /** 80 | * @param array $config Options: host, encrypto, user, pass, port, folders 81 | * 82 | * @return boolean True if is connected 83 | */ 84 | public function connect(array $config = []) 85 | { 86 | $config = array_replace_recursive($this->config, $config); 87 | $this->config = $config; 88 | 89 | if ($config['cache']['active'] === true) 90 | { 91 | $this->CI->load->driver('cache', 92 | [ 93 | 'adapter' => $config['cache']['adapter'], 94 | 'backup' => $config['cache']['backup'], 95 | 'key_prefix' => $config['cache']['key_prefix'], 96 | ]); 97 | } 98 | 99 | $enc = ''; 100 | 101 | if (isset($config['port'])) 102 | { 103 | $enc .= ':' . $config['port']; 104 | } 105 | 106 | if (isset($config['encrypto'])) 107 | { 108 | $enc .= '/' . $config['encrypto']; 109 | } 110 | 111 | if (isset($config['validate']) && $config['validate'] === false) 112 | { 113 | $enc .= '/novalidate-cert'; 114 | } 115 | 116 | $this->mailbox = '{' . $config['host'] . $enc . '}'; 117 | $this->stream = imap_open($this->mailbox, $config['username'], $config['password']); 118 | 119 | //show_error($this->get_last_error()); 120 | 121 | return is_resource($this->stream); 122 | } 123 | 124 | protected function set_cache($cache_id, $data) 125 | { 126 | if ($this->config['cache']['active'] === true) 127 | { 128 | return $this->CI->cache->save($cache_id, $data, $this->config['cache']['ttl']); 129 | } 130 | 131 | return true; 132 | } 133 | 134 | protected function get_cache($cache_id) 135 | { 136 | if ($this->config['cache']['active'] === true) 137 | { 138 | return $this->CI->cache->get($cache_id); 139 | } 140 | 141 | return false; 142 | } 143 | 144 | /** 145 | * [set_timeout description] 146 | * 147 | * @param integer $timeout 148 | * @param string $type open, read, write, or close 149 | * 150 | * @return boolean 151 | */ 152 | public function set_timeout(int $timeout = 60, string $type = 'open') 153 | { 154 | $types = [ 155 | 'open' => IMAP_OPENTIMEOUT, 156 | 'read' => IMAP_READTIMEOUT, 157 | 'write' => IMAP_WRITETIMEOUT, 158 | 'close' => IMAP_CLOSETIMEOUT, 159 | ]; 160 | 161 | return imap_timeout($types[$type], $timeout); 162 | } 163 | 164 | /** 165 | * [get_timeout description] 166 | * 167 | * @param string $type open, read, write, or close 168 | * 169 | * @return integer 170 | */ 171 | public function get_timeout(string $type = 'open') 172 | { 173 | $types = [ 174 | 'open' => IMAP_OPENTIMEOUT, 175 | 'read' => IMAP_READTIMEOUT, 176 | 'write' => IMAP_WRITETIMEOUT, 177 | 'close' => IMAP_CLOSETIMEOUT, 178 | ]; 179 | 180 | return imap_timeout($types[$type]); 181 | } 182 | 183 | /** 184 | * [ping description] 185 | * 186 | * @return boolean 187 | */ 188 | public function ping() 189 | { 190 | //return $this->fun('ping'); 191 | return imap_ping($this->stream); 192 | } 193 | 194 | /** 195 | * [disconnect description] 196 | * 197 | * @return boolean 198 | */ 199 | public function disconnect() 200 | { 201 | if (is_resource($this->stream)) 202 | { 203 | if ($this->config['expunge_on_disconnect'] === true) 204 | { 205 | $this->select_folder($this->get_trash_folder()); 206 | $this->mark_as_deleted($this->search()); 207 | $this->expunge(); 208 | } 209 | 210 | // Clears all errors before to close 211 | // See: https://github.com/natanfelles/codeigniter-imap/issues/5#issuecomment-355453233 212 | imap_errors(); 213 | 214 | return imap_close($this->stream); 215 | } 216 | 217 | return true; 218 | } 219 | 220 | /** 221 | * [set_expunge_on_disconnect description] 222 | * 223 | * @param boolean $active 224 | * 225 | * @return Imap 226 | */ 227 | public function set_expunge_on_disconnect(bool $active = true) 228 | { 229 | $this->config['expunge_on_disconnect'] = $active; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * [expunge description] 236 | * 237 | * @return boolean 238 | */ 239 | public function expunge() 240 | { 241 | return imap_expunge($this->stream); 242 | } 243 | 244 | /** 245 | * Gets the last IMAP error that occurred during this page request 246 | * 247 | * @return string|boolean Last error message or false if no errors 248 | */ 249 | public function get_last_error() 250 | { 251 | return imap_last_error(); 252 | } 253 | 254 | /** 255 | * [get_errors description] 256 | * 257 | * @return array|boolean Array of errors or false if no errors 258 | */ 259 | public function get_errors() 260 | { 261 | return imap_errors(); 262 | } 263 | 264 | public function get_alerts() 265 | { 266 | return imap_alerts(); 267 | } 268 | 269 | /** 270 | * Get all folders names 271 | * 272 | * @return array Array of folder names. If an item is an array then 273 | * this is an associative array of "folder" => [subfolders] 274 | */ 275 | public function get_folders() 276 | { 277 | $folders = imap_list($this->stream, $this->mailbox, '*'); 278 | $folders = $this->get_subfolders(str_replace($this->mailbox, '', $folders)); 279 | 280 | sort($folders); 281 | 282 | return $folders; 283 | } 284 | 285 | protected function get_subfolders($folders) 286 | { 287 | for ($i = 0; $i < count($folders); $i++) 288 | { 289 | if (isset(explode('.', $folders[$i])[1])) 290 | { 291 | $folders[$i] = $this->get_subfolders($folders[$i]); 292 | } 293 | } 294 | 295 | // for ($i = 0; $i < count($folders); $i++) 296 | // { 297 | // if (strpos($folders[$i],'.') !== false) 298 | // { 299 | // $folders[$folders[$i]] = $this->static_dot_notation($folders[$i]); 300 | // } 301 | // } 302 | 303 | return $folders; 304 | } 305 | 306 | // function static_dot_notation($string, $value = null) 307 | // { 308 | // static $return; 309 | 310 | // $token = strtok($string, '.'); 311 | 312 | // $ref =& $return; 313 | 314 | // while ($token !== false) 315 | // { 316 | // $ref =& $ref[$token]; 317 | // $token = strtok('.'); 318 | // } 319 | 320 | // $ref = $value; 321 | 322 | // return $return; 323 | // } 324 | 325 | /** 326 | * Select folder 327 | * 328 | * @param string $folder Folder name 329 | * 330 | * @return boolean 331 | */ 332 | public function select_folder(string $folder) 333 | { 334 | if ($result = imap_reopen($this->stream, $this->mailbox . $folder)) 335 | { 336 | $this->folder = $folder; 337 | } 338 | 339 | return $result; 340 | } 341 | 342 | /** 343 | * Add folder 344 | * 345 | * @param string $name Folder name 346 | * 347 | * @return boolean 348 | */ 349 | public function add_folder(string $folder_name) 350 | { 351 | return imap_createmailbox($this->stream, $this->mailbox . $folder_name); 352 | } 353 | 354 | /** 355 | * Rename folder 356 | * 357 | * @param string $name Current folder name 358 | * @param string $new_name New folder name 359 | * 360 | * @return boolean TRUE on success or FALSE on failure. 361 | */ 362 | public function rename_folder(string $name, string $new_name) 363 | { 364 | return imap_renamemailbox($this->stream, $this->mailbox . $name, $this->mailbox . $new_name); 365 | } 366 | 367 | /** 368 | * Remove folder 369 | * 370 | * @param string $folder_name 371 | * 372 | * @return boolean TRUE on success or FALSE on failure. 373 | */ 374 | public function remove_folder(string $folder_name) 375 | { 376 | return imap_deletemailbox($this->stream, $this->mailbox . $folder_name); 377 | } 378 | 379 | /** 380 | * Count all messages in the current or given folder, 381 | * optionally matching a criteria 382 | * 383 | * @param string $folder 384 | * @param string $flag_criteria Ex: RECENT, SEEN, UNSEEN, FROM "a@b.cc" 385 | * 386 | * @return integer 387 | */ 388 | public function count_messages(string $folder = null, string $flag_criteria = null) 389 | { 390 | $current_folder = $this->folder; 391 | 392 | if (isset($folder)) 393 | { 394 | $this->select_folder($folder); 395 | } 396 | 397 | if ($flag_criteria) 398 | { 399 | $count = count($this->search($flag_criteria)); 400 | } 401 | else 402 | { 403 | $count = imap_num_msg($this->stream); 404 | } 405 | 406 | if (isset($folder)) 407 | { 408 | $this->select_folder($current_folder); 409 | } 410 | 411 | return $count; 412 | } 413 | 414 | /** 415 | * Get quota usage and limit from mail account 416 | * 417 | * @return array 418 | */ 419 | public function get_quota(string $folder = null) 420 | { 421 | $current_folder = $this->folder; 422 | 423 | if (isset($folder)) 424 | { 425 | $this->select_folder($folder); 426 | } 427 | 428 | $quota = imap_get_quotaroot($this->stream, $this->mailbox . $folder); 429 | 430 | if (isset($folder)) 431 | { 432 | $this->select_folder($current_folder); 433 | } 434 | 435 | return $quota; 436 | } 437 | 438 | /** 439 | * [move_messages description] 440 | * 441 | * @param mixed $uids Array list of uid's or a comma separated list of uids 442 | * @param string $target 443 | * 444 | * @return boolean 445 | */ 446 | public function move_messages($uids, string $target) 447 | { 448 | if (is_array($uids)) 449 | { 450 | $uids = implode(',', $uids); 451 | } 452 | 453 | if (imap_mail_move($this->stream, str_replace(' ', '', $uids), $target, CP_UID)) 454 | { 455 | // Expunge is necessary to remove the original message that was 456 | // automatically marked as deleted when moved 457 | return imap_expunge($this->stream); 458 | } 459 | 460 | return false; 461 | } 462 | 463 | public function move_to_inbox($uids) 464 | { 465 | return $this->move_messages($uids, $this->get_inbox_folder()); 466 | } 467 | 468 | public function move_to_trash($uids) 469 | { 470 | return $this->move_messages($uids, $this->get_trash_folder()); 471 | } 472 | 473 | public function move_to_draft($uids) 474 | { 475 | return $this->move_messages($uids, $this->get_draft_folder()); 476 | } 477 | 478 | public function move_to_spam($uids) 479 | { 480 | return $this->move_messages($uids, $this->get_spam_folder()); 481 | } 482 | 483 | public function move_to_sent($uids) 484 | { 485 | return $this->move_messages($uids, $this->get_sent_folder()); 486 | } 487 | 488 | /** 489 | * [move_messages description] 490 | * 491 | * @param mixed $uids Array list of uid's or a comma separated list of uids 492 | * 493 | * @return boolean 494 | */ 495 | public function mark_as_read($uids) 496 | { 497 | return $this->message_setflag($uids, 'Seen'); 498 | } 499 | 500 | /** 501 | * [move_messages description] 502 | * 503 | * @param mixed $uids Array list of uid's or a comma separated list of uids 504 | * 505 | * @return boolean 506 | */ 507 | public function mark_as_unread($uids) 508 | { 509 | return $this->message_clearflag($uids, 'Seen'); 510 | } 511 | 512 | /** 513 | * [move_messages description] 514 | * 515 | * @param mixed $uids Array list of uid's or a comma separated list of uids 516 | * 517 | * @return boolean 518 | */ 519 | public function mark_as_answered($uids) 520 | { 521 | return $this->message_setflag($uids, 'Answered'); 522 | } 523 | 524 | /** 525 | * [move_messages description] 526 | * 527 | * @param mixed $uids Array list of uid's or a comma separated list of uids 528 | * 529 | * @return boolean 530 | */ 531 | public function mark_as_unanswered($uids) 532 | { 533 | return $this->message_clearflag($uids, 'Answered'); 534 | } 535 | 536 | /** 537 | * [move_messages description] 538 | * 539 | * @param mixed $uids Array list of uid's or a comma separated list of uids 540 | * 541 | * @return boolean 542 | */ 543 | public function mark_as_flagged($uids) 544 | { 545 | return $this->message_setflag($uids, 'Flagged'); 546 | } 547 | 548 | /** 549 | * [move_messages description] 550 | * 551 | * @param mixed $uids Array list of uid's or a comma separated list of uids 552 | * 553 | * @return boolean 554 | */ 555 | public function mark_as_unflagged($uids) 556 | { 557 | return $this->message_clearflag($uids, 'Flagged'); 558 | } 559 | 560 | /** 561 | * [move_messages description] 562 | * 563 | * @param mixed $uids Array list of uid's or a comma separated list of uids 564 | * 565 | * @return boolean 566 | */ 567 | public function mark_as_deleted($uids) 568 | { 569 | return $this->message_setflag($uids, 'Deleted'); 570 | } 571 | 572 | /** 573 | * [move_messages description] 574 | * 575 | * @param mixed $uids Array list of uid's or a comma separated list of uids 576 | * 577 | * @return boolean 578 | */ 579 | public function mark_as_undeleted($uids) 580 | { 581 | return $this->message_clearflag($uids, 'Deleted'); 582 | } 583 | 584 | /** 585 | * [move_messages description] 586 | * 587 | * @param mixed $uids Array list of uid's or a comma separated list of uids 588 | * 589 | * @return boolean 590 | */ 591 | public function mark_as_draft($uids) 592 | { 593 | return $this->message_setflag($uids, 'Draft'); 594 | } 595 | 596 | /** 597 | * [move_messages description] 598 | * 599 | * @param mixed $uids Array list of uid's or a comma separated list of uids 600 | * 601 | * @return boolean 602 | */ 603 | public function mark_as_undraft($uids) 604 | { 605 | return $this->message_clearflag($uids, 'Draft'); 606 | } 607 | 608 | /** 609 | * [message_setflag description] 610 | * 611 | * @param mixed $uids Array list of uid's or a comma separated list of uids 612 | * @param string $flag 613 | * 614 | * @return boolean 615 | */ 616 | protected function message_setflag($uids, string $flag) 617 | { 618 | if (is_array($uids)) 619 | { 620 | $uids = implode(',', $uids); 621 | } 622 | 623 | return imap_setflag_full($this->stream, str_replace(' ', '', $uids), '\\' . ucfirst($flag), ST_UID); 624 | } 625 | 626 | /** 627 | * [message_clearflag description] 628 | * 629 | * @param mixed $uids Array list of uid's or a comma separated list of uids 630 | * @param string $flag 631 | * 632 | * @return boolean 633 | */ 634 | protected function message_clearflag($uids, string $flag) 635 | { 636 | if (is_array($uids)) 637 | { 638 | $uids = implode(',', $uids); 639 | } 640 | 641 | return imap_clearflag_full($this->stream, str_replace(' ', '', $uids), '\\' . ucfirst($flag), ST_UID); 642 | } 643 | 644 | /** 645 | * Get all email addresses from all messages 646 | * 647 | * @return array Array with all email addresses 648 | */ 649 | public function get_all_email_addresses() 650 | { 651 | $cache_id = 'email_addresses'; 652 | 653 | if (($cache = $this->get_cache($cache_id)) !== false) 654 | { 655 | return $cache; 656 | } 657 | 658 | $current_folder = $this->folder; 659 | $contacts = []; 660 | 661 | foreach ($this->get_folders() as $folder) 662 | { 663 | $this->select_folder($folder); 664 | 665 | $uids = $this->search(); 666 | 667 | foreach ($uids as $uid) 668 | { 669 | $msg = $this->get_message($uid); 670 | 671 | // As we get the messages uid's ordering by newest we do not 672 | // need to replace the name if we already have the most recent 673 | $contacts[$msg['from']['email']] = isset($contacts[$msg['from']['email']]) 674 | ? $contacts[$msg['from']['email']] 675 | : $msg['from']['name']; 676 | 677 | foreach (['to', 'cc', 'bcc'] as $field) 678 | { 679 | foreach ($msg[$field] as $i) 680 | { 681 | $contacts[$i['email']] = isset($contacts[$i['email']]) 682 | ? $contacts[$i['email']] 683 | : $i['name']; 684 | } 685 | } 686 | } 687 | } 688 | 689 | ksort($contacts); 690 | 691 | $this->select_folder($current_folder); 692 | 693 | $this->set_cache($cache_id, $contacts); 694 | 695 | return $contacts; 696 | } 697 | 698 | /** 699 | * Return content of messages attachment 700 | * Save the attachment in a optional path or get the binary code in the content index 701 | * 702 | * @param integer $uid Message uid 703 | * @param integer $index Index of the attachment - 0 to the first attachment 704 | * 705 | * @return array|boolean False if attachment could not be get 706 | */ 707 | public function get_attachment(int $uid, int $index = 0) 708 | { 709 | $cache_id = $this->folder . ':message_' . $uid . ':attachment_' . $index; 710 | 711 | if (($cache = $this->get_cache($cache_id)) !== false) 712 | { 713 | return $cache; 714 | } 715 | 716 | $id = imap_msgno($this->stream, $uid); 717 | $structure = imap_fetchstructure($this->stream, $id); 718 | $attachment = $this->_get_attachments($uid, $structure, '', $index); 719 | 720 | $this->set_cache($cache_id, $attachment); 721 | 722 | if (empty($attachment)) 723 | { 724 | return false; 725 | } 726 | 727 | return $attachment; 728 | } 729 | 730 | /** 731 | * [get_attachments description] 732 | * 733 | * @param integer $uid 734 | * @param array $indexes 735 | * 736 | * @return array 737 | */ 738 | public function get_attachments(int $uid, array $indexes = []) 739 | { 740 | $attachments = []; 741 | 742 | foreach ($indexes as $index) 743 | { 744 | $attachments[] = $this->get_attachment($uid, (int)$index); 745 | } 746 | 747 | return $attachments; 748 | } 749 | 750 | /** 751 | * [_get_attachments description] 752 | * 753 | * @param integer $uid 754 | * @param object $structure 755 | * @param string $part_number 756 | * @param integer|null $index 757 | * @param boolean $with_content 758 | * 759 | * @return array 760 | */ 761 | protected function _get_attachments(int $uid, $structure, string $part_number = '', int $index = null) 762 | { 763 | $id = imap_msgno($this->stream, $uid); 764 | $attachments = []; 765 | 766 | if (isset($structure->parts)) 767 | { 768 | foreach ($structure->parts as $key => $sub_structure) 769 | { 770 | $new_part_number = empty($part_number) ? $key + 1 : $part_number . '.' . ($key + 1); 771 | 772 | $results = $this->_get_attachments($uid, $sub_structure, $new_part_number); 773 | 774 | if (count($results)) 775 | { 776 | if (isset($results[0]['name'])) 777 | { 778 | foreach ($results as $result) 779 | { 780 | array_push($attachments, $result); 781 | } 782 | } 783 | else 784 | { 785 | array_push($attachments, $results); 786 | } 787 | } 788 | 789 | // If we already have the given indexes return here 790 | if (! is_null($index) && isset($attachments[$index])) 791 | { 792 | return $attachments[$index]; 793 | } 794 | } 795 | } 796 | else 797 | { 798 | $attachment = []; 799 | 800 | if (isset($structure->dparameters[0])) 801 | { 802 | $bodystruct = imap_bodystruct($this->stream, $id, $part_number); 803 | $decoded_name = imap_mime_header_decode($bodystruct->dparameters[0]->value); 804 | $filename = $this->convert_to_utf8($decoded_name[0]->text); 805 | $content = imap_fetchbody($this->stream, $id, $part_number); 806 | $content = (string)$this->struc_decoding($content, $bodystruct->encoding); 807 | 808 | $attachment = [ 809 | 'name' => (string)$filename, 810 | 'part_number' => (string)$part_number, 811 | 'encoding' => (int)$bodystruct->encoding, 812 | 'size' => (int)$structure->bytes, 813 | 'reference' => isset($bodystruct->id) ? (string)$bodystruct->id : '', 814 | 'disposition' => (string)strtolower($structure->disposition), 815 | 'type' => (string)strtolower($structure->subtype), 816 | 'content' => $content, 817 | 'content_size' => strlen($content), 818 | ]; 819 | } 820 | 821 | return $attachment; 822 | } 823 | 824 | return $attachments; 825 | } 826 | 827 | /** 828 | * [struc_decoding description] 829 | * 830 | * @param string $text 831 | * @param integer $encoding 832 | * 833 | * @see http://php.net/manual/pt_BR/function.imap-fetchstructure.php 834 | * 835 | * @return string 836 | */ 837 | protected function struc_decoding(string $text, int $encoding = 5) 838 | { 839 | switch ($encoding) 840 | { 841 | case ENC7BIT: // 0 7bit 842 | return $text; 843 | case ENC8BIT: // 1 8bit 844 | return imap_8bit($text); 845 | case ENCBINARY: // 2 Binary 846 | return imap_binary($text); 847 | case ENCBASE64: // 3 Base64 848 | return imap_base64($text); 849 | case ENCQUOTEDPRINTABLE: // 4 Quoted-Printable 850 | return quoted_printable_decode($text); 851 | case ENCOTHER: // 5 other 852 | return $text; 853 | default: 854 | return $text; 855 | } 856 | } 857 | 858 | protected function get_default_folder(string $type) 859 | { 860 | foreach ($this->get_folders() as $folder) 861 | { 862 | if (strtolower($folder) === strtolower($this->config['folders'][$type])) 863 | { 864 | return $folder; 865 | } 866 | } 867 | 868 | // No folder found? Create one 869 | $this->add_folder($this->config['folders'][$type]); 870 | 871 | return $this->config['folders'][$type]; 872 | } 873 | 874 | protected function get_inbox_folder() 875 | { 876 | return $this->get_default_folder('inbox'); 877 | } 878 | 879 | protected function get_trash_folder() 880 | { 881 | return $this->get_default_folder('trash'); 882 | } 883 | 884 | protected function get_sent_folder() 885 | { 886 | return $this->get_default_folder('sent'); 887 | } 888 | 889 | protected function get_spam_folder() 890 | { 891 | return $this->get_default_folder('spam'); 892 | } 893 | 894 | protected function get_draft_folder() 895 | { 896 | return $this->get_default_folder('draft'); 897 | } 898 | 899 | /** 900 | * Create the final message array 901 | * 902 | * @param integer $uid Message uid 903 | * @param boolean $with_body Define if the output will get the message body 904 | * @param boolean $embed_images Define if message body will show embeded images 905 | * 906 | * @return array|boolean 907 | */ 908 | public function get_message(int $uid) 909 | { 910 | $cache_id = $this->folder . ':message_' . $uid; 911 | 912 | if (($cache = $this->get_cache($cache_id)) !== false) 913 | { 914 | return $cache; 915 | } 916 | 917 | // TODO: Maybe put this check before try get from cache 918 | // then we will know if the msg already exists 919 | $id = imap_msgno($this->stream, $uid); 920 | 921 | // If id is zero the message do not exists 922 | if ($id === 0) 923 | { 924 | return false; 925 | } 926 | 927 | $header = imap_headerinfo($this->stream, $id); 928 | 929 | // Check Priority 930 | preg_match('/X-Priority: ([\d])/mi', imap_fetchheader($this->stream, $id), $matches); 931 | $priority = isset($matches[1]) ? $matches[1] : 3; 932 | 933 | $subject = ''; 934 | 935 | if (isset($header->subject) && strlen($header->subject) > 0) 936 | { 937 | foreach (imap_mime_header_decode($header->subject) as $decoded_header) 938 | { 939 | $subject .= $decoded_header->text; 940 | } 941 | } 942 | 943 | $email = [ 944 | 'id' => (int)$id, 945 | 'uid' => (int)$uid, 946 | 'from' => isset($header->from[0]) ? (array)$this->to_address($header->from[0]) : [], 947 | 'to' => isset($header->to) ? (array)$this->array_to_address($header->to) : [], 948 | 'cc' => isset($header->cc) ? (array)$this->array_to_address($header->cc) : [], 949 | 'bcc' => isset($header->bcc) ? (array)$this->array_to_address($header->bcc) : [], 950 | 'reply_to' => isset($header->reply_to) ? (array)$this->array_to_address($header->reply_to) : [], 951 | //'return_path' => isset($header->return_path) ? (array)$this->array_to_address($header->return_path) : [], 952 | 'message_id' => $header->message_id, 953 | 'in_reply_to' => isset($header->in_reply_to) ? (string)$header->in_reply_to : '', 954 | 'references' => isset($header->references) ? explode(' ', $header->references) : [], 955 | 'date' => $header->date,//date('c', strtotime(substr($header->date, 0, 30))), 956 | 'udate' => (int)$header->udate, 957 | 'subject' => $this->convert_to_utf8($subject), 958 | 'priority' => (int)$priority, 959 | 'recent' => strlen(trim($header->Recent)) > 0, 960 | 'read' => strlen(trim($header->Unseen)) < 1, 961 | 'answered' => strlen(trim($header->Answered)) > 0, 962 | 'flagged' => strlen(trim($header->Flagged)) > 0, 963 | 'deleted' => strlen(trim($header->Deleted)) > 0, 964 | 'draft' => strlen(trim($header->Draft)) > 0, 965 | 'size' => (int)$header->Size, 966 | 'attachments' => (array)$this->_get_attachments($uid, imap_fetchstructure($this->stream, $id)), 967 | 'body' => $this->get_body($uid), 968 | ]; 969 | 970 | $email = $this->embed_images($email); 971 | 972 | for ($i = 0; $i < count($email['attachments']); $i++) 973 | { 974 | if ($email['attachments'][$i]['disposition'] !== 'attachment') 975 | { 976 | unset($email['attachments'][$i]); 977 | } 978 | } 979 | 980 | $this->set_cache($cache_id, $email); 981 | 982 | return $email; 983 | } 984 | 985 | /** 986 | * Get messages 987 | * 988 | * @param array|string $uids Array list of uid's or a comma separated list of uids 989 | * @param boolean $with_body Define if the output will get the message body 990 | * @param boolean $embed_images Define if message body will show embeded images 991 | * 992 | * @return array|boolean 993 | */ 994 | public function get_messages($uids) 995 | { 996 | $messages = []; 997 | 998 | if (empty($uids)) 999 | { 1000 | return false; 1001 | } 1002 | 1003 | if (is_string($uids)) 1004 | { 1005 | $uids = explode(',', $uids); 1006 | } 1007 | 1008 | foreach ($uids as $uid) 1009 | { 1010 | $messages[] = $this->get_message((int)$uid); 1011 | } 1012 | 1013 | return $messages; 1014 | } 1015 | 1016 | /** 1017 | * [get_eml description] 1018 | * 1019 | * @param integer $uid [description] 1020 | * 1021 | * @see https://stackoverflow.com/questions/7496266/need-to-save-a-copy-of-email-using-imap-php-and-then-can-be-open-in-outlook-expr 1022 | * 1023 | * @return string [description] 1024 | */ 1025 | public function get_eml(int $uid) 1026 | { 1027 | $headers = imap_fetchheader($this->stream, $uid, FT_UID | FT_PREFETCHTEXT); 1028 | $body = imap_body($this->stream, $uid, FT_UID); 1029 | 1030 | return $headers . "\n" . $body; 1031 | } 1032 | 1033 | public function fun(string $function, ...$params) 1034 | { 1035 | array_unshift($params, $this->stream); 1036 | 1037 | return call_user_func_array("imap_{$function}", $params); 1038 | } 1039 | 1040 | /** 1041 | * [get_threads description] 1042 | * 1043 | * @see https://stackoverflow.com/questions/16248448/php-creating-a-multidimensional-array-of-message-threads-from-a-multidimensional 1044 | * 1045 | * @return array 1046 | */ 1047 | public function get_threads() 1048 | { 1049 | $thread = imap_thread($this->stream, SE_UID); 1050 | $items = []; 1051 | 1052 | foreach ($thread as $key => $uid) 1053 | { 1054 | $item = explode('.', $key); 1055 | 1056 | $node = (int)$item[0]; 1057 | 1058 | $items[$node]['node'] = $node; 1059 | 1060 | 1061 | switch ($item[1]) { 1062 | case 'num': 1063 | $items[$node]['num'] = $uid; 1064 | $message = $this->get_message($uid); 1065 | $items[$node]['msg'] = $message['subject'] . ' - ' . $message['date']; 1066 | break; 1067 | case 'next': 1068 | $items[$node]['next'] = $uid; // node id 1069 | break; 1070 | case 'branch': 1071 | $items[$node]['branch'] = $uid; // node id 1072 | break; 1073 | } 1074 | } 1075 | 1076 | return $items; 1077 | } 1078 | 1079 | /** 1080 | * Paginate uid's returning messages by "page" number 1081 | * 1082 | * @param array $uids 1083 | * @param integer $page Starts with 1 1084 | * @param integer $per_page 1085 | * 1086 | * @return array 1087 | */ 1088 | public function paginate(array $uids, int $page = 1, int $per_page = 10) 1089 | { 1090 | if (count($uids) < $per_page * $page) 1091 | { 1092 | return []; 1093 | } 1094 | 1095 | return $this->get_messages(array_slice($uids, $per_page * $page - $per_page, $per_page)); 1096 | } 1097 | 1098 | /** 1099 | * Embed inline images in HTML Body 1100 | * 1101 | * @param array $email The email message 1102 | * 1103 | * @return array 1104 | */ 1105 | protected function embed_images(array $email) 1106 | { 1107 | foreach ($email['attachments'] as $key => $attachment) 1108 | { 1109 | if ($attachment['disposition'] === 'inline' && ! empty($attachment['reference'])) 1110 | { 1111 | $reference = str_replace(['<', '>'], '', $attachment['reference']); 1112 | $img_embed = 'data:image/' . $attachment['type'] . ';base64,' . base64_encode($attachment['content']); 1113 | 1114 | $email['body']['html'] = str_replace('cid:' . $reference, $img_embed, $email['body']['html']); 1115 | } 1116 | } 1117 | 1118 | return $email; 1119 | } 1120 | 1121 | /** 1122 | * [search description] 1123 | * 1124 | * @param string $search_criteria 1125 | * ALL - return all messages matching the rest of the criteria 1126 | * ANSWERED - match messages with the \\ANSWERED flag set 1127 | * BCC "string" - match messages with "string" in the Bcc: field 1128 | * BEFORE "date" - match messages with Date: before "date" 1129 | * BODY "string" - match messages with "string" in the body of the message 1130 | * CC "string" - match messages with "string" in the Cc: field 1131 | * DELETED - match deleted messages 1132 | * FLAGGED - match messages with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set 1133 | * FROM "string" - match messages with "string" in the From: field 1134 | * KEYWORD "string" - match messages with "string" as a keyword 1135 | * NEW - match new messages 1136 | * OLD - match old messages 1137 | * ON "date" - match messages with Date: matching "date" 1138 | * RECENT - match messages with the \\RECENT flag set 1139 | * SEEN - match messages that have been read (the \\SEEN flag is set) 1140 | * SINCE "date" - match messages with Date: after "date" 1141 | * SUBJECT "string" - match messages with "string" in the Subject: 1142 | * TEXT "string" - match messages with text "string" 1143 | * TO "string" - match messages with "string" in the To: 1144 | * UNANSWERED - match messages that have not been answered 1145 | * UNDELETED - match messages that are not deleted 1146 | * UNFLAGGED - match messages that are not flagged 1147 | * UNKEYWORD "string" - match messages that do not have the keyword "string" 1148 | * UNSEEN - match messages which have not been read yet 1149 | * @param string $sort_by 1150 | * One of: 1151 | * 'date' = message Date 1152 | * 'arrival' = arrival date 1153 | * 'from' = mailbox in first From address 1154 | * 'subject' = message subject 1155 | * 'to' = mailbox in first To address 1156 | * 'cc' = mailbox in first cc address 1157 | * 'size' = size of message in octets 1158 | * @param boolean $descending 1159 | * 1160 | * @see http://php.net/manual/pt_BR/function.imap-sort.php 1161 | * @see http://php.net/manual/pt_BR/function.imap-search.php 1162 | * 1163 | * @return array Array of uid's matching the search criteria 1164 | */ 1165 | public function search(string $search_criteria = 'ALL', string $sort_by = 'date', bool $descending = true) 1166 | { 1167 | $search_criteria = $this->search_criteria . ' ' . $search_criteria; 1168 | 1169 | $this->search_criteria = ''; 1170 | 1171 | $criterias = [ 1172 | 'date' => SORTDATE, // message Date 1173 | 'arrival' => SORTARRIVAL, // arrival date 1174 | 'from' => SORTFROM, // mailbox in first From address 1175 | 'subject' => SORTSUBJECT, // message subject 1176 | 'to' => SORTTO, // mailbox in first To address 1177 | 'cc' => SORTCC, // mailbox in first cc address 1178 | 'size' => SORTSIZE, // size of message in octets 1179 | ]; 1180 | 1181 | return imap_sort($this->stream, $criterias[$sort_by], (int)$descending, SE_UID, $search_criteria, 'UTF-8'); 1182 | } 1183 | 1184 | /** 1185 | * [search_body description] 1186 | * 1187 | * @param string $str 1188 | * 1189 | * @return Imap 1190 | */ 1191 | public function search_body($str) 1192 | { 1193 | $this->search_criteria .= ' BODY "' . $str . '"'; 1194 | 1195 | return $this; 1196 | } 1197 | 1198 | /** 1199 | * [search_body description] 1200 | * 1201 | * @param string $str 1202 | * 1203 | * @return Imap 1204 | */ 1205 | public function search_subject($str) 1206 | { 1207 | $this->search_criteria .= ' SUBJECT "' . $str . '"'; 1208 | 1209 | return $this; 1210 | } 1211 | 1212 | /** 1213 | * [search_body description] 1214 | * 1215 | * @param string|integer $str String on valid format (D, d M Y) or a timestamp 1216 | * 1217 | * @return Imap 1218 | */ 1219 | public function search_on_date($str) 1220 | { 1221 | if (is_numeric($str)) 1222 | { 1223 | $str = date('D, d M Y', $str); 1224 | } 1225 | 1226 | $this->search_criteria .= ' ON "' . $str . '"'; 1227 | 1228 | return $this; 1229 | } 1230 | 1231 | /** 1232 | * Return general folder statistics 1233 | * 1234 | * @param string $folder 1235 | * 1236 | * @return array 1237 | */ 1238 | public function get_folder_stats(string $folder = null) 1239 | { 1240 | $current_folder = $this->folder; 1241 | 1242 | if (isset($folder)) 1243 | { 1244 | $this->select_folder($folder); 1245 | } 1246 | 1247 | $stats = imap_mailboxmsginfo($this->stream); 1248 | 1249 | if ($stats) 1250 | { 1251 | $stats = [ 1252 | 'unread' => $stats->Unread, 1253 | 'deleted' => $stats->Deleted, 1254 | 'messages' => $stats->Nmsgs, 1255 | 'size' => $stats->Size, 1256 | 'Date' => $stats->Date, 1257 | 'date' => date('c', strtotime(substr($stats->Date, 0, 30))), 1258 | 'recent' => $stats->Recent, 1259 | ]; 1260 | } 1261 | 1262 | if (isset($folder)) 1263 | { 1264 | $this->select_folder($current_folder); 1265 | } 1266 | 1267 | return $stats; 1268 | } 1269 | 1270 | /** 1271 | * [convert_to_utf8 description] 1272 | * 1273 | * @param string $str 1274 | * 1275 | * @return string 1276 | */ 1277 | protected function convert_to_utf8(string $str) 1278 | { 1279 | if (mb_detect_encoding($str, 'UTF-8, ISO-8859-1, GBK') !== 'UTF-8') 1280 | { 1281 | $str = utf8_encode($str); 1282 | } 1283 | 1284 | $str = iconv('UTF-8', 'UTF-8//IGNORE', $str); 1285 | 1286 | return $str; 1287 | } 1288 | 1289 | /** 1290 | * [array_to_address description] 1291 | * 1292 | * @param array $addresses 1293 | * 1294 | * @return array 1295 | */ 1296 | protected function array_to_address($addresses = []) 1297 | { 1298 | $formated = []; 1299 | 1300 | foreach ($addresses as $address) 1301 | { 1302 | $formated[] = $this->to_address($address); 1303 | } 1304 | 1305 | return $formated; 1306 | } 1307 | 1308 | /** 1309 | * [to_address description] 1310 | * 1311 | * @param object $headerinfos 1312 | * 1313 | * @return array 1314 | */ 1315 | protected function to_address($headerinfos) 1316 | { 1317 | $from = [ 1318 | 'email' => '', 1319 | 'name' => '', 1320 | ]; 1321 | 1322 | if (isset($headerinfos->mailbox) && isset($headerinfos->host)) 1323 | { 1324 | $from['email'] = $headerinfos->mailbox . '@' . $headerinfos->host; 1325 | } 1326 | 1327 | if (! empty($headerinfos->personal)) 1328 | { 1329 | $name = imap_mime_header_decode($headerinfos->personal); 1330 | $name = $name[0]->text; 1331 | $from['name'] = empty($name) ? '' : $this->convert_to_utf8($name); 1332 | } 1333 | 1334 | return $from; 1335 | } 1336 | 1337 | /** 1338 | * [get_body description] 1339 | * 1340 | * @param integer $uid 1341 | * 1342 | * @return array 1343 | */ 1344 | protected function get_body(int $uid) 1345 | { 1346 | return [ 1347 | 'html' => $this->get_part($uid, 'TEXT/HTML'), 1348 | 'plain' => $this->get_part($uid, 'TEXT/PLAIN'), 1349 | ]; 1350 | } 1351 | 1352 | /** 1353 | * [get_part description] 1354 | * 1355 | * @param integer $uid 1356 | * @param string $mimetype 1357 | * @param object|boolean $structure The bodystruct or false to none 1358 | * @param string|boolean $part_number Part number or false to none 1359 | * 1360 | * @return string 1361 | */ 1362 | protected function get_part(int $uid, $mimetype = '', $structure = false, $part_number = '') 1363 | { 1364 | if (! $structure) 1365 | { 1366 | $structure = imap_fetchstructure($this->stream, $uid, FT_UID); 1367 | } 1368 | 1369 | if ($structure) 1370 | { 1371 | if ($mimetype === $this->get_mime_type($structure)) 1372 | { 1373 | if (! $part_number) 1374 | { 1375 | $part_number = '1'; 1376 | } 1377 | 1378 | $text = imap_fetchbody($this->stream, $uid, $part_number, FT_UID | FT_PEEK); 1379 | 1380 | return $this->struc_decoding($text, $structure->encoding); 1381 | } 1382 | 1383 | if ($structure->type === TYPEMULTIPART) // 1 multipart 1384 | { 1385 | foreach ($structure->parts as $index => $subStruct) 1386 | { 1387 | $prefix = ''; 1388 | 1389 | if ($part_number) 1390 | { 1391 | $prefix = $part_number . '.'; 1392 | } 1393 | 1394 | $data = $this->get_part($uid, $mimetype, $subStruct, $prefix . ($index + 1)); 1395 | 1396 | if ($data) 1397 | { 1398 | return $data; 1399 | } 1400 | } 1401 | } 1402 | } 1403 | 1404 | return false; 1405 | } 1406 | 1407 | /** 1408 | * [get_mime_type description] 1409 | * 1410 | * @param object $structure 1411 | * 1412 | * @see http://php.net/manual/pt_BR/function.imap-fetchstructure.php 1413 | * 1414 | * @return string 1415 | */ 1416 | protected function get_mime_type($structure) 1417 | { 1418 | $primary_body_types = [ 1419 | TYPETEXT => 'TEXT', 1420 | TYPEMULTIPART => 'MULTIPART', 1421 | TYPEMESSAGE => 'MESSAGE', 1422 | TYPEAPPLICATION => 'APPLICATION', 1423 | TYPEAUDIO => 'AUDIO', 1424 | TYPEIMAGE => 'IMAGE', 1425 | TYPEVIDEO => 'VIDEO', 1426 | TYPEMODEL => 'MODEL', 1427 | TYPEOTHER => 'OTHER', 1428 | ]; 1429 | 1430 | if ($structure->ifsubtype) 1431 | { 1432 | return strtoupper($primary_body_types[(int)$structure->type] . '/' . $structure->subtype); 1433 | } 1434 | 1435 | return 'TEXT/PLAIN'; 1436 | } 1437 | 1438 | /** 1439 | * [__destruct description] 1440 | */ 1441 | public function __destruct() 1442 | { 1443 | // TODO: Maybe is not necessary auto-close everytime 1444 | // Analyze it 1445 | if (is_resource($this->stream)) 1446 | { 1447 | imap_errors(); 1448 | imap_close($this->stream); 1449 | } 1450 | } 1451 | 1452 | } 1453 | --------------------------------------------------------------------------------