├── .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 [](https://travis-ci.org/tedious/Fetch)
2 |
3 | [](https://github.com/tedious/fetch/blob/master/LICENSE)
4 | [](https://packagist.org/packages/tedivm/fetch)
5 | [](https://coveralls.io/r/tedious/Fetch?branch=master)
6 | [](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 |
--------------------------------------------------------------------------------