DMail Milestone 1.0
Drupal Mail Client
|
00001 <?php 00002 // $Id: 5996b2c311c737287ab1ba4325f2a79e978eb685 $ 00011 class IMAP { 00012 protected $host = 'localhost'; 00013 protected $port = 143; 00014 protected $service = 'imap'; 00015 protected $encrypt = 'none'; 00016 protected $validate = FALSE; 00017 protected $mbdelim = '.'; 00018 protected $mbuser = array('user' => 'mbuser', 'pass' => 'mbpass'); 00019 protected $mbreadonly = FALSE; 00020 protected $mbflags = NULL; 00021 protected $mbconnstr = NULL; 00022 protected $mbox = NULL; 00023 protected $mbres = NULL; 00024 private $mode = NULL; 00025 private $check = NULL; 00026 00059 function __construct ($user, $pass, $host = NULL, $port = NULL, $service = NULL, $mb_delim = NULL, $encrypt_mode = NULL, $validate_cert = NULL, $readonly = NULL) { 00060 $this->mbuser = array('user' => $user, 'pass' => $pass); 00061 if ($host) $this->host = $host; 00062 if ($port) $this->port = $port; 00063 if ($service) $this->service = $service; 00064 $this->encrypt = $encrypt_mode; 00065 $this->encrypt = ($this->encrypt === 'none' || is_null($this->encrypt)) ? FALSE : $this->encrypt; 00066 if ($validate_cert) $this->validate = ($validate_cert) === 'yes' ? TRUE : FALSE; 00067 if ($mb_delim) $this->mbdelim = $mb_delim; 00068 if ($readonly) $this->mbreadonly = ($readonly) === 'yes' ? TRUE : FALSE; 00069 $this->mbflags = '/service=' . $this->service; 00070 if ($this->mbreadonly) { 00071 $this->mbflags .= '/readonly'; 00072 } 00073 if ($this->encrypt) { 00074 $this->mbflags .= '/' . $this->encrypt; 00075 } 00076 if ($this->validate) { 00077 $this->mbflags .= '/validate-cert'; 00078 } 00079 else { 00080 $this->mbflags .= '/novalidate-cert'; 00081 } 00082 } 00083 00087 function __destruct() { 00088 $this->close(); 00089 } 00090 00100 function __set($param, $value) { 00101 switch ($param) { 00102 case 'mbox': 00103 case 'mailbox': { 00104 $this->mbox = $value; 00105 } break; 00106 } 00107 } 00108 00130 function open($mode = 0, $force = FALSE) { 00131 if (!$this->mbconnstr) { 00132 $this->mbconnstr = '{' . $this->host . ':' . $this->port . $this->mbflags . '}'; 00133 } 00134 $user =& $this->mbuser['user']; 00135 $pass =& $this->mbuser['pass']; 00136 if ($this->mbreadonly) { 00137 $mode |= OP_READONLY; 00138 } 00139 if ($mode & OP_HALFOPEN) { 00140 $mb = NULL; 00141 } 00142 else { 00143 $mb = $this->mbox; 00144 } 00145 if (!$force && $this->mbres) { 00146 imap_reopen($this->mbres, $this->mbconnstr . $mb, $mode); 00147 $error = $this->error(); 00148 if ($error->type == 'CLOSED') { 00149 return $this->open($mode, TRUE); 00150 } 00151 } 00152 else { 00153 if ($force && !$mode) { 00154 $mode == $this->mode; 00155 } 00156 else { 00157 $this->mode = $mode; 00158 } 00159 $this->mbres = imap_open($this->mbconnstr . $mb, $user, $pass, $mode); 00160 } 00161 if ($errors = imap_errors()) { 00162 foreach ($errors as $error) { 00163 drupal_set_message($error, 'error'); 00164 } 00165 } 00166 return $this->mbres; 00167 } 00168 00175 function close() { 00176 $return = NULL; 00177 if (is_resource($this->mbres)) $return = imap_close($this->mbres); 00178 return $return; 00179 } 00180 00195 function check($mbox_name = NULL) { 00196 $this->check_mbox($mbox_name); 00197 return imap_check($this->mbres); 00198 } 00199 00225 function status($mbox_name = NULL, $options = SA_ALL) { 00226 $this->check_mbox($mbox_name); 00227 $mbox = $this->mbconnstr . $this->mbox; 00228 return imap_status($this->mbres, $mbox, $options); 00229 } 00230 00263 function fetch_overview($messages, $mbox_name = NULL) { 00264 $this->check_mbox($mbox_name); 00265 watchdog('imap debug', print_r($messages, true)); 00266 return imap_fetch_overview($this->mbres, $messages, 0); 00267 } 00268 00294 function list_headers($mbox_name) { 00295 $headers = array(); 00296 $this->check_mbox($mbox_name); 00297 $check = imap_check($this->mbres); 00298 $headers = imap_fetch_overview($this->mbres, "1:{$check->Nmsgs}", 0); 00299 return $headers; 00300 } 00301 00302 /* 00303 * Delete the mailbox item. 00304 * 00305 * @param $msgno 00306 * - The message sequence number. 00307 * 00308 * @return 00309 * - The result of the delete, TRUE or FALSE. 00310 */ 00311 function delete_item($msgno) { 00312 return imap_delete($this->mbres, $msgno); 00313 } 00314 00326 function copy_item($msgno, $to_folder) { 00327 return imap_mail_copy($this->mbres, $msgno, $to_folder); 00328 } 00329 00341 function move_item($msgno, $to_folder) { 00342 return imap_mail_move($this->mbres, $msgno, $to_folder); 00343 } 00344 00354 function expunge($mbox_name = NULL) { 00355 $this->check_mbox($mbox_name); 00356 return imap_expunge($this->mbres); 00357 } 00358 00449 function headerinfo($msgno) { 00450 static $except = FALSE; 00451 $fetch = imap_headerinfo($this->mbres, $msgno); 00452 $error = $this->error(); 00453 if ($error) { 00454 switch ($error->type) { 00455 case 'CLOSED': { 00456 if (!$except) { 00457 $this->open(0, TRUE); 00458 $except = TRUE; 00459 return $this->headerinfo($msgno); 00460 } 00461 else { 00462 throw new Exception($error->text); 00463 } 00464 } break; 00465 case 'WARNING': { 00466 //ignore. 00467 } break; 00468 default: { 00469 throw new Exception($error->text); 00470 } break; 00471 } 00472 } 00473 $except = FALSE; 00474 return $fetch; 00475 } 00476 00486 function fetchheader($msgno) { 00487 static $except = FALSE; 00488 $fetch = imap_fetchheader($this->mbres, $msgno); 00489 $error = $this->error(); 00490 if ($error) { 00491 switch ($error->type) { 00492 case 'CLOSED': { 00493 if (!$except) { 00494 $this->open(0, TRUE); 00495 $except = TRUE; 00496 return $this->fetchheader($msgno); 00497 } 00498 else { 00499 throw new Exception($error->text); 00500 } 00501 } break; 00502 case 'WARNING': { 00503 //ignore. 00504 } break; 00505 default: { 00506 throw new Exception($error->text); 00507 } break; 00508 } 00509 } 00510 $except = FALSE; 00511 return $fetch; 00512 } 00513 00547 function fetchstructure($msgno) { 00548 static $except = FALSE; 00549 $fetch = imap_fetchstructure($this->mbres, $msgno); 00550 $error = $this->error(); 00551 if ($error) { 00552 switch ($error->type) { 00553 case 'CLOSED': { 00554 if (!$except) { 00555 $this->open(0, TRUE); 00556 $except = TRUE; 00557 return $this->fetchstructure($msgno); 00558 } 00559 else { 00560 throw new Exception($error->text); 00561 } 00562 } break; 00563 case 'WARNING': { 00564 //ignore. 00565 } break; 00566 default: { 00567 throw new Exception($error->text); 00568 } break; 00569 } 00570 } 00571 $except = FALSE; 00572 return $fetch; 00573 } 00574 00587 function bodystruct($msgno, $partno) { 00588 static $except = FALSE; 00589 $fetch = imap_bodystruct($this->mbres, $msgno, $partno); 00590 $error = $this->error(); 00591 if ($error) { 00592 switch ($error->type) { 00593 case 'CLOSED': { 00594 if (!$except) { 00595 $this->open(0, TRUE); 00596 $except = TRUE; 00597 return $this->bodystruct($msgno, $partno); 00598 } 00599 else { 00600 throw new Exception($error->text); 00601 } 00602 } break; 00603 case 'WARNING': { 00604 //ignore. 00605 } break; 00606 default: { 00607 throw new Exception($error->text); 00608 } break; 00609 } 00610 } 00611 $except = FALSE; 00612 return $fetch; 00613 } 00614 00625 function list_mbox($mbox) { 00626 $mboxes = imap_getmailboxes($this->mbres, $this->mbconnstr, $mbox); 00627 if (count($mboxes) === 0) { 00628 return FALSE; 00629 } 00630 elseif (count($mboxes) > 1) { 00631 throw new Exception('Invalid mailbox specification.'); 00632 } 00633 $mbx =& $mboxes[0]; 00634 $status = imap_status($this->mbres, $this->mbconnstr . $mbox, SA_ALL); 00635 foreach ((array)$status as $key => $stat) { 00636 $mbx->$key = $stat; 00637 } 00638 return $mbx; 00639 } 00640 00647 function list_mboxes() { 00648 $mboxes = imap_getmailboxes($this->mbres, $this->mbconnstr, '*'); 00649 $return = array(); 00650 foreach ($mboxes as $mbox) { 00651 list(,$mbox) = explode('}', $mbox->name); 00652 $return[] = $this->list_mbox($mbox); 00653 } 00654 return $return; 00655 } 00656 00677 function fetch_item($msgno, $mime_part = 'TEXT/PLAIN', $parts = NULL) { 00678 if (is_null($parts)) { 00679 $structure = imap_fetchstructure($this->mbres, $msgno); 00680 $parts = $this->parts($structure, $msgno); 00681 return $this->fetch_item($msgno, $mime_part, $parts); 00682 } 00683 else { 00684 $ret = NULL; 00685 foreach ($parts as $part) { 00686 if ($part->mime_type === $mime_part) { 00687 $ret .= "\r\n\r\n" . $part->text; 00688 } 00689 elseif ($mime_part == 'ATTACHMENT' && 00690 $part->disposition == 'attachment') 00691 { 00692 $att = new stdClass; 00693 $att->subtype = $att->filename = $att->name = NULL; 00694 $att->subtype = $part->subtype; 00695 if ($part->dparameters) { 00696 $att->{$part->dparameters[0]->attribute} = $part->dparameters[0]->value; 00697 } 00698 if ($part->parameters) { 00699 $att->{$part->parameters[0]->attribute} = $part->parameters[0]->value; 00700 } 00701 $att->contents = $part->text; 00702 $ret[] = $att; 00703 } 00704 } 00705 return $ret; 00706 } 00707 } 00708 00724 private function parts($structure, $msgno, $partno = FALSE) { 00725 if ($structure) { 00726 $parts = array(); 00727 if (!$structure->parts) { 00728 if ($partno === FALSE) { 00729 $partno = '1'; 00730 } 00731 $body = imap_fetchbody($this->mbres, $msgno, $partno); 00732 $bodystruct = imap_bodystruct($this->mbres, $msgno, $partno); 00733 $body = $this->part_decode($bodystruct->encoding, $body); 00734 if ($bodystruct->ifparameters) { 00735 foreach ($bodystruct->parameters as $parameter) { 00736 if (strtolower($parameter->attribute) == 'charset') { 00737 $body = iconv($parameter->value, 'utf-8', $body); 00738 break; 00739 } 00740 } 00741 } 00742 $part = new stdClass; 00743 $part->partno = $partno; 00744 $part->mime_type = $this->mime_type($bodystruct); 00745 $part->text = $body; 00746 $part->bytes = $bodystruct->bytes; 00747 $part->subtype = $bodystruct->ifsubtype ? $bodystruct->subtype : NULL; 00748 $part->disposition = $bodystruct->ifdisposition ? $bodystruct->disposition : NULL; 00749 $part->dparameters = $bodystruct->ifdparameters ? $bodystruct->dparameters : NULL; 00750 $part->parameters = $bodystruct->ifparameters ? $bodystruct->parameters : NULL; 00751 $parts[$partno] = $part; 00752 } 00753 else { 00754 while (list($index, $substructure) = each($structure->parts)) { 00755 if ($partno) { 00756 $prefix = $partno . '.'; 00757 } 00758 else { 00759 $prefix = NULL; 00760 } 00761 $data = $this->parts($substructure, $msgno, $prefix . ($index + 1)); 00762 $parts += $data; 00763 } 00764 } 00765 return $parts; 00766 } 00767 return FALSE; 00768 } 00769 00783 private function part_decode($encoding, $text) { 00784 switch ($encoding) { 00785 case 3: { 00786 return imap_base64($text); 00787 } break; 00788 case 4: { 00789 return quoted_printable_decode($text); // This resolved and issue of NULL being returned with imap_qprint 00790 return imap_qprint($text); 00791 } break; 00792 default: { 00793 return $text; 00794 } break; 00795 } 00796 } 00797 00808 private function mime_type($structure) { 00809 $primary_mime_types = array('TEXT', 'MULTIPART', 'MESSAGE', 'APPLICATION', 'AUDIO', 'IMAGE', 'VIDEO', 'OTHER'); 00810 if ($structure->subtype) { 00811 $mime_type = $primary_mime_types[$structure->type] . '/' . $structure->subtype; 00812 } 00813 else { 00814 $mime_type = "TEXT/PLAIN"; 00815 } 00816 return $mime_type; 00817 } 00818 00831 private function error() { 00832 $text = imap_last_error(); 00833 if ($text === FALSE) { 00834 return $text; 00835 } 00836 $parts = explode(' ', strtolower($text)); 00837 switch ($parts[0]) { 00838 case 'warning:': { 00839 $type = 'WARNING'; 00840 } break; 00841 case 'unexpected': { 00842 $type = 'WARNING'; 00843 } break; 00844 case 'unterminated': { 00845 $type = 'WARNING'; 00846 } break; 00847 case '[closed]': { 00848 $type = 'CLOSED'; 00849 } break; 00850 case 'invalid': { 00851 if ($parts[1] === 'mailbox' && $parts[2] === 'list:' && $parts[3] === '<>') { 00852 $type = 'WARNING'; 00853 } 00854 else { 00855 $type = 'UNKNOWN'; 00856 } 00857 } break; 00858 case 'must': { 00859 if ($parts[1] === 'use' && $parts[2] === 'comma') { 00860 $type = 'WARNING'; 00861 } 00862 else { 00863 $type = 'UNKNOWN'; 00864 } 00865 } break; 00866 case 'no': { 00867 if ($parts[1] === 'body' && $parts[2] === 'information') { 00868 $type = 'WARNING'; 00869 } 00870 else { 00871 $type = 'UNKNOWN'; 00872 } 00873 } break; 00874 case 'store': { 00875 if ($parts[3] === 'read-only') { 00876 $type = 'WARNING'; 00877 } 00878 else { 00879 $type = 'UNKNOWN'; 00880 } 00881 } break; 00882 default: { 00883 $type = 'UNKNOWN'; 00884 } break; 00885 } 00886 $error->type = $type; 00887 $error->text = $text; 00888 return $error; 00889 } 00890 00903 private function check_mbox($mbox_name, $open_mode = OP_READONLY) { 00904 if ($mbox_name) { 00905 if ($mbox_name != $this->mbox) { 00906 $this->mbox = $mbox_name; 00907 $this->open($open_mode); 00908 } 00909 } 00910 } 00911 00912 }