Completed
Push — master ( f7ad33...cd335b )
by Rudie
02:05
created

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
class IMAPMailbox {
4
5
	public $IMAPMessageClass = 'IMAPMessage';
6
7
	public $server = '';
8
	public $username = '';
9
	public $password = '';
10
	public $mailbox = '';
11
12
	public $mbox; // IMAP resource
13
14
	public function __construct( $server, $username, $password, $mailbox = 'INBOX', $flags = array() ) {
15
		$this->server = $server;
16
		$this->username = $username;
17
		$this->password = $password;
18
		$this->mailbox = $mailbox;
19
		$this->flags = $flags;
0 ignored issues
show
The property flags does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
20
21
		$this->connect();
22
	}
23
24
	public function connect() {
25
		$server = $this->server;
26
		if ( $this->flags ) {
27
			$server .= '/' . implode('/', $this->flags);
28
		}
29
30
		$mailbox = '{'.$server.'}'.$this->mailbox;
31
		$this->mbox = imap_open($mailbox, $this->username, $this->password);
32
	}
33
34
	public function messages( $options = true ) {
35
		if ( is_bool($options) ) {
36
			$options = array('seen' => $options);
37
		}
38
39
		$options = self::options($options, array(
40
			'offset' => 0,
41
			'limit' => 0,
42
			'seen' => true,
43
			'newestFirst' => true,
44
		));
45
46
		$IMAPMessageClass = $this->IMAPMessageClass;
47
48
		$headers = imap_headers($this->mbox);
49
		if ( $options['newestFirst'] ) {
50
			$headers = array_reverse($headers);
51
		}
52
53
		$messages = array();
54
		$eligables = 0;
55
		foreach ( $headers AS $n => $header ) {
56
			if ( preg_match('/(U?)\s+(\d+)\)/', $header, $match) ) {
57
				$unseen = (bool)trim($match[1]);
58
				$msgNum = (int)$match[2];
59
60
				$eligable = $options['seen'] || $unseen;
61
				if ( $eligable ) {
62
					$eligables++;
63
				}
64
65
				if ( $eligable ) {
66
					if ( $eligables > $options['offset'] ) {
67
						if ( !$options['limit'] || !isset($messages[$options['limit']-1]) ) {
68
							$messages[] = new $IMAPMessageClass($this, $msgNum, $header, $unseen);
69
						}
70
					}
71
				}
72
73
				if ( $options['limit'] && isset($messages[$options['limit']-1]) ) {
74
					break;
75
				}
76
			}
77
		}
78
79
		return $messages;
80
	}
81
82
	static public function options( $options, $base ) {
83
		foreach ( $options AS $name => $value ) {
84
			$base[$name] = $value; // overwrite base
85
		}
86
87
		return $base;
88
	}
89
90
}
91
92
class IMAPMessage {
93
94
	static public $IMAPMessagePartClass = 'IMAPMessagePart';
95
	static public $IMAPMessageAttachmentClass = 'IMAPMessageAttachment';
96
97
	public $mailbox; // typeof IMAPMailbox
98
99
	public $msgNumber = 1; // starts at 1, not 0
100
	public $header = '';
101
	public $unseen = true;
102
103
	public $headers; // typeof stdClass
104
	public $structure; // typeof stdClass
105
106
	public $subject = '';
107
	public $parts = array();
108
	public $plainBody;
109
	public $HTMLBody;
110
	public $attachments = array(); // typeof Array<IMAPMessageAttachment>
111
112
	public function __construct( IMAPMailbox $mailbox, $msgNumber, $header, $unseen ) {
113
		$this->mailbox = $mailbox;
114
		$this->msgNumber = $msgNumber;
115
		$this->header = $header;
116
		$this->unseen = $unseen;
117
	}
118
119
	protected function flags( $flags, $clear ) {
120
		$cb = $clear ? 'imap_clearflag_full' : 'imap_setflag_full';
121
122
		$feedback = array();
123
		foreach ( (array)$flags AS $flag ) {
124
			$flag = '\\' . ucfirst($flag);
125
			$feedback[] = $cb($this->mailbox->mbox, (string)$this->msgNumber, $flag);
126
		}
127
128
		return is_array($flags) ? $feedback : $feedback[0];
129
	}
130
131
	public function flag( $flags ) {
132
		return $this->flags($flags, false);
133
	}
134
135
	public function unflag( $flags ) {
136
		return $this->flags($flags, true);
137
	}
138
139
	public function subject() {
140
		if ( !$this->subject ) {
141
			$headers = $this->headers();
142
143
			$subject = imap_utf8($headers->Subject);
144
145
			$this->subject = trim($subject);
146
		}
147
148
		return $this->subject;
149
	}
150
151
	public function headers() {
152
		if ( !$this->headers ) {
153
			$this->headers = imap_headerinfo($this->mailbox->mbox, $this->msgNumber);
154
		}
155
156
		return $this->headers;
157
	}
158
159
	public function parts() {
160
		if ( !$this->parts ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
161
			$structure = $this->structure();
162
163
			// Possibilities:
164
			// - PLAIN => only plain, no attachments
165
			// - ALTERNATIVE => plain & html, no attachments
166
			// - MIXED => message (ALTERNATIVE or PLAIN) & attachments
167
168
			$IMAPMessagePartClass = self::$IMAPMessagePartClass;
169
170
			$parts = $attachments = array();
171
172
			// - PLAIN
173
			if ( 'PLAIN' == $structure->subtype ) {
174
				$parts[] = new $IMAPMessagePartClass($this, $structure, '1');
175
			}
176
177
			// - ALTERNATIVE
178
			else if ( 'ALTERNATIVE' == $structure->subtype ) {
179
				// get message parts
180
				$parts = $this->messageParts($structure->parts, null, $IMAPMessagePartClass);
0 ignored issues
show
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
181
			}
182
183
			// - MIXED -- uh oh -- attachments!
184
			else {
185
				$IMAPMessageAttachmentClass = self::$IMAPMessageAttachmentClass;
186
187
				foreach ( $structure->parts AS $i => $part ) {
188
					if ( 'ALTERNATIVE' == $part->subtype ) {
189
						$parts = array_merge($parts, $this->messageParts($part->parts, $i+1, $IMAPMessagePartClass));
0 ignored issues
show
$i + 1 is of type integer|double, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
190
					}
191
					else {
192
						$parts[] = new $IMAPMessagePartClass($this, $part, $i+1);
193
194
						if ( $part->ifdisposition && 'ATTACHMENT' == $part->disposition ) {
195
							$attachments[] = new $IMAPMessageAttachmentClass($this, $part, $i+1);
196
						}
197
					}
198
				}
199
			}
200
201
			$this->parts = $parts;
202
			$this->attachments = $attachments;
203
204
			foreach ( $parts AS $part ) {
205
				if ( 'PLAIN' == $part->subtype && !$this->plainBody ) {
206
					$this->plainBody = $part;
207
				}
208
				else if ( 'HTML' == $part->subtype && !$this->HTMLBody ) {
209
					$this->HTMLBody = $part;
210
				}
211
			}
212
		}
213
214
		return $this->parts;
215
	}
216
217
	protected function messageParts( $parts, $sectionPrefix = array(), $IMAPMessagePartClass ) {
218
		$sectionPrefix = (array)$sectionPrefix;
219
220
		$partObjects = array();
221
		foreach ( $parts AS $i => $part ) {
222
			$s = $sectionPrefix;
223
			$s[] = (string)($i+1);
224
			$section = implode('.', $s);
225
226
			$partObjects[] = new $IMAPMessagePartClass($this, $part, $section);
227
		}
228
229
		return $partObjects;
230
	}
231
232
	public function structure() {
233
		if ( !$this->structure ) {
234
			$this->structure = imap_fetchstructure($this->mailbox->mbox, $this->msgNumber);
235
		}
236
237
		return $this->structure;
238
	}
239
240
}
241
242
class IMAPMessagePart {
243
244
	public $section = '';
245
	public $subtype = '';
246
	public $contentType = '';
247
	public $charset = '';
248
	public $size = 0;
249
	public $data = '';
250
251
	public $message; // typeof IMAPMessage
252
	public $structure; // typeof stdClass
253
254
	public function __construct( $message, $structure, $section ) {
255
		$this->message = $message;
256
		$this->structure = $structure;
257
		$this->section = (string)$section;
258
		$this->subtype = $structure->subtype;
259
	}
260
261
	public function content() {
262
		$body = imap_fetchbody($this->message->mailbox->mbox, $this->message->msgNumber, $this->section);
263
		return $this->decode($body);
264
	}
265
266
	public function decode( $content ) {
267
		return quoted_printable_decode($content);
268
	}
269
270
}
271
272
class IMAPMessageAttachment extends IMAPMessagePart {
273
274
	public $filename = '';
275
276
	public function __construct( $message, $structure, $section ) {
277
		parent::__construct($message, $structure, $section);
278
279
		$this->filename();
280
	}
281
282
	public function filename() {
283
		if ( !$this->filename ) {
284
			// from dparameters
285
			if ( $this->structure->ifdparameters ) {
286 View Code Duplication
				foreach ( $this->structure->dparameters AS $param ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
287
					if ( 'FILENAME' == $param->attribute ) {
288
						$this->filename = $param->value;
289
						break;
290
					}
291
				}
292
			}
293
294
			// from parameters
295
			if ( !$this->filename && $this->structure->ifparameters ) {
296 View Code Duplication
				foreach ( $this->structure->parameters AS $param ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
					if ( 'NAME' == $param->attribute ) {
298
						$this->filename = $param->value;
299
						break;
300
					}
301
				}
302
			}
303
		}
304
305
		return $this->filename;
306
	}
307
308
	public function save( $filepath ) {
309
		if ( '/' == substr($filepath, -1) ) {
310
			$filepath .= $this->filename;
311
		}
312
313
		return file_put_contents($filepath, $this->decode($this->content()));
314
	}
315
316
	public function decode( $content ) {
317
		return base64_decode($content);
318
	}
319
320
}
321
322
323