├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── autoload.php ├── composer.json ├── phpunit.xml.dist ├── src └── Fetch │ ├── Attachment.php │ ├── MIME.php │ ├── Message.php │ └── Server.php └── tests ├── Fetch └── Test │ ├── AttachmentTest.php │ ├── MIMETest.php │ ├── MessageTest.php │ └── ServerTest.php ├── bootstrap.php └── runTests.sh /.coveralls.yml: -------------------------------------------------------------------------------- 1 | src_dir: src 2 | coverage_clover: build/logs/clover.xml 3 | json_path: build/logs/coveralls-upload.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | /.idea 3 | /.settings 4 | /.buildpath 5 | /.project 6 | /composer.lock 7 | /vendor 8 | /report 9 | /build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - hhvm 6 | - hhvm-nightly 7 | 8 | before_script: 9 | - composer self-update && composer install --dev 10 | - vendor/tedivm/dovecottesting/SetupEnvironment.sh 11 | 12 | script: ./tests/runTests.sh 13 | 14 | after_script: 15 | - php vendor/bin/coveralls -v 16 | 17 | matrix: 18 | fast_finish: true 19 | allow_failures: 20 | - php: hhvm 21 | - php: hhvm-nightly 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions Welcome! 2 | 3 | Pull Requests and Community Contributions are the bread and butter of open source software. Every contribution- from bug 4 | reports to feature requests, typos to full new features- are greatly appreciated. 5 | 6 | 7 | ## Important Guidelines 8 | 9 | * One Item Per Pull Request or Issue. This makes it much easier to review code and merge it back in, and prevents issues 10 | with one request from blocking another. 11 | 12 | * Code Coverage is extremely important, and pull requests are much more likely to be accepted if testing is also improved. 13 | New code should be properly tested, and all tests must pass. 14 | 15 | * Read the LICENSE document and make sure you understand it, because your code is going to be released under it. 16 | 17 | * Be prepared to make revisions. Don't be discouraged if you're asked to make changes, as that is just another step 18 | towards refining the code and getting it merged back in. 19 | 20 | * Remember to add the relevant documentation, particular the docblock comments. 21 | 22 | 23 | ## Code Styling 24 | 25 | This project follows the PSR standards set forth by the [PHP Framework Interop Group](http://www.php-fig.org/). 26 | 27 | * [PSR-0: Class and file naming conventions](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) 28 | * [PSR-1: Basic coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) 29 | * [PSR-2: Coding style guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 30 | 31 | All code most follow these standards to be accepted. The easiest way to accomplish this is to run php-cs-fixer once the 32 | new changes are finished. The php-cs-fixer package is installed as a development dependency of this project. 33 | 34 | composer install --dev 35 | vendor/bin/php-cs-fixer fix ./ --level="all" -vv 36 | 37 | 38 | ## Running the test suite 39 | 40 | First install dependencies using Composer. It's important to include the dev packages: 41 | 42 | composer install --dev 43 | 44 | The "runTests.sh" script runs the full test suite- phpunit, php-cs-fixer, as well as any environmental setup: 45 | 46 | tests/runTests.sh 47 | 48 | To call phpunit directly: 49 | 50 | vendor/bin/phpunit 51 | 52 | To call php-cs-fixer directly: 53 | 54 | vendor/bin/php-cs-fixer fix ./ --level="all" -vv --dry-run 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Robert Hafner 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 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Stash Project nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL Robert Hafner BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fetch [![Build Status](https://travis-ci.org/tedious/Fetch.svg?branch=master)](https://travis-ci.org/tedious/Fetch) 2 | 3 | [![License](http://img.shields.io/packagist/l/tedivm/fetch.svg)](https://github.com/tedious/fetch/blob/master/LICENSE) 4 | [![Latest Stable Version](http://img.shields.io/github/release/tedious/fetch.svg)](https://packagist.org/packages/tedivm/fetch) 5 | [![Coverage Status](http://img.shields.io/coveralls/tedious/Fetch.svg)](https://coveralls.io/r/tedious/Fetch?branch=master) 6 | [![Total Downloads](http://img.shields.io/packagist/dt/tedivm/fetch.svg)](https://packagist.org/packages/tedivm/fetch) 7 | 8 | Fetch is a library for reading email and attachments, primarily using the POP 9 | and IMAP protocols. 10 | 11 | 12 | ## Installing 13 | 14 | > N.b. A note on Ubuntu 14.04 (probably other Debian-based / Apt managed 15 | > systems), the install of php5-imap does not enable the extension for CLI 16 | > (possibly others as well), which can cause composer to report fetch 17 | > requires `ext-imap` 18 | 19 | ```sh 20 | sudo ln -s /etc/php5/mods-available/imap.ini /etc/php5/cli/conf.d/30-imap.ini 21 | ``` 22 | 23 | ### Composer 24 | 25 | Installing Fetch can be done through a variety of methods, although Composer 26 | is recommended. 27 | 28 | Until Fetch reaches a stable API with version 1.0 it is recommended that you 29 | review changes before even Minor updates, although bug fixes will always be 30 | backwards compatible. 31 | 32 | ``` 33 | "require": { 34 | "tedivm/fetch": "0.7.*" 35 | } 36 | ``` 37 | 38 | ### Pear 39 | 40 | Fetch is also available through Pear. 41 | 42 | ``` 43 | $ pear channel-discover pear.tedivm.com 44 | $ pear install tedivm/Fetch 45 | ``` 46 | 47 | ### Github 48 | 49 | Releases of Fetch are available on [Github][:releases:]. 50 | 51 | 52 | ## Sample Usage 53 | 54 | This is just a simple code to show how to access messages by using Fetch. It 55 | uses Fetch own autoload, but it can (and should be, if applicable) replaced 56 | with the one generated by composer. 57 | 58 | ```php 59 | use Fetch\Server; 60 | use Fetch\Message; 61 | 62 | $server = new Server('imap.example.com', 993); 63 | $server->setAuthentication('username', 'password'); 64 | 65 | /** @var Message[] $message */ 66 | $messages = $server->getMessages(); 67 | 68 | foreach ($messages as $message) { 69 | echo "Subject: {$message->getSubject()}", PHP_EOL; 70 | echo "Body: {$message->getMessageBody()}", PHP_EOL; 71 | } 72 | ``` 73 | 74 | ## License 75 | 76 | Fetch is licensed under the BSD License. See the LICENSE file for details. 77 | 78 | [:releases:]: https://github.com/tedious/Fetch/releases 79 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | spl_autoload_register(function ($class) { 13 | $base = '/src/'; 14 | 15 | if (strpos($class, 'Fetch\Test') === 0) { 16 | $base = '/tests/'; 17 | } 18 | 19 | $file = __DIR__.$base.strtr($class, '\\', '/').'.php'; 20 | if (file_exists($file)) { 21 | require $file; 22 | 23 | return true; 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tedivm/fetch", 3 | "description": "A PHP IMAP Library", 4 | "keywords": ["email","imap","pop3"], 5 | "homepage": "http://github.com/tedious/Fetch", 6 | "type": "library", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "Robert Hafner", 11 | "email": "tedivm@tedivm.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0", 16 | "ext-imap": "*" 17 | }, 18 | "require-dev": { 19 | "tedivm/dovecottesting": "1.2.3", 20 | "phpunit/phpunit": "4.2.*", 21 | "fabpot/php-cs-fixer": "0.5.*", 22 | "satooshi/php-coveralls": "dev-master" 23 | 24 | }, 25 | "autoload": { 26 | "psr-0": {"Fetch": "src/"} 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/Fetch 17 | 18 | 19 | 20 | 21 | ./src/Fetch/ 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Fetch/Attachment.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch; 13 | 14 | /** 15 | * This library is a wrapper around the Imap library functions included in php. This class wraps around an attachment 16 | * in a message, allowing developers to easily save or display attachments. 17 | * 18 | * @package Fetch 19 | * @author Robert Hafner 20 | */ 21 | class Attachment 22 | { 23 | 24 | /** 25 | * This is the structure object for the piece of the message body that the attachment is located it. 26 | * 27 | * @var \stdClass 28 | */ 29 | protected $structure; 30 | 31 | /** 32 | * This is the unique identifier for the message this attachment belongs to. 33 | * 34 | * @var int 35 | */ 36 | protected $messageId; 37 | 38 | /** 39 | * This is the ImapResource. 40 | * 41 | * @var resource 42 | */ 43 | protected $imapStream; 44 | 45 | /** 46 | * This is the id pointing to the section of the message body that contains the attachment. 47 | * 48 | * @var int 49 | */ 50 | protected $partId; 51 | 52 | /** 53 | * This is the attachments filename. 54 | * 55 | * @var string 56 | */ 57 | protected $filename; 58 | 59 | /** 60 | * This is the size of the attachment. 61 | * 62 | * @var int 63 | */ 64 | protected $size; 65 | 66 | /** 67 | * This stores the data of the attachment so it doesn't have to be retrieved from the server multiple times. It is 68 | * only populated if the getData() function is called and should not be directly used. 69 | * 70 | * @internal 71 | * @var array 72 | */ 73 | protected $data; 74 | 75 | /** 76 | * This function takes in an ImapMessage, the structure object for the particular piece of the message body that the 77 | * attachment is located at, and the identifier for that body part. As a general rule you should not be creating 78 | * instances of this yourself, but rather should get them from an ImapMessage class. 79 | * 80 | * @param Message $message 81 | * @param \stdClass $structure 82 | * @param string $partIdentifier 83 | */ 84 | public function __construct(Message $message, $structure, $partIdentifier = null) 85 | { 86 | $this->messageId = $message->getUid(); 87 | $this->imapStream = $message->getImapBox()->getImapStream(); 88 | $this->structure = $structure; 89 | 90 | if (isset($partIdentifier)) 91 | $this->partId = $partIdentifier; 92 | 93 | $parameters = Message::getParametersFromStructure($structure); 94 | 95 | if (isset($parameters['filename'])) { 96 | $this->setFileName($parameters['filename']); 97 | } elseif (isset($parameters['name'])) { 98 | $this->setFileName($parameters['name']); 99 | } 100 | 101 | $this->size = $structure->bytes; 102 | 103 | $this->mimeType = Message::typeIdToString($structure->type); 104 | 105 | if (isset($structure->subtype)) 106 | $this->mimeType .= '/' . strtolower($structure->subtype); 107 | 108 | $this->encoding = $structure->encoding; 109 | } 110 | 111 | /** 112 | * This function returns the data of the attachment. Combined with getMimeType() it can be used to directly output 113 | * data to a browser. 114 | * 115 | * @return string 116 | */ 117 | public function getData() 118 | { 119 | if (!isset($this->data)) { 120 | $messageBody = isset($this->partId) ? 121 | imap_fetchbody($this->imapStream, $this->messageId, $this->partId, FT_UID) 122 | : imap_body($this->imapStream, $this->messageId, FT_UID); 123 | 124 | $messageBody = Message::decode($messageBody, $this->encoding); 125 | $this->data = $messageBody; 126 | } 127 | 128 | return $this->data; 129 | } 130 | 131 | /** 132 | * This returns the filename of the attachment, or false if one isn't given. 133 | * 134 | * @return string 135 | */ 136 | public function getFileName() 137 | { 138 | return (isset($this->filename)) ? $this->filename : false; 139 | } 140 | 141 | /** 142 | * This function returns the mimetype of the attachment. 143 | * 144 | * @return string 145 | */ 146 | public function getMimeType() 147 | { 148 | return $this->mimeType; 149 | } 150 | 151 | /** 152 | * This returns the size of the attachment. 153 | * 154 | * @return int 155 | */ 156 | public function getSize() 157 | { 158 | return $this->size; 159 | } 160 | 161 | /** 162 | * This function returns the object that contains the structure of this attachment. 163 | * 164 | * @return \stdClass 165 | */ 166 | public function getStructure() 167 | { 168 | return $this->structure; 169 | } 170 | 171 | /** 172 | * This function saves the attachment to the passed directory, keeping the original name of the file. 173 | * 174 | * @param string $path 175 | * @return bool 176 | */ 177 | public function saveToDirectory($path) 178 | { 179 | $path = rtrim($path, '/') . '/'; 180 | 181 | if (is_dir($path)) 182 | return $this->saveAs($path . $this->getFileName()); 183 | 184 | return false; 185 | } 186 | 187 | /** 188 | * This function saves the attachment to the exact specified location. 189 | * 190 | * @param string $path 191 | * @return bool 192 | */ 193 | public function saveAs($path) 194 | { 195 | $dirname = dirname($path); 196 | if (file_exists($path)) { 197 | if (!is_writable($path)) { 198 | return false; 199 | } 200 | } elseif (!is_dir($dirname) || !is_writable($dirname)) { 201 | return false; 202 | } 203 | 204 | if (($filePointer = fopen($path, 'w')) == false) { 205 | return false; 206 | } 207 | 208 | switch ($this->encoding) { 209 | case 3: //base64 210 | $streamFilter = stream_filter_append($filePointer, 'convert.base64-decode', STREAM_FILTER_WRITE); 211 | break; 212 | 213 | case 4: //quoted-printable 214 | $streamFilter = stream_filter_append($filePointer, 'convert.quoted-printable-decode', STREAM_FILTER_WRITE); 215 | break; 216 | 217 | default: 218 | $streamFilter = null; 219 | } 220 | 221 | // Fix an issue causing server to throw an error 222 | // See: https://github.com/tedious/Fetch/issues/74 for more details 223 | $fetch = imap_fetchbody($this->imapStream, $this->messageId, $this->partId ?: 1, FT_UID); 224 | $result = imap_savebody($this->imapStream, $filePointer, $this->messageId, $this->partId ?: 1, FT_UID); 225 | 226 | if ($streamFilter) { 227 | stream_filter_remove($streamFilter); 228 | } 229 | 230 | fclose($filePointer); 231 | 232 | return $result; 233 | } 234 | 235 | protected function setFileName($text) 236 | { 237 | $this->filename = MIME::decode($text, Message::$charset); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Fetch/MIME.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch; 13 | 14 | /** 15 | * This library is a wrapper around the Imap library functions included in php. 16 | * 17 | * @package Fetch 18 | * @author Robert Hafner 19 | * @author Sergey Linnik 20 | */ 21 | final class MIME 22 | { 23 | /** 24 | * @param string $text 25 | * @param string $targetCharset 26 | * 27 | * @return string 28 | */ 29 | public static function decode($text, $targetCharset = 'utf-8') 30 | { 31 | if (null === $text) { 32 | return null; 33 | } 34 | 35 | $result = ''; 36 | 37 | foreach (imap_mime_header_decode($text) as $word) { 38 | $ch = 'default' === $word->charset ? 'ascii' : $word->charset; 39 | 40 | $result .= iconv($ch, $targetCharset, $word->text); 41 | } 42 | 43 | return $result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Fetch/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch; 13 | 14 | /** 15 | * This library is a wrapper around the Imap library functions included in php. This class represents a single email 16 | * message as retrieved from the Imap. 17 | * 18 | * @package Fetch 19 | * @author Robert Hafner 20 | */ 21 | class Message 22 | { 23 | /** 24 | * This is the connection/mailbox class that the email came from. 25 | * 26 | * @var Server 27 | */ 28 | protected $imapConnection; 29 | 30 | /** 31 | * This is the unique identifier for the message. This corresponds to the imap "uid", which we use instead of the 32 | * sequence number. 33 | * 34 | * @var int 35 | */ 36 | protected $uid; 37 | 38 | /** 39 | * This is a reference to the Imap stream generated by 'imap_open'. 40 | * 41 | * @var resource 42 | */ 43 | protected $imapStream; 44 | 45 | /** 46 | * This as an string which contains raw header information for the message. 47 | * 48 | * @var string 49 | */ 50 | protected $rawHeaders; 51 | 52 | /** 53 | * This as an object which contains header information for the message. 54 | * 55 | * @var \stdClass 56 | */ 57 | protected $headers; 58 | 59 | /** 60 | * This is an object which contains various status messages and other information about the message. 61 | * 62 | * @var \stdClass 63 | */ 64 | protected $messageOverview; 65 | 66 | /** 67 | * This is an object which contains information about the structure of the message body. 68 | * 69 | * @var \stdClass 70 | */ 71 | protected $structure; 72 | 73 | /** 74 | * This is an array with the index being imap flags and the value being a boolean specifying whether that flag is 75 | * set or not. 76 | * 77 | * @var array 78 | */ 79 | protected $status = array(); 80 | 81 | /** 82 | * This is an array of the various imap flags that can be set. 83 | * 84 | * @var string 85 | */ 86 | protected static $flagTypes = array(self::FLAG_RECENT, self::FLAG_FLAGGED, self::FLAG_ANSWERED, self::FLAG_DELETED, self::FLAG_SEEN, self::FLAG_DRAFT); 87 | 88 | /** 89 | * This holds the plantext email message. 90 | * 91 | * @var string 92 | */ 93 | protected $plaintextMessage; 94 | 95 | /** 96 | * This holds the html version of the email. 97 | * 98 | * @var string 99 | */ 100 | protected $htmlMessage; 101 | 102 | /** 103 | * This is the date the email was sent. 104 | * 105 | * @var int 106 | */ 107 | protected $date; 108 | 109 | /** 110 | * This is the subject of the email. 111 | * 112 | * @var string 113 | */ 114 | protected $subject; 115 | 116 | /** 117 | * This is the size of the email. 118 | * 119 | * @var int 120 | */ 121 | protected $size; 122 | 123 | /** 124 | * This is an array containing information about the address the email came from. 125 | * 126 | * @var string 127 | */ 128 | protected $from; 129 | 130 | /** 131 | * This is an array containing information about the address the email was sent from. 132 | * 133 | * @var string 134 | */ 135 | protected $sender; 136 | 137 | /** 138 | * This is an array of arrays that contains information about the addresses the email was sent to. 139 | * 140 | * @var array 141 | */ 142 | protected $to; 143 | 144 | /** 145 | * This is an array of arrays that contains information about the addresses the email was cc'd to. 146 | * 147 | * @var array 148 | */ 149 | protected $cc; 150 | 151 | /** 152 | * This is an array of arrays that contains information about the addresses the email was bcc'd to. 153 | * 154 | * @var array 155 | */ 156 | protected $bcc; 157 | 158 | /** 159 | * This is an array of arrays that contain information about the addresses that should receive replies to the email. 160 | * 161 | * @var array 162 | */ 163 | protected $replyTo; 164 | 165 | /** 166 | * This is an array of ImapAttachments retrieved from the message. 167 | * 168 | * @var Attachment[] 169 | */ 170 | protected $attachments = array(); 171 | 172 | /** 173 | * Contains the mailbox that the message resides in. 174 | * 175 | * @var string 176 | */ 177 | protected $mailbox; 178 | 179 | /** 180 | * This value defines the encoding we want the email message to use. 181 | * 182 | * @var string 183 | */ 184 | public static $charset = 'UTF-8'; 185 | 186 | /** 187 | * This value defines the flag set for encoding if the mb_convert_encoding 188 | * function can't be found, and in this case iconv encoding will be used. 189 | * 190 | * @var string 191 | */ 192 | public static $charsetFlag = '//TRANSLIT'; 193 | 194 | /** 195 | * These constants can be used to easily access available flags 196 | */ 197 | const FLAG_RECENT = 'recent'; 198 | const FLAG_FLAGGED = 'flagged'; 199 | const FLAG_ANSWERED = 'answered'; 200 | const FLAG_DELETED = 'deleted'; 201 | const FLAG_SEEN = 'seen'; 202 | const FLAG_DRAFT = 'draft'; 203 | 204 | /** 205 | * This constructor takes in the uid for the message and the Imap class representing the mailbox the 206 | * message should be opened from. This constructor should generally not be called directly, but rather retrieved 207 | * through the apprioriate Imap functions. 208 | * 209 | * @param int $messageUniqueId 210 | * @param Server $mailbox 211 | */ 212 | public function __construct($messageUniqueId, Server $connection) 213 | { 214 | $this->imapConnection = $connection; 215 | $this->mailbox = $connection->getMailBox(); 216 | $this->uid = $messageUniqueId; 217 | $this->imapStream = $this->imapConnection->getImapStream(); 218 | if($this->loadMessage() !== true) 219 | throw new \RuntimeException('Message with ID ' . $messageUniqueId . ' not found.'); 220 | } 221 | 222 | /** 223 | * This function is called when the message class is loaded. It loads general information about the message from the 224 | * imap server. 225 | * 226 | */ 227 | protected function loadMessage() 228 | { 229 | 230 | /* First load the message overview information */ 231 | 232 | if(!is_object($messageOverview = $this->getOverview())) 233 | 234 | return false; 235 | 236 | $this->subject = MIME::decode($messageOverview->subject, self::$charset); 237 | $this->date = strtotime($messageOverview->date); 238 | $this->size = $messageOverview->size; 239 | 240 | foreach (self::$flagTypes as $flag) 241 | $this->status[$flag] = ($messageOverview->$flag == 1); 242 | 243 | /* Next load in all of the header information */ 244 | 245 | $headers = $this->getHeaders(); 246 | 247 | if (isset($headers->to)) 248 | $this->to = $this->processAddressObject($headers->to); 249 | 250 | if (isset($headers->cc)) 251 | $this->cc = $this->processAddressObject($headers->cc); 252 | 253 | if (isset($headers->bcc)) 254 | $this->bcc = $this->processAddressObject($headers->bcc); 255 | 256 | if (isset($headers->sender)) 257 | $this->sender = $this->processAddressObject($headers->sender); 258 | 259 | $this->from = isset($headers->from) ? $this->processAddressObject($headers->from) : array(''); 260 | $this->replyTo = isset($headers->reply_to) ? $this->processAddressObject($headers->reply_to) : $this->from; 261 | 262 | /* Finally load the structure itself */ 263 | 264 | $structure = $this->getStructure(); 265 | 266 | if (!isset($structure->parts)) { 267 | // not multipart 268 | $this->processStructure($structure); 269 | } else { 270 | // multipart 271 | foreach ($structure->parts as $id => $part) 272 | $this->processStructure($part, $id + 1); 273 | } 274 | 275 | return true; 276 | } 277 | 278 | /** 279 | * This function returns an object containing information about the message. This output is similar to that over the 280 | * imap_fetch_overview function, only instead of an array of message overviews only a single result is returned. The 281 | * results are only retrieved from the server once unless passed true as a parameter. 282 | * 283 | * @param bool $forceReload 284 | * @return \stdClass 285 | */ 286 | public function getOverview($forceReload = false) 287 | { 288 | if ($forceReload || !isset($this->messageOverview)) { 289 | // returns an array, and since we just want one message we can grab the only result 290 | $results = imap_fetch_overview($this->imapStream, $this->uid, FT_UID); 291 | if ( sizeof($results) == 0 ) { 292 | throw new \RuntimeException('Error fetching overview'); 293 | } 294 | $this->messageOverview = array_shift($results); 295 | if ( ! isset($this->messageOverview->date)) { 296 | $this->messageOverview->date = null; 297 | } 298 | } 299 | 300 | return $this->messageOverview; 301 | } 302 | 303 | /** 304 | * This function returns an object containing the raw headers of the message. 305 | * 306 | * @param bool $forceReload 307 | * @return string 308 | */ 309 | public function getRawHeaders($forceReload = false) 310 | { 311 | if ($forceReload || !isset($this->rawHeaders)) { 312 | // raw headers (since imap_headerinfo doesn't use the unique id) 313 | $this->rawHeaders = imap_fetchheader($this->imapStream, $this->uid, FT_UID); 314 | } 315 | 316 | return $this->rawHeaders; 317 | } 318 | 319 | /** 320 | * This function returns an object containing the headers of the message. This is done by taking the raw headers 321 | * and running them through the imap_rfc822_parse_headers function. The results are only retrieved from the server 322 | * once unless passed true as a parameter. 323 | * 324 | * @param bool $forceReload 325 | * @return \stdClass 326 | */ 327 | public function getHeaders($forceReload = false) 328 | { 329 | if ($forceReload || !isset($this->headers)) { 330 | // raw headers (since imap_headerinfo doesn't use the unique id) 331 | $rawHeaders = $this->getRawHeaders(); 332 | 333 | // convert raw header string into a usable object 334 | $headerObject = imap_rfc822_parse_headers($rawHeaders); 335 | 336 | // to keep this object as close as possible to the original header object we add the udate property 337 | if (isset($headerObject->date)) { 338 | $headerObject->udate = strtotime($headerObject->date); 339 | } else { 340 | $headerObject->date = null; 341 | $headerObject->udate = null; 342 | } 343 | 344 | $this->headers = $headerObject; 345 | } 346 | 347 | return $this->headers; 348 | } 349 | 350 | /** 351 | * This function returns an object containing the structure of the message body. This is the same object thats 352 | * returned by imap_fetchstructure. The results are only retrieved from the server once unless passed true as a 353 | * parameter. 354 | * 355 | * @param bool $forceReload 356 | * @return \stdClass 357 | */ 358 | public function getStructure($forceReload = false) 359 | { 360 | if ($forceReload || !isset($this->structure)) { 361 | $this->structure = imap_fetchstructure($this->imapStream, $this->uid, FT_UID); 362 | } 363 | 364 | return $this->structure; 365 | } 366 | 367 | /** 368 | * This function returns the message body of the email. By default it returns the plaintext version. If a plaintext 369 | * version is requested but not present, the html version is stripped of tags and returned. If the opposite occurs, 370 | * the plaintext version is given some html formatting and returned. If neither are present the return value will be 371 | * false. 372 | * 373 | * @param bool $html Pass true to receive an html response. 374 | * @return string|bool Returns false if no body is present. 375 | */ 376 | public function getMessageBody($html = false) 377 | { 378 | if ($html) { 379 | if (!isset($this->htmlMessage) && isset($this->plaintextMessage)) { 380 | $output = nl2br($this->plaintextMessage); 381 | 382 | return $output; 383 | 384 | } elseif (isset($this->htmlMessage)) { 385 | return $this->htmlMessage; 386 | } 387 | } else { 388 | if (!isset($this->plaintextMessage) && isset($this->htmlMessage)) { 389 | $output = preg_replace('/\s*\/i', PHP_EOL, trim($this->htmlMessage) ); 390 | $output = strip_tags($output); 391 | 392 | return $output; 393 | } elseif (isset($this->plaintextMessage)) { 394 | return $this->plaintextMessage; 395 | } 396 | } 397 | 398 | return false; 399 | } 400 | 401 | /** 402 | * This function returns the plain text body of the email or false if not present. 403 | * @return string|bool Returns false if not present 404 | */ 405 | public function getPlainTextBody() 406 | { 407 | return isset($this->plaintextMessage) ? $this->plaintextMessage : false; 408 | } 409 | 410 | /** 411 | * This function returns the HTML body of the email or false if not present. 412 | * @return string|bool Returns false if not present 413 | */ 414 | public function getHtmlBody() 415 | { 416 | return isset($this->htmlMessage) ? $this->htmlMessage : false; 417 | } 418 | 419 | /** 420 | * This function returns either an array of email addresses and names or, optionally, a string that can be used in 421 | * mail headers. 422 | * 423 | * @param string $type Should be 'to', 'cc', 'bcc', 'from', 'sender', or 'reply-to'. 424 | * @param bool $asString 425 | * @return array|string|bool 426 | */ 427 | public function getAddresses($type, $asString = false) 428 | { 429 | $type = ( $type == 'reply-to' ) ? 'replyTo' : $type; 430 | $addressTypes = array('to', 'cc', 'bcc', 'from', 'sender', 'replyTo'); 431 | 432 | if (!in_array($type, $addressTypes) || !isset($this->$type) || count($this->$type) < 1) 433 | return false; 434 | 435 | if (!$asString) { 436 | if ($type == 'from') 437 | return $this->from[0]; 438 | elseif ($type == 'sender') 439 | return $this->sender[0]; 440 | 441 | return $this->$type; 442 | } else { 443 | $outputString = ''; 444 | foreach ($this->$type as $address) { 445 | if (isset($set)) 446 | $outputString .= ', '; 447 | if (!isset($set)) 448 | $set = true; 449 | 450 | $outputString .= isset($address['name']) ? 451 | $address['name'] . ' <' . $address['address'] . '>' 452 | : $address['address']; 453 | } 454 | 455 | return $outputString; 456 | } 457 | } 458 | 459 | /** 460 | * This function returns the date, as a timestamp, of when the email was sent. 461 | * 462 | * @return int 463 | */ 464 | public function getDate() 465 | { 466 | return isset($this->date) ? $this->date : false; 467 | } 468 | 469 | /** 470 | * This returns the subject of the message. 471 | * 472 | * @return string 473 | */ 474 | public function getSubject() 475 | { 476 | return isset($this->subject) ? $this->subject : null; 477 | } 478 | 479 | /** 480 | * This function marks a message for deletion. It is important to note that the message will not be deleted form the 481 | * mailbox until the Imap->expunge it run. 482 | * 483 | * @return bool 484 | */ 485 | public function delete() 486 | { 487 | return imap_delete($this->imapStream, $this->uid, FT_UID); 488 | } 489 | 490 | /** 491 | * This function returns Imap this message came from. 492 | * 493 | * @return Server 494 | */ 495 | public function getImapBox() 496 | { 497 | return $this->imapConnection; 498 | } 499 | 500 | /** 501 | * This function takes in a structure and identifier and processes that part of the message. If that portion of the 502 | * message has its own subparts, those are recursively processed using this function. 503 | * 504 | * @param \stdClass $structure 505 | * @param string $partIdentifier 506 | */ 507 | protected function processStructure($structure, $partIdentifier = null) 508 | { 509 | $parameters = self::getParametersFromStructure($structure); 510 | 511 | if ((isset($parameters['name']) || isset($parameters['filename'])) 512 | || (isset($structure->subtype) && strtolower($structure->subtype) == 'rfc822') 513 | ) { 514 | $attachment = new Attachment($this, $structure, $partIdentifier); 515 | $this->attachments[] = $attachment; 516 | } elseif ($structure->type == 0 || $structure->type == 1) { 517 | $messageBody = isset($partIdentifier) ? 518 | imap_fetchbody($this->imapStream, $this->uid, $partIdentifier, FT_UID | FT_PEEK) 519 | : imap_body($this->imapStream, $this->uid, FT_UID | FT_PEEK); 520 | 521 | $messageBody = self::decode($messageBody, $structure->encoding); 522 | 523 | if (!empty($parameters['charset']) && $parameters['charset'] !== self::$charset) { 524 | $mb_converted = false; 525 | if (function_exists('mb_convert_encoding')) { 526 | if (!in_array($parameters['charset'], mb_list_encodings())) { 527 | if ($structure->encoding === 0) { 528 | $parameters['charset'] = 'US-ASCII'; 529 | } else { 530 | $parameters['charset'] = 'UTF-8'; 531 | } 532 | } 533 | 534 | $messageBody = @mb_convert_encoding($messageBody, self::$charset, $parameters['charset']); 535 | $mb_converted = true; 536 | } 537 | if (!$mb_converted) { 538 | $messageBodyConv = @iconv($parameters['charset'], self::$charset . self::$charsetFlag, $messageBody); 539 | 540 | if ($messageBodyConv !== false) { 541 | $messageBody = $messageBodyConv; 542 | } 543 | } 544 | } 545 | 546 | if (strtolower($structure->subtype) === 'plain' || ($structure->type == 1 && strtolower($structure->subtype) !== 'alternative')) { 547 | if (isset($this->plaintextMessage)) { 548 | $this->plaintextMessage .= PHP_EOL . PHP_EOL; 549 | } else { 550 | $this->plaintextMessage = ''; 551 | } 552 | 553 | $this->plaintextMessage .= trim($messageBody); 554 | } elseif (strtolower($structure->subtype) === 'html') { 555 | if (isset($this->htmlMessage)) { 556 | $this->htmlMessage .= '

