From: <wis...@us...> - 2015-06-29 18:11:29
|
Revision: 13093 http://sourceforge.net/p/xoops/svn/13093 Author: wishcraft Date: 2015-06-29 18:11:25 +0000 (Mon, 29 Jun 2015) Log Message: ----------- Intial planning for Email Ticketter with secondary raise to MantisBT for second level support and faults! Added Paths: ----------- XoopsModules/please/ XoopsModules/please/trunk/ XoopsModules/please/trunk/admin/ XoopsModules/please/trunk/blocks/ XoopsModules/please/trunk/class/ XoopsModules/please/trunk/class/IncomingMail.php XoopsModules/please/trunk/class/Mailbox.php XoopsModules/please/trunk/class/imap.php XoopsModules/please/trunk/class/pop3.php XoopsModules/please/trunk/crons/ XoopsModules/please/trunk/image/ XoopsModules/please/trunk/include/ XoopsModules/please/trunk/include/common.php XoopsModules/please/trunk/include/functions.php XoopsModules/please/trunk/index.php XoopsModules/please/trunk/language/ XoopsModules/please/trunk/language/english/ XoopsModules/please/trunk/language/english/admin.php XoopsModules/please/trunk/language/english/blocks.php XoopsModules/please/trunk/language/english/errors.php XoopsModules/please/trunk/language/english/mail_template/ XoopsModules/please/trunk/language/english/main.php XoopsModules/please/trunk/language/english/modinfo.php XoopsModules/please/trunk/mantis.php XoopsModules/please/trunk/preloads/ XoopsModules/please/trunk/preloads/sef.php XoopsModules/please/trunk/sql/ XoopsModules/please/trunk/sql/mysqli.sql XoopsModules/please/trunk/templates/ XoopsModules/please/trunk/templates/admin/ XoopsModules/please/trunk/templates/blocks/ XoopsModules/please/trunk/templates/salts.php.tpl XoopsModules/please/trunk/xoops_version.php Added: XoopsModules/please/trunk/class/IncomingMail.php =================================================================== --- XoopsModules/please/trunk/class/IncomingMail.php (rev 0) +++ XoopsModules/please/trunk/class/IncomingMail.php 2015-06-29 18:11:25 UTC (rev 13093) @@ -0,0 +1,86 @@ +<?php +/** + * Please Email Ticketer of Batch Group & User Emails + * + * You may not change or alter any portion of this comment or credits + * of supporting developers from this source code or any supporting source code + * which is considered copyrighted (c) material of the original comment or credit authors. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright The XOOPS Project http://sourceforge.net/projects/xoops/ + * @license GNU GPL 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) + * @author Simon Roberts (wishcraft) <wis...@us...> + * @subpackage please + * @description Email Ticking for Support/Faults/Management of Batch Group & User managed emails tickets + * @version 1.0.5 + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.5/Modules/please + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.6/Modules/please + * @link https://sourceforge.net/p/xoops/svn/HEAD/tree/XoopsModules/please + * @link http://internetfounder.wordpress.com + */ + +namespace PhpImap; + +/** + * @see https://github.com/barbushin/php-imap + * @author Barbushin Sergey http://linkedin.com/in/barbushin + */ +class IncomingMail { + + public $id; + public $date; + public $subject; + + public $fromName; + public $fromAddress; + + public $to = array(); + public $toString; + public $cc = array(); + public $replyTo = array(); + + public $textPlain; + public $textHtml; + /** @var IncomingMailAttachment[] */ + protected $attachments = array(); + + public function addAttachment(IncomingMailAttachment $attachment) { + $this->attachments[$attachment->id] = $attachment; + } + + /** + * @return IncomingMailAttachment[] + */ + public function getAttachments() { + return $this->attachments; + } + + /** + * Get array of internal HTML links placeholders + * @return array attachmentId => link placeholder + */ + public function getInternalLinksPlaceholders() { + return preg_match_all('/=["\'](ci?d:([\w\.%*@-]+))["\']/i', $this->textHtml, $matches) ? array_combine($matches[2], $matches[1]) : array(); + + } + + public function replaceInternalLinks($baseUri) { + $baseUri = rtrim($baseUri, '\\/') . '/'; + $fetchedHtml = $this->textHtml; + foreach($this->getInternalLinksPlaceholders() as $attachmentId => $placeholder) { + if(isset($this->attachments[$attachmentId])) { + $fetchedHtml = str_replace($placeholder, $baseUri . basename($this->attachments[$attachmentId]->filePath), $fetchedHtml); + } + } + return $fetchedHtml; + } +} + +class IncomingMailAttachment { + + public $id; + public $name; + public $filePath; +} Added: XoopsModules/please/trunk/class/Mailbox.php =================================================================== --- XoopsModules/please/trunk/class/Mailbox.php (rev 0) +++ XoopsModules/please/trunk/class/Mailbox.php 2015-06-29 18:11:25 UTC (rev 13093) @@ -0,0 +1,624 @@ +<?php +/** + * Please Email Ticketer of Batch Group & User Emails + * + * You may not change or alter any portion of this comment or credits + * of supporting developers from this source code or any supporting source code + * which is considered copyrighted (c) material of the original comment or credit authors. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright The XOOPS Project http://sourceforge.net/projects/xoops/ + * @license GNU GPL 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) + * @author Simon Roberts (wishcraft) <wis...@us...> + * @subpackage please + * @description Email Ticking for Support/Faults/Management of Batch Group & User managed emails tickets + * @version 1.0.5 + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.5/Modules/please + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.6/Modules/please + * @link https://sourceforge.net/p/xoops/svn/HEAD/tree/XoopsModules/please + * @link http://internetfounder.wordpress.com + */ + +namespace PhpImap; + +use stdClass; + +/** + * @see https://github.com/barbushin/php-imap + * @author Barbushin Sergey http://linkedin.com/in/barbushin + */ +class Mailbox { + + protected $imapPath; + protected $imapLogin; + protected $imapPassword; + protected $imapOptions = 0; + protected $imapRetriesNum = 0; + protected $imapParams = array(); + protected $serverEncoding; + protected $attachmentsDir; + + public function __construct($imapPath, $login, $password, $attachmentsDir = null, $serverEncoding = 'UTF-8') { + $this->imapPath = $imapPath; + $this->imapLogin = $login; + $this->imapPassword = $password; + $this->serverEncoding = strtoupper($serverEncoding); + if($attachmentsDir) { + if(!is_dir($attachmentsDir)) { + throw new Exception('Directory "' . $attachmentsDir . '" not found'); + } + $this->attachmentsDir = rtrim(realpath($attachmentsDir), '\\/'); + } + } + + /** + * Set custom connection arguments of imap_open method. See http://php.net/imap_open + * @param int $options + * @param int $retriesNum + * @param array $params + */ + public function setConnectionArgs($options = 0, $retriesNum = 0, array $params = null) { + $this->imapOptions = $options; + $this->imapRetriesNum = $retriesNum; + $this->imapParams = $params; + } + + /** + * Get IMAP mailbox connection stream + * @param bool $forceConnection Initialize connection if it's not initialized + * @return null|resource + */ + public function getImapStream($forceConnection = true) { + static $imapStream; + if($forceConnection) { + if($imapStream && (!is_resource($imapStream) || !imap_ping($imapStream))) { + $this->disconnect(); + $imapStream = null; + } + if(!$imapStream) { + $imapStream = $this->initImapStream(); + } + } + return $imapStream; + } + + protected function initImapStream() { + $imapStream = @imap_open($this->imapPath, $this->imapLogin, $this->imapPassword, $this->imapOptions, $this->imapRetriesNum, $this->imapParams); + if(!$imapStream) { + throw new Exception('Connection error: ' . imap_last_error()); + } + return $imapStream; + } + + protected function disconnect() { + $imapStream = $this->getImapStream(false); + if($imapStream && is_resource($imapStream)) { + imap_close($imapStream, CL_EXPUNGE); + } + } + + /** + * Get information about the current mailbox. + * + * Returns the information in an object with following properties: + * Date - current system time formatted according to RFC2822 + * Driver - protocol used to access this mailbox: POP3, IMAP, NNTP + * Mailbox - the mailbox name + * Nmsgs - number of mails in the mailbox + * Recent - number of recent mails in the mailbox + * + * @return stdClass + */ + public function checkMailbox() { + return imap_check($this->getImapStream()); + } + + /** + * Creates a new mailbox specified by mailbox. + * + * @return bool + */ + + public function createMailbox() { + return imap_createmailbox($this->getImapStream(), imap_utf7_encode($this->imapPath)); + } + + /** + * Gets status information about the given mailbox. + * + * This function returns an object containing status information. + * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. + * + * @return stdClass if the box doesn't exist + */ + + public function statusMailbox() { + return imap_status($this->getImapStream(), $this->imapPath, SA_ALL); + } + + + /** + * Gets listing the folders + * + * This function returns an object containing listing the folders. + * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. + * + * @return array listing the folders + */ + + public function getListingFolders() { + $folders = imap_list($this->getImapStream(), $this->imapPath, "*"); + foreach ($folders as $key => $folder) + { + $folder = str_replace($this->imapPath, "", imap_utf7_decode($folder)); + $folders[$key] = $folder; + } + return $folders; + } + + + /** + * This function performs a search on the mailbox currently opened in the given IMAP stream. + * For example, to match all unanswered mails sent by Mom, you'd use: "UNANSWERED FROM mom". + * Searches appear to be case insensitive. This list of criteria is from a reading of the UW + * c-client source code and may be incomplete or inaccurate (see also RFC2060, section 6.4.4). + * + * @param string $criteria String, delimited by spaces, in which the following keywords are allowed. Any multi-word arguments (e.g. FROM "joey smith") must be quoted. Results will match all criteria entries. + * ALL - return all mails matching the rest of the criteria + * ANSWERED - match mails with the \\ANSWERED flag set + * BCC "string" - match mails with "string" in the Bcc: field + * BEFORE "date" - match mails with Date: before "date" + * BODY "string" - match mails with "string" in the body of the mail + * CC "string" - match mails with "string" in the Cc: field + * DELETED - match deleted mails + * FLAGGED - match mails with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set + * FROM "string" - match mails with "string" in the From: field + * KEYWORD "string" - match mails with "string" as a keyword + * NEW - match new mails + * OLD - match old mails + * ON "date" - match mails with Date: matching "date" + * RECENT - match mails with the \\RECENT flag set + * SEEN - match mails that have been read (the \\SEEN flag is set) + * SINCE "date" - match mails with Date: after "date" + * SUBJECT "string" - match mails with "string" in the Subject: + * TEXT "string" - match mails with text "string" + * TO "string" - match mails with "string" in the To: + * UNANSWERED - match mails that have not been answered + * UNDELETED - match mails that are not deleted + * UNFLAGGED - match mails that are not flagged + * UNKEYWORD "string" - match mails that do not have the keyword "string" + * UNSEEN - match mails which have not been read yet + * + * @return array Mails ids + */ + public function searchMailbox($criteria = 'ALL') { + $mailsIds = imap_search($this->getImapStream(), $criteria, SE_UID, $this->serverEncoding); + return $mailsIds ? $mailsIds : array(); + } + + /** + * Save mail body. + * @return bool + */ + public function saveMail($mailId, $filename = 'email.eml') { + return imap_savebody($this->getImapStream(), $filename, $mailId, "", FT_UID); + } + + /** + * Marks mails listed in mailId for deletion. + * @return bool + */ + public function deleteMail($mailId) { + return imap_delete($this->getImapStream(), $mailId, FT_UID); + } + + public function moveMail($mailId, $mailBox) { + return imap_mail_move($this->getImapStream(), $mailId, $mailBox, CP_UID) && $this->expungeDeletedMails(); + } + + /** + * Deletes all the mails marked for deletion by imap_delete(), imap_mail_move(), or imap_setflag_full(). + * @return bool + */ + public function expungeDeletedMails() { + return imap_expunge($this->getImapStream()); + } + + /** + * Add the flag \Seen to a mail. + * @return bool + */ + public function markMailAsRead($mailId) { + return $this->setFlag(array($mailId), '\\Seen'); + } + + /** + * Remove the flag \Seen from a mail. + * @return bool + */ + public function markMailAsUnread($mailId) { + return $this->clearFlag(array($mailId), '\\Seen'); + } + + /** + * Add the flag \Flagged to a mail. + * @return bool + */ + public function markMailAsImportant($mailId) { + return $this->setFlag(array($mailId), '\\Flagged'); + } + + /** + * Add the flag \Seen to a mails. + * @return bool + */ + public function markMailsAsRead(array $mailId) { + return $this->setFlag($mailId, '\\Seen'); + } + + /** + * Remove the flag \Seen from some mails. + * @return bool + */ + public function markMailsAsUnread(array $mailId) { + return $this->clearFlag($mailId, '\\Seen'); + } + + /** + * Add the flag \Flagged to some mails. + * @return bool + */ + public function markMailsAsImportant(array $mailId) { + return $this->setFlag($mailId, '\\Flagged'); + } + + /** + * Causes a store to add the specified flag to the flags set for the mails in the specified sequence. + * + * @param array $mailsIds + * @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060. + * @return bool + */ + public function setFlag(array $mailsIds, $flag) { + return imap_setflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID); + } + + /** + * Cause a store to delete the specified flag to the flags set for the mails in the specified sequence. + * + * @param array $mailsIds + * @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060. + * @return bool + */ + public function clearFlag(array $mailsIds, $flag) { + return imap_clearflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID); + } + + /** + * Fetch mail headers for listed mails ids + * + * Returns an array of objects describing one mail header each. The object will only define a property if it exists. The possible properties are: + * subject - the mails subject + * from - who sent it + * to - recipient + * date - when was it sent + * message_id - Mail-ID + * references - is a reference to this mail id + * in_reply_to - is a reply to this mail id + * size - size in bytes + * uid - UID the mail has in the mailbox + * msgno - mail sequence number in the mailbox + * recent - this mail is flagged as recent + * flagged - this mail is flagged + * answered - this mail is flagged as answered + * deleted - this mail is flagged for deletion + * seen - this mail is flagged as already read + * draft - this mail is flagged as being a draft + * + * @param array $mailsIds + * @return array + */ + public function getMailsInfo(array $mailsIds) { + $mails = imap_fetch_overview($this->getImapStream(), implode(',', $mailsIds), FT_UID); + if(is_array($mails) && count($mails)) + { + foreach($mails as &$mail) + { + if(isset($mail->subject)) { + $mail->subject = $this->decodeMimeStr($mail->subject, $this->serverEncoding); + } + if(isset($mail->from)) { + $mail->from = $this->decodeMimeStr($mail->from, $this->serverEncoding); + } + if(isset($mail->to)) { + $mail->to = $this->decodeMimeStr($mail->to, $this->serverEncoding); + } + } + } + return $mails; + } + + /** + * Get information about the current mailbox. + * + * Returns an object with following properties: + * Date - last change (current datetime) + * Driver - driver + * Mailbox - name of the mailbox + * Nmsgs - number of messages + * Recent - number of recent messages + * Unread - number of unread messages + * Deleted - number of deleted messages + * Size - mailbox size + * + * @return object Object with info | FALSE on failure + */ + + public function getMailboxInfo() { + return imap_mailboxmsginfo($this->getImapStream()); + } + + /** + * Gets mails ids sorted by some criteria + * + * Criteria can be one (and only one) of the following constants: + * SORTDATE - mail Date + * SORTARRIVAL - arrival date (default) + * SORTFROM - mailbox in first From address + * SORTSUBJECT - mail subject + * SORTTO - mailbox in first To address + * SORTCC - mailbox in first cc address + * SORTSIZE - size of mail in octets + * + * @param int $criteria + * @param bool $reverse + * @return array Mails ids + */ + public function sortMails($criteria = SORTARRIVAL, $reverse = true) { + return imap_sort($this->getImapStream(), $criteria, $reverse, SE_UID); + } + + /** + * Get mails count in mail box + * @return int + */ + public function countMails() { + return imap_num_msg($this->getImapStream()); + } + + /** + * Retrieve the quota settings per user + * @return array - FALSE in the case of call failure + */ + protected function getQuota() { + return imap_get_quotaroot($this->getImapStream(), 'INBOX'); + } + + /** + * Return quota limit in KB + * @return int - FALSE in the case of call failure + */ + public function getQuotaLimit() { + $quota = $this->getQuota(); + if(is_array($quota)) { + $quota = $quota['STORAGE']['limit']; + } + return $quota; + } + + /** + * Return quota usage in KB + * @return int - FALSE in the case of call failure + */ + public function getQuotaUsage() { + $quota = $this->getQuota(); + if(is_array($quota)) { + $quota = $quota['STORAGE']['usage']; + } + return $quota; + } + + /** + * Get mail data + * + * @param $mailId + * @return IncomingMail + */ + public function getMail($mailId) { + $head = imap_rfc822_parse_headers(imap_fetchheader($this->getImapStream(), $mailId, FT_UID)); + + $mail = new IncomingMail(); + $mail->id = $mailId; + $mail->date = date('Y-m-d H:i:s', isset($head->date) ? strtotime(preg_replace('/\(.*?\)/', '', $head->date)) : time()); + $mail->subject = isset($head->subject) ? $this->decodeMimeStr($head->subject, $this->serverEncoding) : null; + $mail->fromName = isset($head->from[0]->personal) ? $this->decodeMimeStr($head->from[0]->personal, $this->serverEncoding) : null; + $mail->fromAddress = strtolower($head->from[0]->mailbox . '@' . $head->from[0]->host); + + if(isset($head->to)) { + $toStrings = array(); + foreach($head->to as $to) { + if(!empty($to->mailbox) && !empty($to->host)) { + $toEmail = strtolower($to->mailbox . '@' . $to->host); + $toName = isset($to->personal) ? $this->decodeMimeStr($to->personal, $this->serverEncoding) : null; + $toStrings[] = $toName ? "$toName <$toEmail>" : $toEmail; + $mail->to[$toEmail] = $toName; + } + } + $mail->toString = implode(', ', $toStrings); + } + + if(isset($head->cc)) { + foreach($head->cc as $cc) { + $mail->cc[strtolower($cc->mailbox . '@' . $cc->host)] = isset($cc->personal) ? $this->decodeMimeStr($cc->personal, $this->serverEncoding) : null; + } + } + + if(isset($head->reply_to)) { + foreach($head->reply_to as $replyTo) { + $mail->replyTo[strtolower($replyTo->mailbox . '@' . $replyTo->host)] = isset($replyTo->personal) ? $this->decodeMimeStr($replyTo->personal, $this->serverEncoding) : null; + } + } + + $mailStructure = imap_fetchstructure($this->getImapStream(), $mailId, FT_UID); + + if(empty($mailStructure->parts)) { + $this->initMailPart($mail, $mailStructure, 0); + } + else { + foreach($mailStructure->parts as $partNum => $partStructure) { + $this->initMailPart($mail, $partStructure, $partNum + 1); + } + } + + return $mail; + } + + protected function initMailPart(IncomingMail $mail, $partStructure, $partNum) { + $data = $partNum ? imap_fetchbody($this->getImapStream(), $mail->id, $partNum, FT_UID) : imap_body($this->getImapStream(), $mail->id, FT_UID); + + if($partStructure->encoding == 1) { + $data = imap_utf8($data); + } + elseif($partStructure->encoding == 2) { + $data = imap_binary($data); + } + elseif($partStructure->encoding == 3) { + $data = imap_base64($data); + } + elseif($partStructure->encoding == 4) { + $data = imap_qprint($data); + } + + $params = array(); + if(!empty($partStructure->parameters)) { + foreach($partStructure->parameters as $param) { + $params[strtolower($param->attribute)] = $param->value; + } + } + if(!empty($partStructure->dparameters)) { + foreach($partStructure->dparameters as $param) { + $paramName = strtolower(preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute); + if(isset($params[$paramName])) { + $params[$paramName] .= $param->value; + } + else { + $params[$paramName] = $param->value; + } + } + } + + // attachments + $attachmentId = $partStructure->ifid + ? trim($partStructure->id, " <>") + : (isset($params['filename']) || isset($params['name']) ? mt_rand() . mt_rand() : null); + + if($attachmentId) { + if(empty($params['filename']) && empty($params['name'])) { + $fileName = $attachmentId . '.' . strtolower($partStructure->subtype); + } + else { + $fileName = !empty($params['filename']) ? $params['filename'] : $params['name']; + $fileName = $this->decodeMimeStr($fileName, $this->serverEncoding); + $fileName = $this->decodeRFC2231($fileName, $this->serverEncoding); + } + $attachment = new IncomingMailAttachment(); + $attachment->id = $attachmentId; + $attachment->name = $fileName; + if($this->attachmentsDir) { + $replace = array( + '/\s/' => '_', + '/[^0-9a-zа-яіїє_\.]/iu' => '', + '/_+/' => '_', + '/(^_)|(_$)/' => '', + ); + $fileSysName = preg_replace('~[\\\\/]~', '', $mail->id . '_' . $attachmentId . '_' . preg_replace(array_keys($replace), $replace, $fileName)); + $attachment->filePath = $this->attachmentsDir . DIRECTORY_SEPARATOR . $fileSysName; + file_put_contents($attachment->filePath, $data); + } + $mail->addAttachment($attachment); + } + else { + if(!empty($params['charset'])) { + $data = $this->convertStringEncoding($data, $params['charset'], $this->serverEncoding); + } + if($partStructure->type == 0 && $data) { + if(strtolower($partStructure->subtype) == 'plain') { + $mail->textPlain .= $data; + } + else { + $mail->textHtml .= $data; + } + } + elseif($partStructure->type == 2 && $data) { + $mail->textPlain .= trim($data); + } + } + if(!empty($partStructure->parts)) { + foreach($partStructure->parts as $subPartNum => $subPartStructure) { + if($partStructure->type == 2 && $partStructure->subtype == 'RFC822') { + $this->initMailPart($mail, $subPartStructure, $partNum); + } + else { + $this->initMailPart($mail, $subPartStructure, $partNum . '.' . ($subPartNum + 1)); + } + } + } + } + + protected function decodeMimeStr($string, $charset = 'utf-8') { + $newString = ''; + $elements = imap_mime_header_decode($string); + for($i = 0; $i < count($elements); $i++) { + if($elements[$i]->charset == 'default') { + $elements[$i]->charset = 'iso-8859-1'; + } + $newString .= $this->convertStringEncoding($elements[$i]->text, $elements[$i]->charset, $charset); + } + return $newString; + } + + function isUrlEncoded($string) { + $hasInvalidChars = preg_match( '#[^%a-zA-Z0-9\-_\.\+]#', $string ); + $hasEscapedChars = preg_match( '#%[a-zA-Z0-9]{2}#', $string ); + return !$hasInvalidChars && $hasEscapedChars; + } + + protected function decodeRFC2231($string, $charset = 'utf-8') { + if(preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) { + $encoding = $matches[1]; + $data = $matches[2]; + if($this->isUrlEncoded($data)) { + $string = $this->convertStringEncoding(urldecode($data), $encoding, $charset); + } + } + return $string; + } + + /** + * Converts a string from one encoding to another. + * @param string $string + * @param string $fromEncoding + * @param string $toEncoding + * @return string Converted string if conversion was successful, or the original string if not + */ + protected function convertStringEncoding($string, $fromEncoding, $toEncoding) { + $convertedString = null; + if($string && $fromEncoding != $toEncoding) { + $convertedString = @iconv($fromEncoding, $toEncoding . '//IGNORE', $string); + if(!$convertedString && extension_loaded('mbstring')) { + $convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding); + } + } + return $convertedString ?: $string; + } + + public function __destruct() { + $this->disconnect(); + } +} + +class Exception extends \Exception { + +} Added: XoopsModules/please/trunk/class/imap.php =================================================================== --- XoopsModules/please/trunk/class/imap.php (rev 0) +++ XoopsModules/please/trunk/class/imap.php 2015-06-29 18:11:25 UTC (rev 13093) @@ -0,0 +1,705 @@ +<?php +/** + * Please Email Ticketer of Batch Group & User Emails + * + * You may not change or alter any portion of this comment or credits + * of supporting developers from this source code or any supporting source code + * which is considered copyrighted (c) material of the original comment or credit authors. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright The XOOPS Project http://sourceforge.net/projects/xoops/ + * @license GNU GPL 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) + * @author Simon Roberts (wishcraft) <wis...@us...> + * @subpackage please + * @description Email Ticking for Support/Faults/Management of Batch Group & User managed emails tickets + * @version 1.0.5 + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.5/Modules/please + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.6/Modules/please + * @link https://sourceforge.net/p/xoops/svn/HEAD/tree/XoopsModules/please + * @link http://internetfounder.wordpress.com + */ + +/** + * The Imap PHP class provides a wrapper for commonly used PHP IMAP functions. + * + * This class was originally written by Josh Grochowski, and was reformatted and + * documented by Jeff Geerling. + * + * Usage examples can be found in the included README file, and all methods + * should have adequate documentation to get you started. + * + * Quick Start: + * @code + * include 'path/to/Imap/JJG/Imap.php'; + * use \JJG\Imap as Imap; + * $mailbox = new Imap($host, $user, $pass, $port, $ssl, $folder); + * $mailbox->getMailboxInfo(); + * @endcode + * + * Minimum requirements: PHP 5.3.x, php5-imap + * + * @version 1.0-beta2 + * @author Josh Grochowski (josh[at]kastang[dot]com). + * @author Jeff Geerling (geerlingguy). + */ + +namespace JJG; + +class Imap { + + private $host; + private $user; + private $pass; + private $port; + private $folder; + private $ssl; + + private $baseAddress; + private $address; + private $mailbox; + + /** + * Called when the Imap object is created. + * + * Sample of a complete address: {imap.gmail.com:993/imap/ssl}INBOX + * + * @param $host (string) + * The IMAP hostname. Example: imap.gmail.com + * @param $port (int) + * Example: 933 + * @param $ssl (bool) + * TRUE to use SSL, FALSE for no SSL. + * @param $folder (string) + * IMAP Folder to open. + * @param $user (string) + * Username used for connection. Gmail uses full use...@gm..., but + * many providers simply use username. + * @param $pass (string) + * Account password. + * + * @return (empty) + */ + public function __construct($host, $user, $pass, $port, $ssl = true, $folder = 'INBOX') { + if ((!isset($host)) || (!isset($user)) || (!isset($pass)) || (!isset($port))) { + throw new Exception("Error: All Constructor values require a non NULL input."); + } + + $this->host = $host; + $this->user = $user; + $this->pass = $pass; + $this->port = $port; + $this->folder = $folder; + $this->ssl = $ssl; + + $this->changeLoginInfo($host, $user, $pass, $port, $ssl, $folder); + } + + /** + * Change IMAP folders and reconnect to the server. + * + * @param $folderName + * The name of the folder to change to. + * + * @return (empty) + */ + public function changeFolder($folderName) { + if ($this->ssl) { + $address = '{' . $this->host . ':' . $this->port . '/imap/ssl}' . $folderName; + } else { + $address = '{' . $this->host . ':' . $this->port . '/imap}' . $folderName; + } + + $this->address = $address; + $this->reconnect(); + } + + /** + * Log into an IMAP server. + * + * This method is called on the initialization of the class (see + * __construct()), and whenever you need to log into a different account. + * + * Please see __construct() for parameter info. + * + * @return (empty) + * + * @throws Exception when IMAP can't connect. + */ + public function changeLoginInfo($host, $user, $pass, $port, $ssl, $folder) { + if ($ssl) { + $baseAddress = '{' . $host . ':' . $port . '/imap/ssl}'; + $address = $baseAddress . $folder; + } else { + $baseAddress = '{' . $host . ':' . $port . '/imap}'; + $address = $baseAddress . $folder; + } + + // Set the new address and the base address. + $this->baseAddress = $baseAddress; + $this->address = $address; + + // Open new IMAP connection + if ($mailbox = imap_open($address, $user, $pass)) { + $this->mailbox = $mailbox; + } else { + throw new Exception("Error: " . imap_last_error()); + } + } + + /** + * Returns an associative array with detailed information about a given + * message. + * + * @param $messageId (int) + * Message id. + * + * @return Associative array with keys (strings unless otherwise noted): + * raw_header + * to + * from + * cc + * bcc + * reply_to + * sender + * date_sent + * subject + * deleted (bool) + * answered (bool) + * draft (bool) + * body + * original_encoding + * size (int) + * auto_response (bool) + * + * @throws Exception when message with given id can't be found. + */ + public function getMessage($messageId) { + $this->tickle(); + + // Get message details. + $details = imap_headerinfo($this->mailbox, $messageId); + if ($details) { + // Get the raw headers. + $raw_header = imap_fetchheader($this->mailbox, $messageId); + + // Detect whether the message is an autoresponse. + $autoresponse = $this->detectAutoresponder($raw_header); + + // Get some basic variables. + $deleted = ($details->Deleted == 'D'); + $answered = ($details->Answered == 'A'); + $draft = ($details->Draft == 'X'); + + // Get the message body. + $body = imap_fetchbody($this->mailbox, $messageId, 1.2); + if (!strlen($body) > 0) { + $body = imap_fetchbody($this->mailbox, $messageId, 1); + } + + // Get the message body encoding. + $encoding = $this->getEncodingType($messageId); + + // Decode body into plaintext (8bit, 7bit, and binary are exempt). + if ($encoding == 'BASE64') { + $body = $this->decodeBase64($body); + } + elseif ($encoding == 'QUOTED-PRINTABLE') { + $body = $this->decodeQuotedPrintable($body); + } + elseif ($encoding == '8BIT') { + $body = $this->decode8Bit($body); + } + elseif ($encoding == '7BIT') { + $body = $this->decode7Bit($body); + } + + // Build the message. + $message = array( + 'raw_header' => $raw_header, + 'to' => $details->toaddress, + 'from' => $details->fromaddress, + 'cc' => isset($details->ccaddress) ? $details->ccaddress : '', + 'bcc' => isset($details->bccaddress) ? $details->bccaddress : '', + 'reply_to' => isset($details->reply_toaddress) ? $details->reply_toaddress : '', + 'sender' => $details->senderaddress, + 'date_sent' => $details->date, + 'subject' => $details->subject, + 'deleted' => $deleted, + 'answered' => $answered, + 'draft' => $draft, + 'body' => $body, + 'original_encoding' => $encoding, + 'size' => $details->Size, + 'auto_response' => $autoresponse, + ); + } + else { + throw new Exception("Message could not be found: " . imap_last_error()); + } + + return $message; + } + + /** + * Deletes an email matching the specified $messageId. + * + * @param $messageId (int) + * Message id. + * @param $immediate (bool) + * Set TRUE if message should be deleted immediately. Otherwise, message + * will not be deleted until disconnect() is called. Normally, this is a + * bad idea, as other message ids will change if a message is deleted. + * + * @return (empty) + * + * @throws Exception when message can't be deleted. + */ + public function deleteMessage($messageId, $immediate = FALSE) { + $this->tickle(); + + // Mark message for deletion. + if (!imap_delete($this->mailbox, $messageId)) { + throw new Exception("Message could not be deleted: " . imap_last_error()); + } + + // Immediately delete the message if $immediate is TRUE. + if ($immediate) { + imap_expunge($this->mailbox); + } + } + + /** + * Moves an email into the given mailbox. + * + * @param $messageId (int) + * Message id. + * @param $folder (string) + * The name of the folder (mailbox) into which messages should be moved. + * $folder could either be the folder name or 'INBOX.foldername'. + * + * @return (bool) + * Returns TRUE on success, FALSE on failure. + */ + public function moveMessage($messageId, $folder) { + $messageRange = $messageId . ':' . $messageId; + return imap_mail_move($this->mailbox, $messageRange, '{sslmail.webguyz.net:143/imap}Questionable'); + } + + /** + * Returns an associative array with email subjects and message ids for all + * messages in the active $folder. + * + * @return Associative array with message id as key and subject as value. + */ + public function getMessageIds() { + $this->tickle(); + + // Fetch overview of mailbox. + $number_messages = imap_num_msg($this->mailbox); + if ($number_messages) { + $overviews = imap_fetch_overview($this->mailbox, "1:" . imap_num_msg($this->mailbox), 0); + } + else { + $overviews = array(); + } + $messageArray = array(); + + // Loop through message overviews, build message array. + foreach($overviews as $overview) { + $messageArray[$overview->msgno] = $overview->subject; + } + + return $messageArray; + } + + /** + * Return an associative array containing the number of recent, unread, and + * total messages. + * + * @return Associative array with keys: + * unread + * recent + * total + */ + public function getCurrentMailboxInfo() { + $this->tickle(); + + // Get general mailbox information. + $info = imap_status($this->mailbox, $this->address, SA_ALL); + $mailInfo = array( + 'unread' => $info->unseen, + 'recent' => $info->recent, + 'total' => $info->messages, + ); + return $mailInfo; + } + + /** + * Return an array of objects containing mailbox information. + * + * @return Array of mailbox names. + */ + public function getMailboxInfo() { + $this->tickle(); + + // Get all mailbox information. + $mailboxInfo = imap_getmailboxes($this->mailbox, $this->baseAddress, '*'); + $mailboxes = array(); + foreach ($mailboxInfo as $mailbox) { + // Remove baseAddress from mailbox name. + $mailboxes[] = array( + 'mailbox' => $mailbox->name, + 'name' => str_replace($this->baseAddress, '', $mailbox->name), + ); + } + + return $mailboxes; + } + + /** + * Decodes Base64-encoded text. + * + * @param $text (string) + * Base64 encoded text to convert. + * + * @return (string) + * Decoded text. + */ + public function decodeBase64($text) { + $this->tickle(); + return imap_base64($text); + } + + /** + * Decodes quoted-printable text. + * + * @param $text (string) + * Quoted printable text to convert. + * + * @return (string) + * Decoded text. + */ + public function decodeQuotedPrintable($text) { + return quoted_printable_decode($text); + } + + /** + * Decodes 8-Bit text. + * + * @param $text (string) + * 8-Bit text to convert. + * + * @return (string) + * Decoded text. + */ + public function decode8Bit($text) { + return quoted_printable_decode(imap_8bit($text)); + } + + /** + * Decodes 7-Bit text. + * + * PHP seems to think that most emails are 7BIT-encoded, therefore this + * decoding method assumes that text passed through may actually be base64- + * encoded, quoted-printable encoded, or just plain text. Instead of passing + * the email directly through a particular decoding function, this method + * runs through a bunch of common encoding schemes to try to decode everything + * and simply end up with something *resembling* plain text. + * + * Results are not guaranteed, but it's pretty good at what it does. + * + * @param $text (string) + * 7-Bit text to convert. + * + * @return (string) + * Decoded text. + */ + public function decode7Bit($text) { + // If there are no spaces on the first line, assume that the body is + // actually base64-encoded, and decode it. + $lines = explode("\r\n", $text); + $first_line_words = explode(' ', $lines[0]); + if ($first_line_words[0] == $lines[0]) { + $text = base64_decode($text); + } + + // Manually convert common encoded characters into their UTF-8 equivalents. + $characters = array( + '=20' => ' ', // space. + '=2C' => ',', // comma. + '=E2=80=99' => "'", // single quote. + '=0A' => "\r\n", // line break. + '=0D' => "\r\n", // carriage return. + '=A0' => ' ', // non-breaking space. + '=B9' => '$sup1', // 1 superscript. + '=C2=A0' => ' ', // non-breaking space. + "=\r\n" => '', // joined line. + '=E2=80=A6' => '…', // ellipsis. + '=E2=80=A2' => '•', // bullet. + '=E2=80=93' => '–', // en dash. + '=E2=80=94' => '—', // em dash. + ); + + // Loop through the encoded characters and replace any that are found. + foreach ($characters as $key => $value) { + $text = str_replace($key, $value, $text); + } + + return $text; + } + + /** + * Strips quotes (older messages) from a message body. + * + * This function removes any lines that begin with a quote character (>). + * Note that quotes in reply bodies will also be removed by this function, + * so only use this function if you're okay with this behavior. + * + * @param $message (string) + * The message to be cleaned. + * @param $plain_text_output (bool) + * Set to TRUE to also run the text through strip_tags() (helpful for + * cleaning up HTML emails). + * + * @return (string) + * Same as message passed in, but with all quoted text removed. + * + * @see http://stackoverflow.com/a/12611562/100134 + */ + public function cleanReplyEmail($message, $plain_text_output = FALSE) { + // Strip markup if $plain_text_output is set. + if ($plain_text_output) { + $message = strip_tags($message); + } + + // Remove quoted lines (lines that begin with '>'). + $message = preg_replace("/(^\w.+:\n)?(^>.*(\n|$))+/mi", '', $message); + + // Remove lines beginning with 'On' and ending with 'wrote:' (matches + // Mac OS X Mail, Gmail). + $message = preg_replace("/^(On).*(wrote:).*$/sm", '', $message); + + // Remove lines like '----- Original Message -----' (some other clients). + // Also remove lines like '--- On ... wrote:' (some other clients). + $message = preg_replace("/^---.*$/mi", '', $message); + + // Remove lines like '____________' (some other clients). + $message = preg_replace("/^____________.*$/mi", '', $message); + + // Remove blocks of text with formats like: + // - 'From: Sent: To: Subject:' + // - 'From: To: Sent: Subject:' + // - 'From: Date: To: Reply-to: Subject:' + $message = preg_replace("/From:.*^(To:).*^(Subject:).*/sm", '', $message); + + // Remove any remaining whitespace. + $message = trim($message); + + return $message; + } + + /** + * Takes in a string of email addresses and returns an array of addresses + * as objects. For example, passing in 'John Doe <jo...@sa...>' + * returns the following array: + * + * Array ( + * [0] => stdClass Object ( + * [mailbox] => johndoe + * [host] => sample.com + * [personal] => John Doe + * ) + * ) + * + * You can pass in a string with as many addresses as you'd like, and each + * address will be parsed into a new object in the returned array. + * + * @param $addresses (string) + * String of one or more email addresses to be parsed. + * + * @return (array) + * Array of parsed email addresses, as objects. + * + * @see imap_rfc822_parse_adrlist(). + */ + public function parseAddresses($addresses) { + return imap_rfc822_parse_adrlist($addresses, '#'); + } + + /** + * Create an email address to RFC822 specifications. + * + * @param $username (string) + * Name before the @ sign in an email address (example: 'johndoe'). + * @param $host (string) + * Address after the @ sign in an email address (example: 'sample.com'). + * @param $name (string) + * Name of the entity (example: 'John Doe'). + * + * @return (string) Email Address in the following format: + * 'John Doe <jo...@sa...>' + */ + public function createAddress($username, $host, $name) { + return imap_rfc822_write_address($username, $host, $name); + } + + /** + * Returns structured information for a given message id. + * + * @param $messageId + * Message id for which structure will be returned. + * + * @return (object) + * See imap_fetchstructure() return values for details. + * + * @see imap_fetchstructure(). + */ + public function getStructure($messageId) { + return imap_fetchstructure($this->mailbox, $messageId); + } + + /** + * Returns the primary body type for a given message id. + * + * @param $messageId (int) + * Message id. + * @param $numeric (bool) + * Set to true for a numerical body type. + * + * @return (mixed) + * Integer value of body type if numeric, string if not numeric. + */ + public function getBodyType($messageId, $numeric = false) { + // See imap_fetchstructure() documentation for explanation. + $types = array( + 0 => 'Text', + 1 => 'Multipart', + 2 => 'Message', + 3 => 'Application', + 4 => 'Audio', + 5 => 'Image', + 6 => 'Video', + 7 => 'Other', + ); + + // Get the structure of the message. + $structure = $this->getStructure($messageId); + + // Return a number or a string, depending on the $numeric value. + if ($numeric) { + return $structure->type; + } else { + return $types[$structure->type]; + } + } + + /** + * Returns the encoding type of a given $messageId. + * + * @param $messageId (int) + * Message id. + * @param $numeric (bool) + * Set to true for a numerical encoding type. + * + * @return (mixed) + * Integer value of body type if numeric, string if not numeric. + */ + public function getEncodingType($messageId, $numeric = false) { + // See imap_fetchstructure() documentation for explanation. + $encodings = array( + 0 => '7BIT', + 1 => '8BIT', + 2 => 'BINARY', + 3 => 'BASE64', + 4 => 'QUOTED-PRINTABLE', + 5 => 'OTHER', + ); + + // Get the structure of the message. + $structure = $this->getStructure($messageId); + + // Return a number or a string, depending on the $numeric value. + if ($numeric) { + return $structure->encoding; + } else { + return $encodings[$structure->encoding]; + } + } + + /** + * Closes an active IMAP connection. + * + * @return (empty) + */ + public function disconnect() { + // Close the connection, deleting all messages marked for deletion. + imap_close($this->mailbox, CL_EXPUNGE); + } + + /** + * Reconnect to the IMAP server. + * + * @return (empty) + * + * @throws Exception when IMAP can't reconnect. + */ + private function reconnect() { + $this->mailbox = imap_open($this->address, $this->user, $this->pass); + if (!$this->mailbox) { + throw new Exception("Reconnection Failure: " . imap_last_error()); + } + } + + /** + * Checks to see if the connection is alive. If not, reconnects to server. + * + * @return (empty) + */ + private function tickle() { + if (!imap_ping($this->mailbox)) { + $this->reconnect; + } + } + + /** + * Determines whether the given message is from an auto-responder. + * + * This method checks whether the header contains any auto response headers as + * outlined in RFC 3834, and also checks to see if the subject line contains + * certain strings set by different email providers to indicate an automatic + * response. + * + * @see http://tools.ietf.org/html/rfc3834 + * + * @param $header (string) + * Message header as returned by imap_fetchheader(). + * + * @return (bool) + * TRUE if this message comes from an autoresponder. + */ + private function detectAutoresponder($header) { + $autoresponder_strings = array( + 'X-Autoresponse:', // Other email servers. + 'X-Autorespond:', // LogSat server. + 'Subject: Auto Response', // Yahoo mail. + 'Out of office', // Generic. + 'Out of the office', // Generic. + 'out of the office', // Generic. + 'Auto-reply', // Generic. + 'Autoreply', // Generic. + 'autoreply', // Generic. + ); + + // Check for presence of different autoresponder strings. + foreach ($autoresponder_strings as $string) { + if (strpos($header, $string) !== false) { + return true; + } + } + + return false; + } + +} Property changes on: XoopsModules/please/trunk/class/imap.php ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: XoopsModules/please/trunk/class/pop3.php =================================================================== --- XoopsModules/please/trunk/class/pop3.php (rev 0) +++ XoopsModules/please/trunk/class/pop3.php 2015-06-29 18:11:25 UTC (rev 13093) @@ -0,0 +1,419 @@ +<?php +/** + * Please Email Ticketer of Batch Group & User Emails + * + * You may not change or alter any portion of this comment or credits + * of supporting developers from this source code or any supporting source code + * which is considered copyrighted (c) material of the original comment or credit authors. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright The XOOPS Project http://sourceforge.net/projects/xoops/ + * @license GNU GPL 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) + * @author Simon Roberts (wishcraft) <wis...@us...> + * @subpackage please + * @description Email Ticking for Support/Faults/Management of Batch Group & User managed emails tickets + * @version 1.0.5 + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.5/Modules/please + * @link https://sourceforge.net/projects/chronolabs/files/XOOPS%202.6/Modules/please + * @link https://sourceforge.net/p/xoops/svn/HEAD/tree/XoopsModules/please + * @link http://internetfounder.wordpress.com + */ + +/** + * PHPMailer POP-Before-SMTP Authentication Class. + * PHP Version 5 + * @package PHPMailer + * @link https://github.com/PHPMailer/PHPMailer/ + * @author Marcus Bointon (Synchro/coolbru) <php...@sy...> + * @author Jim Jagielski (jimjag) <ji...@gm...> + * @author Andy Prevost (codeworxtech) <cod...@us...> + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2014 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * PHPMailer POP-Before-SMTP Authentication Class. + * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. + * Does not support APOP. + * @package PHPMailer + * @author Richard Davey (original author) <ri...@co...> + * @author Marcus Bointon (Synchro/coolbru) <php...@sy...> + * @author Jim Jagielski (jimjag) <ji...@gm...> + * @author Andy Prevost (codeworxtech) <cod...@us...> + */ +class POP3 +{ + /** + * The POP3 PHPMailer Version number. + * @type string + * @access public + */ + public $Version = '5.2.10'; + + /** + * Default POP3 port number. + * @type integer + * @access public + */ + public $POP3_PORT = 110; + + /** + * Default timeout in seconds. + * @type integer + * @access public + */ + public $POP3_TIMEOUT = 30; + + /** + * POP3 Carriage Return + Line Feed. + * @type string + * @access public + * @deprecated Use the constant instead + */ + public $CRLF = "\r\n"; + + /** + * Debug display level. + * Options: 0 = no, 1+ = yes + * @type integer + * @access public + */ + public $do_debug = 0; + + /** + * POP3 mail server hostname. + * @type string + * @access public + */ + public $host; + + /** + * POP3 port number. + * @type integer + * @access public + */ + public $port; + + /** + * POP3 Timeout Value in seconds. + * @type integer + * @access public + */ + public $tval; + + /** + * POP3 username + * @type string + * @access public + */ + public $username; + + /** + * POP3 password. + * @type string + * @access public + */ + public $password; + + /** + * Resource handle for the POP3 connection socket. + * @type resource + * @access private + */ + private $pop_conn; + + /** + * Are we connected? + * @type boolean + * @access private + */ + private $connected = false; + + /** + * Error container. + * @type array + * @access private + */ + private $errors = array(); + + /** + * Line break constant + */ + const CRLF = "\r\n"; + + /** + * Simple static wrapper for all-in-one POP before SMTP + * @param $host + * @param integer|boolean $port The port number to connect to + * @param integer|boolean $timeout The timeout value + * @param string $username + * @param string $password + * @param integer $debug_level + * @return boolean + */ + public static function popBeforeSmtp( + $host, + $port = false, + $timeout = false, + $username = '', + $password = '', + $debug_level = 0 + ) { + $pop = new POP3; + return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); + } + + /** + * Authenticate with a POP3 server. + * A connect, login, disconnect sequence + * appropriate for POP-before SMTP authorisation. + * @access public + * @param string $host The hostname to connect to + * @param integer|boolean $port The port number to connect to + * @param integer|boolean $timeout The timeout value + * @param string $username + * @param string $password + * @param integer $debug_level + * @return boolean + */ + public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) + { + $this->host = $host; + // If no port value provided, use default + if (false === $port) { + $this->port = $this->POP3_PORT; + } else { + $this->port = (integer)$port; + } + // If no timeout value provided, use default + if (false === $timeout) { + $this->tval = $this->POP3_TIMEOUT; + } else { + $this->tval = (integer)$timeout; + } + $this->do_debug = $debug_level; + $this->username = $username; + $this->password = $password; + // Reset the error log + $this->errors = array(); + // connect + $result = $this->connect($this->host, $this->port, $this->tval); + if ($result) { + $login_result = $this->login($this->username, $this->password); + if ($login_result) { + $this->disconnect(); + return true; + } + } + // We need to disconnect regardless of whether the login succeeded + $this->disconnect(); + return false; + } + + /** + * Connect to a POP3 server. + * @access public + * @param string $host + * @param integer|boolean $port + * @param integer $tval + * @return boolean + */ + public function connect($host, $port = false, $tval = 30) + { + // Are we already connected? + if ($this->connected) { + return true; + } + + //On Windows this will raise a PHP Warning error if the hostname doesn't exist. + //Rather than suppress it with @fsockopen, capture it cleanly instead + set_error_handler(array($this, 'catchWarning')); + + if (false === $port) { + $port = $this->POP3_PORT; + } + + // connect to the POP3 server + $this->pop_conn = fsockopen( + $host, // POP3 Host + $port, // Port # + $errno, // Error Number + $errstr, // Error Message + $tval + ); // Timeout (seconds) + // Restore the error handler + restore_error_handler(); + + // Did we connect? + if (false === $this->pop_conn) { + // It would appear not... + $this->setError(array( + 'error' => "Failed to connect to server $host on port $port", + 'errno' => $errno, + 'errstr' => $errstr + )); + return false; + } + + // Increase the stream time-out + stream_set_timeout($this->pop_conn, $tval, 0); + + // Get the POP3 server response + $pop3_response = $this->getResponse(); + // Check for the +OK + if ($this->checkResponse($pop3_response)) { + // The connection is established and the POP3 server is talking + $this->connected = true; + return true; + } + return false; + } + + /** + * Log in to the POP3 server. + * Does not support APOP (RFC 2828, 4949). + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + public function login($username = '', $password = '') + { + if (!$this->connected) { + $this->setError('Not connected to POP3 server'); + } + if (empty($username)) { + $username = $this->username; + } + if (empty($password)) { + $password = $this->password; + } + + // Send the Username + $this->sendString("USER $username" . self::CRLF); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + // Send the Password + $this->sendString("PASS $password" . self::CRLF); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + return true; + } + } + return false; + } + + /** + * Disconnect from the POP3 server. + * @access public + */ + public function disconnect() + { + $this->sendString('QUIT'); + //The QUIT command may cause the daemon to exit, which will kill our connection + //So ignore errors here + try { + @fclose($this->pop_conn); + } catch (Exception $e) { + //Do nothing + }; + } + + /** + * Get a response from the POP3 server. + * $size is the maximum number of bytes to retrieve + * @param integer $size + * @return string + * @access private + */ + private function getResponse($size = 128) + { + $response = fgets($this->pop_conn, $size); + if ($this->do_debug >= 1) { + echo "Server -> Client: $response"; + } + return $response; + } + + /** + * Send raw data to the POP3 server. + * @param string $string + * @return integer + * @access private + */ + private function sendString($string) + { + if ($this->pop_conn) { + if ($this->do_debug >= 2) { //Show client messages when debug >= 2 + echo "Client -> Server: $string"; + } + return fwrite($this->pop_conn, $string, strlen($string)); + } + return 0; + } + + /** + * Checks the POP3 server response. + * Looks for for +OK or -ERR. + * @param string $string + * @return boolean + * @access private + */ + private function checkResponse($string) + { + if (substr($string, 0, 3) !== '+OK') { + $this->setError(array( + 'error' => "Server reported an error: $string", + 'errno' => 0, + 'errstr' => '' + )); + return false; + } else { + return true; + } + } + + /** + * Add an error to the internal error store. + * Also display debug output if it's enabled. + * @param $error + */ + private function setError($error) + { + $this->errors[] = $error; + if ($this->do_debug >= 1) { + echo '<pre>'; + foreach ($this->errors as $error) { + print_r($error); + } + echo '</pre>'; + } + } + + /** + * POP3 connection error handler. + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + * @access private + */ + private function catchWarning($errno, $errstr, $errfile, $errline) + { + $this->setError(array( + 'error' => "Connecting to the POP3 server raised a PHP warning: ", + 'errno' => $errno, + 'errstr' => $errstr, + ... [truncated message content] |