'; 557 | } else { 558 | $this->htmlMessage = ''; 559 | } 560 | 561 | $this->htmlMessage .= $messageBody; 562 | } 563 | } 564 | 565 | if (isset($structure->parts)) { // multipart: iterate through each part 566 | 567 | foreach ($structure->parts as $partIndex => $part) { 568 | $partId = $partIndex + 1; 569 | 570 | if (isset($partIdentifier)) 571 | $partId = $partIdentifier . '.' . $partId; 572 | 573 | $this->processStructure($part, $partId); 574 | } 575 | } 576 | } 577 | 578 | /** 579 | * This function takes in the message data and encoding type and returns the decoded data. 580 | * 581 | * @param string $data 582 | * @param int|string $encoding 583 | * @return string 584 | */ 585 | public static function decode($data, $encoding) 586 | { 587 | if (!is_numeric($encoding)) { 588 | $encoding = strtolower($encoding); 589 | } 590 | 591 | switch (true) { 592 | case $encoding === 'quoted-printable': 593 | case $encoding === 4: 594 | return quoted_printable_decode($data); 595 | 596 | case $encoding === 'base64': 597 | case $encoding === 3: 598 | return base64_decode($data); 599 | 600 | default: 601 | return $data; 602 | } 603 | } 604 | 605 | /** 606 | * This function returns the body type that an imap integer maps to. 607 | * 608 | * @param int $id 609 | * @return string 610 | */ 611 | public static function typeIdToString($id) 612 | { 613 | switch ($id) { 614 | case 0: 615 | return 'text'; 616 | 617 | case 1: 618 | return 'multipart'; 619 | 620 | case 2: 621 | return 'message'; 622 | 623 | case 3: 624 | return 'application'; 625 | 626 | case 4: 627 | return 'audio'; 628 | 629 | case 5: 630 | return 'image'; 631 | 632 | case 6: 633 | return 'video'; 634 | 635 | default: 636 | case 7: 637 | return 'other'; 638 | } 639 | } 640 | 641 | /** 642 | * Takes in a section structure and returns its parameters as an associative array. 643 | * 644 | * @param \stdClass $structure 645 | * @return array 646 | */ 647 | public static function getParametersFromStructure($structure) 648 | { 649 | $parameters = array(); 650 | if (isset($structure->parameters)) 651 | foreach ($structure->parameters as $parameter) 652 | $parameters[strtolower($parameter->attribute)] = $parameter->value; 653 | 654 | if (isset($structure->dparameters)) 655 | foreach ($structure->dparameters as $parameter) 656 | $parameters[strtolower($parameter->attribute)] = $parameter->value; 657 | 658 | return $parameters; 659 | } 660 | 661 | /** 662 | * This function takes in an array of the address objects generated by the message headers and turns them into an 663 | * associative array. 664 | * 665 | * @param array $addresses 666 | * @return array 667 | */ 668 | protected function processAddressObject($addresses) 669 | { 670 | $outputAddresses = array(); 671 | if (is_array($addresses)) 672 | foreach ($addresses as $address) { 673 | if (property_exists($address, 'mailbox') && $address->mailbox != 'undisclosed-recipients') { 674 | $currentAddress = array(); 675 | $currentAddress['address'] = $address->mailbox . '@' . $address->host; 676 | if (isset($address->personal)) { 677 | $currentAddress['name'] = MIME::decode($address->personal, self::$charset); 678 | } 679 | $outputAddresses[] = $currentAddress; 680 | } 681 | } 682 | 683 | return $outputAddresses; 684 | } 685 | 686 | /** 687 | * This function returns the unique id that identifies the message on the server. 688 | * 689 | * @return int 690 | */ 691 | public function getUid() 692 | { 693 | return $this->uid; 694 | } 695 | 696 | /** 697 | * This function returns the attachments a message contains. If a filename is passed then just that ImapAttachment 698 | * is returned, unless 699 | * 700 | * @param null|string $filename 701 | * @return array|bool|Attachment[] 702 | */ 703 | public function getAttachments($filename = null) 704 | { 705 | if (!isset($this->attachments) || count($this->attachments) < 1) 706 | return false; 707 | 708 | if (!isset($filename)) 709 | return $this->attachments; 710 | 711 | $results = array(); 712 | foreach ($this->attachments as $attachment) { 713 | if ($attachment->getFileName() == $filename) 714 | $results[] = $attachment; 715 | } 716 | 717 | switch (count($results)) { 718 | case 0: 719 | return false; 720 | 721 | case 1: 722 | return array_shift($results); 723 | 724 | default: 725 | return $results; 726 | break; 727 | } 728 | } 729 | 730 | /** 731 | * This function checks to see if an imap flag is set on the email message. 732 | * 733 | * @param string $flag Recent, Flagged, Answered, Deleted, Seen, Draft 734 | * @return bool 735 | */ 736 | public function checkFlag($flag = self::FLAG_FLAGGED) 737 | { 738 | return (isset($this->status[$flag]) && $this->status[$flag] === true); 739 | } 740 | 741 | /** 742 | * This function is used to enable or disable one or more flags on the imap message. 743 | * 744 | * @param string|array $flag Flagged, Answered, Deleted, Seen, Draft 745 | * @param bool $enable 746 | * @throws \InvalidArgumentException 747 | * @return bool 748 | */ 749 | public function setFlag($flag, $enable = true) 750 | { 751 | $flags = (is_array($flag)) ? $flag : array($flag); 752 | 753 | foreach ($flags as $i => $flag) { 754 | $flag = ltrim(strtolower($flag), '\\'); 755 | if (!in_array($flag, self::$flagTypes) || $flag == self::FLAG_RECENT) 756 | throw new \InvalidArgumentException('Unable to set invalid flag "' . $flag . '"'); 757 | 758 | if ($enable) { 759 | $this->status[$flag] = true; 760 | } else { 761 | unset($this->status[$flag]); 762 | } 763 | 764 | $flags[$i] = $flag; 765 | } 766 | 767 | $imapifiedFlag = '\\'.implode(' \\', array_map('ucfirst', $flags)); 768 | 769 | if ($enable === true) { 770 | return imap_setflag_full($this->imapStream, $this->uid, $imapifiedFlag, ST_UID); 771 | } else { 772 | return imap_clearflag_full($this->imapStream, $this->uid, $imapifiedFlag, ST_UID); 773 | } 774 | } 775 | 776 | /** 777 | * This function is used to move a mail to the given mailbox. 778 | * 779 | * @param $mailbox 780 | * 781 | * @return bool 782 | */ 783 | public function moveToMailBox($mailbox) 784 | { 785 | $currentBox = $this->imapConnection->getMailBox(); 786 | $this->imapConnection->setMailBox($this->mailbox); 787 | 788 | $returnValue = imap_mail_copy($this->imapStream, $this->uid, $mailbox, CP_UID | CP_MOVE); 789 | imap_expunge($this->imapStream); 790 | 791 | $this->mailbox = $mailbox; 792 | 793 | $this->imapConnection->setMailBox($currentBox); 794 | 795 | return $returnValue; 796 | } 797 | } 798 | -------------------------------------------------------------------------------- /src/Fetch/Server.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch; 13 | 14 | /** 15 | * This library is a wrapper around the Imap library functions included in php. This class in particular manages a 16 | * connection to the server (imap, pop, etc) and allows for the easy retrieval of stored messages. 17 | * 18 | * @package Fetch 19 | * @author Robert Hafner 20 | */ 21 | class Server 22 | { 23 | /** 24 | * When SSL isn't compiled into PHP we need to make some adjustments to prevent soul crushing annoyances. 25 | * 26 | * @var bool 27 | */ 28 | public static $sslEnable = true; 29 | 30 | /** 31 | * These are the flags that depend on ssl support being compiled into imap. 32 | * 33 | * @var array 34 | */ 35 | public static $sslFlags = array('ssl', 'validate-cert', 'novalidate-cert', 'tls', 'notls'); 36 | 37 | /** 38 | * This is used to prevent the class from putting up conflicting tags. Both directions- key to value, value to key- 39 | * are checked, so if "novalidate-cert" is passed then "validate-cert" is removed, and vice-versa. 40 | * 41 | * @var array 42 | */ 43 | public static $exclusiveFlags = array('validate-cert' => 'novalidate-cert', 'tls' => 'notls'); 44 | 45 | /** 46 | * This is the domain or server path the class is connecting to. 47 | * 48 | * @var string 49 | */ 50 | protected $serverPath; 51 | 52 | /** 53 | * This is the name of the current mailbox the connection is using. 54 | * 55 | * @var string 56 | */ 57 | protected $mailbox = ''; 58 | 59 | /** 60 | * This is the username used to connect to the server. 61 | * 62 | * @var string 63 | */ 64 | protected $username; 65 | 66 | /** 67 | * This is the password used to connect to the server. 68 | * 69 | * @var string 70 | */ 71 | protected $password; 72 | 73 | /** 74 | * This is an array of flags that modify how the class connects to the server. Examples include "ssl" to enforce a 75 | * secure connection or "novalidate-cert" to allow for self-signed certificates. 76 | * 77 | * @link http://us.php.net/manual/en/function.imap-open.php 78 | * @var array 79 | */ 80 | protected $flags = array(); 81 | 82 | /** 83 | * This is the port used to connect to the server 84 | * 85 | * @var int 86 | */ 87 | protected $port; 88 | 89 | /** 90 | * This is the set of options, represented by a bitmask, to be passed to the server during connection. 91 | * 92 | * @var int 93 | */ 94 | protected $options = 0; 95 | 96 | /** 97 | * This is the set of connection parameters 98 | * 99 | * @var array 100 | */ 101 | protected $params = array(); 102 | 103 | /** 104 | * This is the resource connection to the server. It is required by a number of imap based functions to specify how 105 | * to connect. 106 | * 107 | * @var resource 108 | */ 109 | protected $imapStream; 110 | 111 | /** 112 | * This is the name of the service currently being used. Imap is the default, although pop3 and nntp are also 113 | * options 114 | * 115 | * @var string 116 | */ 117 | protected $service = 'imap'; 118 | 119 | /** 120 | * This constructor takes the location and service thats trying to be connected to as its arguments. 121 | * 122 | * @param string $serverPath 123 | * @param null|int $port 124 | * @param null|string $service 125 | */ 126 | public function __construct($serverPath, $port = 143, $service = 'imap') 127 | { 128 | $this->serverPath = $serverPath; 129 | 130 | $this->port = $port; 131 | 132 | switch ($port) { 133 | case 143: 134 | $this->setFlag('novalidate-cert'); 135 | break; 136 | 137 | case 993: 138 | $this->setFlag('ssl'); 139 | break; 140 | } 141 | 142 | $this->service = $service; 143 | } 144 | 145 | /** 146 | * This function sets the username and password used to connect to the server. 147 | * 148 | * @param string $username 149 | * @param string $password 150 | * @param bool $tryFasterAuth tries to auth faster by disabling GSSAPI & NTLM auth methods (set to false if you use either of these auth methods) 151 | */ 152 | public function setAuthentication($username, $password, $tryFasterAuth=true) 153 | { 154 | $this->username = $username; 155 | $this->password = $password; 156 | if ($tryFasterAuth) { 157 | $this->setParam('DISABLE_AUTHENTICATOR', array('GSSAPI','NTLM')); 158 | } 159 | } 160 | 161 | /** 162 | * This function sets the mailbox to connect to. 163 | * 164 | * @param string $mailbox 165 | * @return bool 166 | */ 167 | public function setMailBox($mailbox = '') 168 | { 169 | if (!$this->hasMailBox($mailbox)) { 170 | return false; 171 | } 172 | 173 | $this->mailbox = $mailbox; 174 | if (isset($this->imapStream)) { 175 | $this->setImapStream(); 176 | } 177 | 178 | return true; 179 | } 180 | 181 | public function getMailBox() 182 | { 183 | return $this->mailbox; 184 | } 185 | 186 | /** 187 | * This function sets or removes flag specifying connection behavior. In many cases the flag is just a one word 188 | * deal, so the value attribute is not required. However, if the value parameter is passed false it will clear that 189 | * flag. 190 | * 191 | * @param string $flag 192 | * @param null|string|bool $value 193 | */ 194 | public function setFlag($flag, $value = null) 195 | { 196 | if (!self::$sslEnable && in_array($flag, self::$sslFlags)) 197 | return; 198 | 199 | if (isset(self::$exclusiveFlags[$flag])) { 200 | $kill = self::$exclusiveFlags[$flag]; 201 | } elseif ($index = array_search($flag, self::$exclusiveFlags)) { 202 | $kill = $index; 203 | } 204 | 205 | if (isset($kill) && false !== $index = array_search($kill, $this->flags)) 206 | unset($this->flags[$index]); 207 | 208 | $index = array_search($flag, $this->flags); 209 | if (isset($value) && $value !== true) { 210 | if ($value == false && $index !== false) { 211 | unset($this->flags[$index]); 212 | } elseif ($value != false) { 213 | $match = preg_grep('/' . $flag . '/', $this->flags); 214 | if (reset($match)) { 215 | $this->flags[key($match)] = $flag . '=' . $value; 216 | } else { 217 | $this->flags[] = $flag . '=' . $value; 218 | } 219 | } 220 | } elseif ($index === false) { 221 | $this->flags[] = $flag; 222 | } 223 | } 224 | 225 | /** 226 | * This funtion is used to set various options for connecting to the server. 227 | * 228 | * @param int $bitmask 229 | * @throws \Exception 230 | */ 231 | public function setOptions($bitmask = 0) 232 | { 233 | if (!is_numeric($bitmask)) 234 | throw new \RuntimeException('Function requires numeric argument.'); 235 | 236 | $this->options = $bitmask; 237 | } 238 | 239 | /** 240 | * This function is used to set connection parameters 241 | * 242 | * @param string $key 243 | * @param string $value 244 | */ 245 | public function setParam($key, $value) 246 | { 247 | $this->params[$key] = $value; 248 | } 249 | 250 | /** 251 | * This function gets the current saved imap resource and returns it. 252 | * 253 | * @return resource 254 | */ 255 | public function getImapStream() 256 | { 257 | if (empty($this->imapStream)) 258 | $this->setImapStream(); 259 | 260 | return $this->imapStream; 261 | } 262 | 263 | /** 264 | * This function takes in all of the connection date (server, port, service, flags, mailbox) and creates the string 265 | * thats passed to the imap_open function. 266 | * 267 | * @return string 268 | */ 269 | public function getServerString() 270 | { 271 | $mailboxPath = $this->getServerSpecification(); 272 | 273 | if (isset($this->mailbox)) 274 | $mailboxPath .= $this->mailbox; 275 | 276 | return $mailboxPath; 277 | } 278 | 279 | /** 280 | * Returns the server specification, without adding any mailbox. 281 | * 282 | * @return string 283 | */ 284 | protected function getServerSpecification() 285 | { 286 | $mailboxPath = '{' . $this->serverPath; 287 | 288 | if (isset($this->port)) 289 | $mailboxPath .= ':' . $this->port; 290 | 291 | if ($this->service != 'imap') 292 | $mailboxPath .= '/' . $this->service; 293 | 294 | foreach ($this->flags as $flag) { 295 | $mailboxPath .= '/' . $flag; 296 | } 297 | 298 | $mailboxPath .= '}'; 299 | 300 | return $mailboxPath; 301 | } 302 | 303 | /** 304 | * This function creates or reopens an imapStream when called. 305 | * 306 | */ 307 | protected function setImapStream() 308 | { 309 | if (!empty($this->imapStream)) { 310 | if (!imap_reopen($this->imapStream, $this->getServerString(), $this->options, 1)) 311 | throw new \RuntimeException(imap_last_error()); 312 | } else { 313 | $imapStream = @imap_open($this->getServerString(), $this->username, $this->password, $this->options, 1, $this->params); 314 | 315 | if ($imapStream === false) 316 | throw new \RuntimeException(imap_last_error()); 317 | 318 | $this->imapStream = $imapStream; 319 | } 320 | } 321 | 322 | /** 323 | * This returns the number of messages that the current mailbox contains. 324 | * 325 | * @param string $mailbox 326 | * @return int 327 | */ 328 | public function numMessages($mailbox='') 329 | { 330 | $cnt = 0; 331 | if ($mailbox==='') { 332 | $cnt = imap_num_msg($this->getImapStream()); 333 | } elseif ($this->hasMailbox($mailbox) && $mailbox !== '') { 334 | $oldMailbox = $this->getMailBox(); 335 | $this->setMailbox($mailbox); 336 | $cnt = $this->numMessages(); 337 | $this->setMailbox($oldMailbox); 338 | } 339 | 340 | return ((int) $cnt); 341 | } 342 | 343 | /** 344 | * This function returns an array of ImapMessage object for emails that fit the criteria passed. The criteria string 345 | * should be formatted according to the imap search standard, which can be found on the php "imap_search" page or in 346 | * section 6.4.4 of RFC 2060 347 | * 348 | * @link http://us.php.net/imap_search 349 | * @link http://www.faqs.org/rfcs/rfc2060 350 | * @param string $criteria 351 | * @param null|int $limit 352 | * @return array An array of ImapMessage objects 353 | */ 354 | public function search($criteria = 'ALL', $limit = null) 355 | { 356 | if ($results = imap_search($this->getImapStream(), $criteria, SE_UID)) { 357 | if (isset($limit) && count($results) > $limit) 358 | $results = array_slice($results, 0, $limit); 359 | 360 | $messages = array(); 361 | 362 | foreach ($results as $messageId) 363 | $messages[] = new Message($messageId, $this); 364 | 365 | return $messages; 366 | } else { 367 | return array(); 368 | } 369 | } 370 | 371 | /** 372 | * This function returns the recently received emails as an array of ImapMessage objects. 373 | * 374 | * @param null|int $limit 375 | * @return array An array of ImapMessage objects for emails that were recently received by the server. 376 | */ 377 | public function getRecentMessages($limit = null) 378 | { 379 | return $this->search('Recent', $limit); 380 | } 381 | 382 | /** 383 | * Returns the emails in the current mailbox as an array of ImapMessage objects. 384 | * 385 | * @param null|int $limit 386 | * @return Message[] 387 | */ 388 | public function getMessages($limit = null) 389 | { 390 | $numMessages = $this->numMessages(); 391 | 392 | if (isset($limit) && is_numeric($limit) && $limit < $numMessages) 393 | $numMessages = $limit; 394 | 395 | if ($numMessages < 1) 396 | return array(); 397 | 398 | $stream = $this->getImapStream(); 399 | $messages = array(); 400 | for ($i = 1; $i <= $numMessages; $i++) { 401 | $uid = imap_uid($stream, $i); 402 | $messages[] = new Message($uid, $this); 403 | } 404 | 405 | return $messages; 406 | } 407 | 408 | /** 409 | * Returns the emails in the current mailbox as an array of ImapMessage objects 410 | * ordered by some ordering 411 | * 412 | * @see http://php.net/manual/en/function.imap-sort.php 413 | * @param int $orderBy 414 | * @param bool $reverse 415 | * @param int $limit 416 | * @return Message[] 417 | */ 418 | public function getOrderedMessages($orderBy, $reverse, $limit) 419 | { 420 | $msgIds = imap_sort($this->getImapStream(), $orderBy, $reverse ? 1 : 0, SE_UID); 421 | 422 | return array_map(array($this, 'getMessageByUid'), array_slice($msgIds, 0, $limit)); 423 | } 424 | 425 | /** 426 | * Returns the requested email or false if it is not found. 427 | * 428 | * @param int $uid 429 | * @return Message|bool 430 | */ 431 | public function getMessageByUid($uid) 432 | { 433 | try { 434 | $message = new \Fetch\Message($uid, $this); 435 | 436 | return $message; 437 | } catch (\Exception $e) { 438 | return false; 439 | } 440 | } 441 | 442 | /** 443 | * This function removes all of the messages flagged for deletion from the mailbox. 444 | * 445 | * @return bool 446 | */ 447 | public function expunge() 448 | { 449 | return imap_expunge($this->getImapStream()); 450 | } 451 | 452 | /** 453 | * Checks if the given mailbox exists. 454 | * 455 | * @param $mailbox 456 | * 457 | * @return bool 458 | */ 459 | public function hasMailBox($mailbox) 460 | { 461 | return (boolean) $this->getMailBoxDetails($mailbox); 462 | } 463 | 464 | /** 465 | * Return information about the mailbox or mailboxes 466 | * 467 | * @param $mailbox 468 | * 469 | * @return array 470 | */ 471 | public function getMailBoxDetails($mailbox) 472 | { 473 | return imap_getmailboxes( 474 | $this->getImapStream(), 475 | $this->getServerString(), 476 | $this->getServerSpecification() . $mailbox 477 | ); 478 | } 479 | 480 | /** 481 | * Creates the given mailbox. 482 | * 483 | * @param $mailbox 484 | * 485 | * @return bool 486 | */ 487 | public function createMailBox($mailbox) 488 | { 489 | return imap_createmailbox($this->getImapStream(), $this->getServerSpecification() . $mailbox); 490 | } 491 | 492 | /** 493 | * List available mailboxes 494 | * 495 | * @param string $pattern 496 | * 497 | * @return array 498 | */ 499 | public function listMailBoxes($pattern = '*') 500 | { 501 | return imap_list($this->getImapStream(), $this->getServerSpecification(), $pattern); 502 | } 503 | 504 | /** 505 | * Deletes the given mailbox. 506 | * 507 | * @param $mailbox 508 | * 509 | * @return bool 510 | */ 511 | public function deleteMailBox($mailbox) 512 | { 513 | return imap_deletemailbox($this->getImapStream(), $this->getServerSpecification() . $mailbox); 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /tests/Fetch/Test/AttachmentTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch\Test; 13 | 14 | /** 15 | * @package Fetch 16 | * @author Robert Hafner 17 | */ 18 | class AttachmentTest extends \PHPUnit_Framework_TestCase 19 | { 20 | 21 | public static function getAttachments($MessageId) 22 | { 23 | $server = ServerTest::getServer(); 24 | $message = new \Fetch\Message($MessageId, $server); 25 | $attachments = $message->getAttachments(); 26 | $returnAttachments = array(); 27 | foreach($attachments as $attachment) 28 | $returnAttachments[$attachment->getFileName()] = $attachment; 29 | 30 | return $returnAttachments; 31 | } 32 | 33 | public function testGetData() 34 | { 35 | $attachments = static::getAttachments('6'); 36 | 37 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 38 | $md5_RCA = '3e9b6f02551590a7bcfff5d50b5b7b20'; 39 | $this->assertEquals($md5_RCA, md5($attachment_RCA->getData())); 40 | 41 | $attachment_TestCard = $attachments['Test_card.png.zip']; 42 | $md5_TestCard = '94c40bd83fbfa03b29bf1811f9aaccea'; 43 | $this->assertEquals($md5_TestCard, md5($attachment_TestCard->getData())); 44 | } 45 | 46 | public function testGetMimeType() 47 | { 48 | $attachments = static::getAttachments('6'); 49 | 50 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 51 | $mimetype_RCA = 'application/zip'; 52 | $this->assertEquals($mimetype_RCA, $attachment_RCA->getMimeType()); 53 | 54 | $attachment_TestCard = $attachments['Test_card.png.zip']; 55 | $mimetype_TestCard = 'application/zip'; 56 | $this->assertEquals($mimetype_TestCard, $attachment_TestCard->getMimeType()); 57 | } 58 | 59 | public function testGetSize() 60 | { 61 | $attachments = static::getAttachments('6'); 62 | 63 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 64 | $size_RCA = 378338; 65 | $this->assertEquals($size_RCA, $attachment_RCA->getSize()); 66 | 67 | $attachment_TestCard = $attachments['Test_card.png.zip']; 68 | $size_TestCard = 32510; 69 | $this->assertEquals($size_TestCard, $attachment_TestCard->getSize()); 70 | } 71 | 72 | public function testGetStructure() 73 | { 74 | $attachments = static::getAttachments('6'); 75 | 76 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 77 | $structure_RCA = $attachment_RCA->getStructure(); 78 | 79 | $this->assertObjectHasAttribute('type', $structure_RCA); 80 | $this->assertEquals(3, $structure_RCA->type); 81 | 82 | $this->assertObjectHasAttribute('subtype', $structure_RCA); 83 | $this->assertEquals('ZIP', $structure_RCA->subtype); 84 | 85 | $this->assertObjectHasAttribute('bytes', $structure_RCA); 86 | $this->assertEquals(378338, $structure_RCA->bytes); 87 | } 88 | 89 | public function testSaveToDirectory() 90 | { 91 | $attachments = static::getAttachments('6'); 92 | 93 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 94 | 95 | $tmpdir = rtrim(sys_get_temp_dir(), '/') . '/'; 96 | $filepath = $tmpdir . 'RCA_Indian_Head_test_pattern.JPG.zip'; 97 | 98 | $this->assertTrue($attachment_RCA->saveToDirectory($tmpdir)); 99 | 100 | $this->assertFileExists($filepath); 101 | $this->assertEquals(md5(file_get_contents($filepath)), md5($attachment_RCA->getData())); 102 | 103 | $attachments = static::getAttachments('6'); 104 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 105 | $this->assertFalse($attachment_RCA->saveToDirectory('/'), 'Returns false when attempting to save without filesystem permission.'); 106 | 107 | $attachments = static::getAttachments('6'); 108 | $attachment_RCA = $attachments['RCA_Indian_Head_test_pattern.JPG.zip']; 109 | $this->assertFalse($attachment_RCA->saveToDirectory($filepath), 'Returns false when attempting to save over a file.'); 110 | } 111 | 112 | public static function tearDownAfterClass() 113 | { 114 | $tmpdir = rtrim(sys_get_temp_dir(), '/') . '/'; 115 | $filepath = $tmpdir . 'RCA_Indian_Head_test_pattern.JPG.zip'; 116 | unlink($filepath); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/Fetch/Test/MIMETest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch\Test; 13 | 14 | use Fetch\MIME; 15 | 16 | /** 17 | * @package Fetch 18 | * @author Robert Hafner 19 | * @author Sergey Linnik 20 | */ 21 | class MIMETest extends \PHPUnit_Framework_TestCase 22 | { 23 | public function decodeData() 24 | { 25 | return array( 26 | array(null, null), 27 | array('Just text', 'Just text'), 28 | array('Keith Moore ', '=?US-ASCII?Q?Keith_Moore?= '), 29 | array('Keld Jørn Simonsen ', '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= '), 30 | array('André Pirard ', '=?ISO-8859-1?Q?Andr=E9?= Pirard '), 31 | array( 32 | 'If you can read this you understand the example.', 33 | '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' 34 | . PHP_EOL . 35 | '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=' 36 | ), 37 | ); 38 | } 39 | 40 | /** 41 | * @dataProvider decodeData 42 | * 43 | * @param string $expected 44 | * @param string $text 45 | * @param string $charset 46 | */ 47 | public function testDecode($expected, $text, $charset = 'UTF-8') 48 | { 49 | self::assertSame($expected, MIME::decode($text, $charset)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Fetch/Test/MessageTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch\Test; 13 | use Fetch\Message; 14 | 15 | /** 16 | * @package Fetch 17 | * @author Robert Hafner 18 | */ 19 | class MessageTest extends \PHPUnit_Framework_TestCase 20 | { 21 | public static function getMessage($id) 22 | { 23 | $server = ServerTest::getServer(); 24 | 25 | return new \Fetch\Message($id, $server); 26 | } 27 | 28 | public function testConstructMessage() 29 | { 30 | $message = static::getMessage(3); 31 | $this->assertInstanceOf('\Fetch\Message', $message); 32 | } 33 | 34 | public function testGetOverview() 35 | { 36 | $message = static::getMessage(3); 37 | $overview = $message->getOverview(); 38 | $this->assertEquals('Welcome', $overview->subject, 'Subject available from overview'); 39 | $this->assertEquals('tedivm@tedivm.com', $overview->from, 'From available from overview'); 40 | $this->assertEquals('testuser@tedivm.com', $overview->to, 'To available from overview'); 41 | $this->assertEquals(1465, $overview->size, 'Size available from overview'); 42 | $this->assertEquals(0, $overview->flagged, 'Flagged available from overview'); 43 | $this->assertEquals(1, $overview->seen, 'Seen available from overview'); 44 | } 45 | 46 | public function testGetHeaders() 47 | { 48 | $message = static::getMessage(3); 49 | $headers = $message->getHeaders(); 50 | $this->assertEquals('Sun, 1 Dec 2013 21:14:03 -0800 (PST)', $headers->date, 'Headers contain the right date.'); 51 | $this->assertEquals('testuser@tedivm.com', $headers->toaddress, 'Headers contain toaddress.'); 52 | $this->assertEquals('tedivm@tedivm.com', $headers->fromaddress, 'Headers contain fromaddress'); 53 | } 54 | 55 | public function testGetStructure() 56 | { 57 | 58 | } 59 | 60 | public function testGetMessageBody() 61 | { 62 | // easiest way to deal with php encoding issues is simply not to. 63 | $plaintextTest = 'f9377a89c9c935463a2b35c92dd61042'; 64 | $convertedHtmlTest = '11498bcf191900d634ff8772a64ca523'; 65 | $pureHtmlTest = '6a366ddecf080199284146d991d52169'; 66 | 67 | $message = static::getMessage(3); 68 | $messageNonHTML = $message->getMessageBody(); 69 | $this->assertEquals($plaintextTest, md5($messageNonHTML), 'Message returns as plaintext.'); 70 | 71 | $messageHTML = $message->getMessageBody(true); 72 | $this->assertEquals($convertedHtmlTest, md5($messageHTML), 'Message converts from plaintext to HTML when requested.'); 73 | 74 | $message = static::getMessage(4); 75 | $messageHTML = $message->getMessageBody(true); 76 | $this->assertEquals($pureHtmlTest, md5($messageHTML), 'Message returns as HTML.'); 77 | 78 | } 79 | 80 | public function testGetPlainTextBody() 81 | { 82 | // easiest way to deal with php encoding issues is simply not to. 83 | $plaintextTest1 = 'f9377a89c9c935463a2b35c92dd61042'; 84 | $plaintextTest2 = '0b8fc9b534a1789f1071f996f238a07a'; 85 | $plaintextTest3 = 'd41d8cd98f00b204e9800998ecf8427e'; 86 | 87 | $message = static::getMessage(3); 88 | $messagePlainText = $message->getPlainTextBody(); 89 | $this->assertEquals($plaintextTest1, md5($messagePlainText), 'Message returns as plaintext.'); 90 | 91 | $message = static::getMessage(4); 92 | $messagePlainText = $message->getPlainTextBody(); 93 | $this->assertEquals($plaintextTest2, md5($messagePlainText), 'Message returns as plaintext.'); 94 | 95 | $message = static::getMessage(6); 96 | $messagePlainText = $message->getPlainTextBody(); 97 | $this->assertEquals($plaintextTest3, md5($messagePlainText), 'Message does not return as plaintext.'); 98 | 99 | } 100 | 101 | public function testGetHtmlBody() 102 | { 103 | // easiest way to deal with php encoding issues is simply not to. 104 | $HtmlTest1 = 'd41d8cd98f00b204e9800998ecf8427e'; 105 | $HtmlTest2 = '6a366ddecf080199284146d991d52169'; 106 | 107 | $message = static::getMessage(3); 108 | $messageHtml = $message->getHtmlBody(); 109 | $this->assertEquals($HtmlTest1, md5($messageHtml), 'Message does not return as HTML.'); 110 | 111 | $message = static::getMessage(4); 112 | $messageHtml = $message->getHtmlBody(); 113 | $this->assertEquals($HtmlTest2, md5($messageHtml), 'Message returns as HTML.'); 114 | 115 | } 116 | 117 | public function testGetAddresses() 118 | { 119 | $message = static::getMessage(3); 120 | 121 | $addresses = $message->getAddresses('to'); 122 | $this->assertEquals('testuser@tedivm.com', $addresses[0]['address'], 'Retrieving to user from address array.'); 123 | 124 | $addressString = $message->getAddresses('to', true); 125 | $this->assertEquals('testuser@tedivm.com', $addressString, 'Returning To address as string.'); 126 | 127 | $addresses = $message->getAddresses('from'); 128 | $this->assertEquals('tedivm@tedivm.com', $addresses['address'], 'Returning From address as an address array.'); 129 | 130 | $addressString = $message->getAddresses('from', true); 131 | $this->assertEquals('tedivm@tedivm.com', $addressString, 'Returning From address as string.'); 132 | } 133 | 134 | public function testGetDate() 135 | { 136 | $message = static::getMessage(3); 137 | $this->assertEquals(1385961243, $message->getDate(), 'Returns date as timestamp.'); 138 | } 139 | 140 | public function testGetSubject() 141 | { 142 | $message = static::getMessage(3); 143 | $this->assertEquals('Welcome', $message->getSubject(), 'Returns Subject.'); 144 | } 145 | 146 | public function testDelete() 147 | { 148 | 149 | } 150 | 151 | public function testGetImapBox() 152 | { 153 | $server = ServerTest::getServer(); 154 | $message = new \Fetch\Message('3', $server); 155 | $this->assertEquals($server, $message->getImapBox(), 'getImapBox returns Server used to create Message.'); 156 | } 157 | 158 | public function testGetUid() 159 | { 160 | $message = static::getMessage('3'); 161 | $this->assertEquals(3, $message->getUid(), 'Message returns UID'); 162 | } 163 | 164 | public function testGetAttachments() 165 | { 166 | $messageWithoutAttachments = static::getMessage('3'); 167 | $this->assertFalse($messageWithoutAttachments->getAttachments(), 'getAttachments returns false when no attachments present.'); 168 | 169 | $messageWithAttachments = static::getMessage('6'); 170 | $attachments = $messageWithAttachments->getAttachments(); 171 | $this->assertCount(2, $attachments); 172 | foreach($attachments as $attachment) 173 | $this->assertInstanceOf('\Fetch\Attachment', $attachment, 'getAttachments returns Fetch\Attachment objects.'); 174 | 175 | $attachment = $messageWithAttachments->getAttachments('Test_card.png.zip'); 176 | $this->assertInstanceOf('\Fetch\Attachment', $attachment, 'getAttachment returns specified Fetch\Attachment object.'); 177 | } 178 | 179 | public function testCheckFlag() 180 | { 181 | $message = static::getMessage('3'); 182 | $this->assertFalse($message->checkFlag('flagged')); 183 | $this->assertTrue($message->checkFlag('seen')); 184 | } 185 | 186 | public function testSetFlag() 187 | { 188 | $message = static::getMessage('3'); 189 | $this->assertFalse($message->checkFlag('answered'), 'Message is not answered.'); 190 | 191 | $this->assertTrue($message->setFlag('answered'), 'setFlag returned true.'); 192 | $this->assertTrue($message->checkFlag('answered'), 'Message was successfully answered.'); 193 | 194 | $this->assertTrue($message->setFlag('answered', false), 'setFlag returned true.'); 195 | $this->assertFalse($message->checkFlag('answered'), 'Message was successfully unanswered.'); 196 | 197 | $message = static::getMessage('2'); 198 | $this->assertFalse($message->checkFlag('flagged'), 'Message is not flagged.'); 199 | 200 | $this->assertTrue($message->setFlag('flagged'), 'setFlag returned true.'); 201 | $this->assertTrue($message->checkFlag('flagged'), 'Message was successfully flagged.'); 202 | 203 | $message = static::getMessage('2'); 204 | $this->assertTrue($message->setFlag('flagged', false), 'setFlag returned true.'); 205 | $this->assertFalse($message->checkFlag('flagged'), 'Message was successfully unflagged.'); 206 | } 207 | 208 | public function testMoveToMailbox() 209 | { 210 | $server = ServerTest::getServer(); 211 | 212 | // Testing by moving message from "Test Folder" to "Sent" 213 | 214 | // Count Test Folder 215 | $testFolderNumStart = $server->numMessages('Test Folder'); 216 | $server->setMailbox('Test Folder'); 217 | $this->assertEquals($testFolderNumStart, $server->numMessages(), 'Server presents consistent information between numMessages when mailbox set and directly queried for number of messages'); 218 | 219 | // Get message from Test Folder 220 | $message = current($server->getMessages(1)); 221 | $this->assertInstanceOf('\Fetch\Message', $message, 'Server returned Message.'); 222 | 223 | // Switch to Sent folder, count messages 224 | $sentFolderNumStart = $server->numMessages('Sent'); 225 | $server->setMailbox('Sent'); 226 | $this->assertEquals($sentFolderNumStart, $server->numMessages(), 'Server presents consistent information between numMessages when mailbox set and directly queried for number of messages'); 227 | 228 | // Switch to "Flagged" folder in order to test that function properly returns to it 229 | $this->assertTrue($server->setMailBox('Flagged Email')); 230 | // Move the message! 231 | $this->assertTrue($message->moveToMailBox('Sent')); 232 | // Make sure we're still in the same folder 233 | $this->assertEquals('Flagged Email', $server->getMailBox(), 'Returned Server back to right mailbox.'); 234 | $this->assertAttributeEquals('Sent', 'mailbox', $message, 'Message mailbox changed to new location.'); 235 | // Make sure Test Folder lost a message 236 | $this->assertTrue($server->setMailBox('Test Folder')); 237 | $this->assertEquals($testFolderNumStart - 1, $server->numMessages(), 'Message moved out of Test Folder.'); 238 | // Make sure Sent folder gains one 239 | $this->assertTrue($server->setMailBox('Sent')); 240 | $this->assertEquals($sentFolderNumStart + 1, $server->numMessages(), 'Message moved into Sent Folder.'); 241 | } 242 | 243 | public function testDecode() 244 | { 245 | $quotedPrintableDecoded = "Now's the time for all folk to come to the aid of their country."; 246 | $quotedPrintable = <<<'ENCODE' 247 | Now's the time = 248 | for all folk to come= 249 | to the aid of their country. 250 | ENCODE; 251 | $this->assertEquals($quotedPrintableDecoded, Message::decode($quotedPrintable, 'quoted-printable'), 'Decodes quoted printable'); 252 | $this->assertEquals($quotedPrintableDecoded, Message::decode($quotedPrintable, 4), 'Decodes quoted printable'); 253 | 254 | $testString = 'This is a test string'; 255 | $base64 = base64_encode($testString); 256 | $this->assertEquals($testString, Message::decode($base64, 'base64'), 'Decodes quoted base64'); 257 | $this->assertEquals($testString, Message::decode($base64, 3), 'Decodes quoted base64'); 258 | 259 | $notEncoded = '> w - www.somesite.com.au'; 260 | $this->assertEquals($notEncoded, Message::decode($notEncoded, 0), 'Nothing to decode'); 261 | } 262 | 263 | public function testTypeIdToString() 264 | { 265 | $types = array(); 266 | $types[0] = 'text'; 267 | $types[1] = 'multipart'; 268 | $types[2] = 'message'; 269 | $types[3] = 'application'; 270 | $types[4] = 'audio'; 271 | $types[5] = 'image'; 272 | $types[6] = 'video'; 273 | $types[7] = 'other'; 274 | $types[8] = 'other'; 275 | $types[32] = 'other'; 276 | 277 | foreach($types as $id => $type) 278 | $this->assertEquals($type, Message::typeIdToString($id)); 279 | } 280 | 281 | public function testGetParametersFromStructure() 282 | { 283 | 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /tests/Fetch/Test/ServerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Fetch\Test; 13 | 14 | use Fetch\Server; 15 | 16 | /** 17 | * @package Fetch 18 | * @author Robert Hafner 19 | */ 20 | class ServerTest extends \PHPUnit_Framework_TestCase 21 | { 22 | public static $num_messages_inbox = 12; 23 | 24 | /** 25 | * @dataProvider flagsDataProvider 26 | * @param string $expected server string with %host% placeholder 27 | * @param integer $port to use (needed to test behavior on port 143 and 993 from constructor) 28 | * @param array $flags to set/unset ($flag => $value) 29 | */ 30 | public function testFlags($expected, $port, $flags) 31 | { 32 | $server = new Server(TESTING_SERVER_HOST, $port); 33 | 34 | foreach ($flags as $flag => $value) { 35 | $server->setFlag($flag, $value); 36 | } 37 | 38 | $this->assertEquals(str_replace('%host%', TESTING_SERVER_HOST, $expected), $server->getServerString()); 39 | } 40 | 41 | public function testFlagOverwrite() 42 | { 43 | $server = static::getServer(); 44 | 45 | $server->setFlag('TestFlag', 'true'); 46 | $this->assertAttributeContains('TestFlag=true', 'flags', $server); 47 | 48 | $server->setFlag('TestFlag', 'false'); 49 | $this->assertAttributeContains('TestFlag=false', 'flags', $server); 50 | } 51 | 52 | public function flagsDataProvider() 53 | { 54 | return array( 55 | array('{%host%:143/novalidate-cert}', 143, array()), 56 | array('{%host%:143/validate-cert}', 143, array('validate-cert' => true)), 57 | array('{%host%:143}', 143, array('novalidate-cert' => false)), 58 | array('{%host%:993/ssl}', 993, array()), 59 | array('{%host%:993}', 993, array('ssl' => false)), 60 | array('{%host%:100/tls}', 100, array('tls' => true)), 61 | array('{%host%:100/tls}', 100, array('tls' => true, 'tls' => true)), 62 | array('{%host%:100/notls}', 100, array('tls' => true, 'notls' => true)), 63 | array('{%host%:100}', 100, array('ssl' => true, 'ssl' => false)), 64 | array('{%host%:100/user=foo}', 100, array('user' => 'foo')), 65 | array('{%host%:100/user=foo}', 100, array('user' => 'foo', 'user' => 'foo')), 66 | array('{%host%:100/user=bar}', 100, array('user' => 'foo', 'user' => 'bar')), 67 | array('{%host%:100}', 100, array('user' => 'foo', 'user' => false)), 68 | ); 69 | } 70 | 71 | /** 72 | * @dataProvider connectionDataProvider 73 | * @param integer $port to use (needed to test behavior on port 143 and 993 from constructor) 74 | * @param array $flags to set/unset ($flag => $value) 75 | * @param string $message Assertion message 76 | */ 77 | public function testConnection($port, $flags, $message) 78 | { 79 | $server = new Server(TESTING_SERVER_HOST, $port); 80 | $server->setAuthentication(TEST_USER, TEST_PASSWORD); 81 | 82 | foreach ($flags as $flag => $value) { 83 | $server->setFlag($flag, $value); 84 | } 85 | 86 | $imapSteam = $server->getImapStream(); 87 | $this->assertInternalType('resource', $imapSteam, $message); 88 | } 89 | 90 | public function connectionDataProvider() 91 | { 92 | return array( 93 | array(143, array(), 'Connects with default settings.'), 94 | array(993, array('novalidate-cert' => true), 'Connects over SSL (self signed).'), 95 | ); 96 | } 97 | 98 | public function testNumMessages() 99 | { 100 | $server = static::getServer(); 101 | $numMessages = $server->numMessages(); 102 | $this->assertEquals(self::$num_messages_inbox, $numMessages); 103 | $this->assertEquals(0, $server->numMessages( 'DOESNOTEXIST'.time() ) ); 104 | } 105 | 106 | public function testGetMessages() 107 | { 108 | $server = static::getServer(); 109 | $messages = $server->getMessages(5); 110 | 111 | $this->assertCount(5, $messages, 'Five messages returned'); 112 | foreach ($messages as $message) { 113 | $this->assertInstanceOf('\Fetch\Message', $message, 'Returned values are Messages'); 114 | } 115 | } 116 | 117 | public function testGetMessagesOrderedByDateAsc() 118 | { 119 | $server = static::getServer(); 120 | $messages = $server->getOrderedMessages(SORTDATE, false, 2); 121 | 122 | $this->assertCount(2, $messages, 'Two messages returned'); 123 | $this->assertGreaterThan($messages[0]->getDate(), $messages[1]->getDate(), 'Messages in ascending order'); 124 | } 125 | 126 | public function testGetMessagesOrderedByDateDesc() 127 | { 128 | $server = static::getServer(); 129 | $messages = $server->getOrderedMessages(SORTDATE, true, 2); 130 | 131 | $this->assertCount(2, $messages, 'Two messages returned'); 132 | $this->assertLessThan($messages[0]->getDate(), $messages[1]->getDate(), 'Messages in descending order'); 133 | } 134 | 135 | public function testGetMailBox() 136 | { 137 | $server = static::getServer(); 138 | $this->assertEquals('', $server->getMailBox()); 139 | $this->assertTrue($server->setMailBox('Sent')); 140 | $this->assertEquals('Sent', $server->getMailBox()); 141 | } 142 | 143 | public function testSetMailBox() 144 | { 145 | $server = static::getServer(); 146 | 147 | $this->assertTrue($server->setMailBox('Sent')); 148 | $this->assertEquals('Sent', $server->getMailBox()); 149 | 150 | $this->assertTrue($server->setMailBox('Flagged Email')); 151 | $this->assertEquals('Flagged Email', $server->getMailBox()); 152 | 153 | $this->assertFalse($server->setMailBox('Cheese')); 154 | 155 | $this->assertTrue($server->setMailBox('')); 156 | $this->assertEquals('', $server->getMailBox()); 157 | } 158 | 159 | public function testHasMailBox() 160 | { 161 | $server = static::getServer(); 162 | 163 | $this->assertTrue($server->hasMailBox('Sent'), 'Has mailbox "Sent"'); 164 | $this->assertTrue($server->hasMailBox('Flagged Email'), 'Has mailbox "Flagged Email"'); 165 | $this->assertFalse($server->hasMailBox('Cheese'), 'Does not have mailbox "Cheese"'); 166 | } 167 | 168 | public function testListMailBoxes() 169 | { 170 | $server = static::getServer(); 171 | $spec = sprintf('{%s:143/novalidate-cert}', TESTING_SERVER_HOST); 172 | 173 | $list = $server->listMailboxes('*'); 174 | $this->assertContains($spec.'Sent', $list, 'Has mailbox "Sent"'); 175 | $this->assertNotContains($spec.'Cheese', $list, 'Does not have mailbox "Cheese"'); 176 | } 177 | 178 | public function testCreateMailbox() 179 | { 180 | $server = static::getServer(); 181 | 182 | $this->assertFalse($server->hasMailBox('Cheese'), 'Does not have mailbox "Cheese"'); 183 | $this->assertTrue($server->createMailBox('Cheese'), 'createMailbox returns true.'); 184 | $this->assertTrue($server->hasMailBox('Cheese'), 'Mailbox "Cheese" was created'); 185 | } 186 | 187 | public function testDeleteMailbox() 188 | { 189 | $server = static::getServer(); 190 | $this->assertTrue($server->hasMailBox('Cheese'), 'Does have mailbox "Cheese"'); 191 | $this->assertTrue($server->deleteMailBox('Cheese'), 'deleteMailBox returns true.'); 192 | $this->assertFalse($server->hasMailBox('Cheese'), 'Mailbox "Cheese" was deleted'); 193 | } 194 | 195 | /** 196 | * @expectedException \RuntimeException 197 | */ 198 | public function testSetOptionsException() 199 | { 200 | $server = static::getServer(); 201 | $server->setOptions('purple'); 202 | } 203 | 204 | public function testSetOptions() 205 | { 206 | $server = static::getServer(); 207 | $server->setOptions(5); 208 | $this->assertAttributeEquals(5, 'options', $server); 209 | } 210 | 211 | public function testExpunge() 212 | { 213 | $server = static::getServer(); 214 | $message = $server->getMessageByUid(12); 215 | 216 | $this->assertInstanceOf('\Fetch\Message', $message, 'Message exists'); 217 | 218 | $message->delete(); 219 | 220 | $this->assertInstanceOf('\Fetch\Message', $server->getMessageByUid(12), 'Message still present after being deleted but before being expunged.'); 221 | 222 | $server->expunge(); 223 | 224 | $this->assertFalse($server->getMessageByUid(12), 'Message successfully expunged'); 225 | } 226 | 227 | public static function getServer() 228 | { 229 | $server = new Server(TESTING_SERVER_HOST, 143); 230 | $server->setAuthentication(TEST_USER, TEST_PASSWORD); 231 | 232 | return $server; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | error_reporting(-1); 13 | 14 | define('TESTING', true); 15 | define('TEST_USER', 'testuser'); 16 | define('TEST_PASSWORD', 'applesauce'); 17 | 18 | date_default_timezone_set('UTC'); 19 | 20 | if (getenv('TRAVIS')) { 21 | define('TESTING_ENVIRONMENT', 'TRAVIS'); 22 | define('TESTING_SERVER_HOST', '127.0.0.1'); 23 | } else { 24 | define('TESTING_ENVIRONMENT', 'VAGRANT'); 25 | define('TESTING_SERVER_HOST', '172.31.1.2'); 26 | } 27 | 28 | $filename = __DIR__ .'/../vendor/autoload.php'; 29 | 30 | if (!file_exists($filename)) { 31 | echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" . PHP_EOL; 32 | echo " You need to execute `composer install` before running the tests. " . PHP_EOL; 33 | echo " Vendors are required for complete test execution. " . PHP_EOL; 34 | echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" . PHP_EOL . PHP_EOL; 35 | $filename = __DIR__ .'/../autoload.php'; 36 | require_once $filename; 37 | } else { 38 | $loader = require $filename; 39 | $loader->add('Fetch\\Test', __DIR__); 40 | } 41 | -------------------------------------------------------------------------------- /tests/runTests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ ! -n "$TRAVIS" ]; then 6 | ./vendor/tedivm/dovecottesting/SetupEnvironment.sh 7 | sleep 5 8 | fi 9 | 10 | echo 'Running unit tests.' 11 | ./vendor/bin/phpunit --verbose --coverage-clover build/logs/clover.xml 12 | 13 | echo '' 14 | echo '' 15 | echo '' 16 | echo 'Testing for Coding Styling Compliance.' 17 | echo 'All code should follow PSR standards.' 18 | ./vendor/bin/php-cs-fixer fix ./ --level="all" -vv --dry-run 19 | --------------------------------------------------------------------------------