Skip to content

Commit 5d0d888

Browse files
committed
Message search method added & several small improvements
1 parent 5d89346 commit 5d0d888

File tree

4 files changed

+249
-81
lines changed

4 files changed

+249
-81
lines changed

src/IMAP/Client.php

Lines changed: 41 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/*
33
* File: Client.php
4-
* Category: Helper
4+
* Category: -
55
* Author: M. Goldenbaum
66
* Created: 19.01.17 22:21
77
* Updated: -
@@ -12,10 +12,9 @@
1212

1313
namespace Webklex\IMAP;
1414

15-
use Illuminate\Support\Facades\Config;
16-
1715
use Webklex\IMAP\Exceptions\ConnectionFailedException;
1816
use Webklex\IMAP\Exceptions\GetMessagesFailedException;
17+
use Webklex\IMAP\Exceptions\MessageSearchValidationException;
1918

2019
/**
2120
* Class Client
@@ -223,9 +222,9 @@ public function disconnect() {
223222

224223
/**
225224
* Get a folder instance by a folder name
226-
* -------------------------------------------------------
227-
* PLEASE NOTE: This is a completely experimental function
228-
* -------------------------------------------------------
225+
* ---------------------------------------------
226+
* PLEASE NOTE: This is an experimental function
227+
* ---------------------------------------------
229228
* @param string $folder_name
230229
* @param int $attributes
231230
* @param null|string $delimiter
@@ -258,11 +257,7 @@ public function getFolders($hierarchical = true, $parent_folder = null) {
258257
$this->checkConnection();
259258
$folders = [];
260259

261-
if ($hierarchical) {
262-
$pattern = $parent_folder.'%';
263-
} else {
264-
$pattern = $parent_folder.'*';
265-
}
260+
$pattern = $parent_folder.($hierarchical ? '%' : '*');
266261

267262
$items = imap_getmailboxes($this->connection, $this->getAddress(), $pattern);
268263
foreach ($items as $item) {
@@ -283,15 +278,16 @@ public function getFolders($hierarchical = true, $parent_folder = null) {
283278
/**
284279
* Open folder.
285280
*
286-
* @param Folder $folder
281+
* @param Folder $folder
282+
* @param integer $attempts
287283
*/
288-
public function openFolder(Folder $folder) {
284+
public function openFolder(Folder $folder, $attempts = 3) {
289285
$this->checkConnection();
290286

291-
if ($this->activeFolder != $folder) {
287+
if ($this->activeFolder !== $folder) {
292288
$this->activeFolder = $folder;
293289

294-
imap_reopen($this->connection, $folder->path, $this->getOptions(), 3);
290+
imap_reopen($this->connection, $folder->path, $this->getOptions(), $attempts);
295291
}
296292
}
297293

@@ -309,71 +305,51 @@ public function createFolder($name){
309305
/**
310306
* Get messages from folder.
311307
*
312-
* @param Folder $folder
313-
* @param string $criteria
308+
* @param Folder $folder
309+
* @param string $criteria
314310
* @param integer $fetch_options
311+
* @param boolean $parse_body
315312
*
316-
* @return array
313+
* @return MessageCollection
317314
* @throws GetMessagesFailedException
315+
* @throws MessageSearchValidationException
318316
*/
319-
public function getMessages(Folder $folder, $criteria = 'ALL', $fetch_options = null) {
320-
$this->checkConnection();
321-
322-
try {
323-
$this->openFolder($folder);
324-
$messages = [];
325-
$availableMessages = imap_search($this->connection, $criteria, SE_UID);
326-
327-
if ($availableMessages !== false) {
328-
$msglist = 1;
329-
foreach ($availableMessages as $msgno) {
330-
$message = new Message($msgno, $msglist, $this, $fetch_options);
331-
332-
$messages[$message->message_id] = $message;
333-
$msglist++;
334-
}
335-
}
336-
return $messages;
337-
} catch (\Exception $e) {
338-
$message = $e->getMessage();
339-
340-
throw new GetMessagesFailedException($message);
341-
}
317+
public function getMessages(Folder $folder, $criteria = 'ALL', $fetch_options = null, $parse_body = true) {
318+
return $folder->getMessages($criteria, $fetch_options, $parse_body);
342319
}
343320

344321
/**
345322
* Get all unseen messages from folder
346323
*
347324
* @param Folder $folder
348325
* @param string $criteria
349-
* @param integer $fetch_options
326+
* @param null $fetch_options
327+
* @param bool $parse_body
350328
*
351-
* @return array
329+
* @return MessageCollection
352330
* @throws GetMessagesFailedException
331+
* @throws MessageSearchValidationException
353332
*/
354-
public function getUnseenMessages(Folder $folder, $criteria = 'UNSEEN', $fetch_options = null) {
355-
$this->checkConnection();
356-
357-
try {
358-
$this->openFolder($folder);
359-
$messages = [];
360-
$availableMessages = imap_search($this->connection, $criteria, SE_UID);
361-
362-
if ($availableMessages !== false) {
363-
$msglist = 1;
364-
foreach ($availableMessages as $msgno) {
365-
$message = new Message($msgno, $msglist, $this, $fetch_options);
366-
367-
$messages[$message->message_id] = $message;
368-
$msglist++;
369-
}
370-
}
371-
return $messages;
372-
} catch (\Exception $e) {
373-
$message = $e->getMessage();
333+
public function getUnseenMessages(Folder $folder, $criteria = 'UNSEEN', $fetch_options = null, $parse_body = true) {
334+
return $folder->getUnseenMessages($criteria, $fetch_options, $parse_body);
335+
}
374336

375-
throw new GetMessagesFailedException($message);
376-
}
337+
/**
338+
* Search messages by a given search criteria
339+
*
340+
* @param array $where
341+
* @param Folder $folder
342+
* @param null $fetch_options
343+
* @param boolean $parse_body
344+
* @param string $charset
345+
*
346+
* @return MessageCollection
347+
* @throws GetMessagesFailedException
348+
* @throws MessageSearchValidationException
349+
*
350+
*/
351+
public function searchMessages(array $where, Folder $folder, $fetch_options = null, $parse_body = true, $charset = "UTF-8") {
352+
return $folder->searchMessages($where, $fetch_options, $parse_body, $charset);
377353
}
378354

379355
/**

src/IMAP/ClientManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/*
33
* File: ClientManager.php
4-
* Category: Helper
4+
* Category: -
55
* Author: M. Goldenbaum
66
* Created: 19.01.17 22:21
77
* Updated: -

src/IMAP/Folder.php

Lines changed: 178 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/*
33
* File: Folder.php
4-
* Category: Helper
4+
* Category: -
55
* Author: M. Goldenbaum
66
* Created: 19.01.17 22:21
77
* Updated: -
@@ -11,6 +11,8 @@
1111
*/
1212

1313
namespace Webklex\IMAP;
14+
use Webklex\IMAP\Exceptions\GetMessagesFailedException;
15+
use Webklex\IMAP\Exceptions\MessageSearchValidationException;
1416

1517
/**
1618
* Class Folder
@@ -141,15 +143,176 @@ public function setChildren($children = []) {
141143
}
142144

143145
/**
144-
* Get messages.
146+
* Get a specific message by UID
147+
*
148+
* @param integer $uid Please note that the uid is not unique and can change
149+
* @param integer|null $msglist
150+
*
151+
* @return Message|null
152+
*/
153+
public function getMessage($uid, $msglist = null){
154+
if(imap_msgno($this->getClient()->getConnection(), $uid) > 0){
155+
return new Message($uid, $msglist, $this->getClient());
156+
}
157+
158+
return null;
159+
}
160+
161+
/**
162+
* Get all messages
163+
*
164+
* @param string $criteria
165+
* @param null $fetch_options
166+
* @param bool $parse_body
167+
*
168+
* @return MessageCollection
169+
* @throws GetMessagesFailedException
170+
* @throws MessageSearchValidationException
171+
*/
172+
public function getMessages($criteria = 'ALL', $fetch_options = null, $parse_body = true) {
173+
return $this->searchMessages([[$criteria]], $fetch_options, $parse_body);
174+
}
175+
176+
/**
177+
* Get all unseen messages
145178
*
146179
* @param string $criteria
147-
* @param null $fetch_options
180+
* @param null $fetch_options
181+
* @param bool $parse_body
182+
*
183+
* @return MessageCollection
184+
* @throws GetMessagesFailedException
185+
* @throws MessageSearchValidationException
186+
*/
187+
public function getUnseenMessages($criteria = 'UNSEEN', $fetch_options = null, $parse_body = true) {
188+
return $this->searchMessages([[$criteria]], $fetch_options, $parse_body);
189+
}
190+
191+
/**
192+
* Search messages by a given search criteria
193+
*
194+
* @param array $where Is a two dimensional array where each array represents a criteria set:
195+
* ---------------------------------------------------------------------------------------
196+
* The following sample would search for all messages received from [email protected] or
197+
* contain the text "Hello world!":
198+
* [['FROM' => '[email protected]'],[' TEXT' => 'Hello world!']]
199+
* ---------------------------------------------------------------------------------------
200+
* The following sample would search for all messages received since march 15 2018:
201+
* [['SINCE' => Carbon::parse('15.03.2018')->format('d M y')]]
202+
* ---------------------------------------------------------------------------------------
203+
* The following sample would search for all flagged messages:
204+
* [['FLAGGED']]
205+
* ---------------------------------------------------------------------------------------
206+
* @param Folder $folder
207+
* @param null $fetch_options
208+
* @param boolean $parse_body
209+
* @param string $charset
210+
*
211+
* @return MessageCollection
212+
* @throws GetMessagesFailedException
213+
* @throws MessageSearchValidationException
214+
*
215+
* @doc http://php.net/manual/en/function.imap-search.php
216+
* imap_search() only supports IMAP2 search criterias, because the function mail_criteria() (from c-client lib)
217+
* is used in ext/imap/php_imap.c for parsing the search string.
218+
* IMAP2 search criteria is defined in RFC 1176, section "tag SEARCH search_criteria".
219+
*
220+
* https://tools.ietf.org/html/rfc1176 - INTERACTIVE MAIL ACCESS PROTOCOL - VERSION 2
221+
* https://tools.ietf.org/html/rfc1064 - INTERACTIVE MAIL ACCESS PROTOCOL - VERSION 2
222+
* https://tools.ietf.org/html/rfc822 - STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES
223+
* Date and time example from RFC822:
224+
* date-time = [ day "," ] date time ; dd mm yy
225+
* ; hh:mm:ss zzz
226+
*
227+
* day = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
228+
*
229+
* date = 1*2DIGIT month 2DIGIT ; day month year
230+
* ; e.g. 20 Jun 82
231+
*
232+
* month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
148233
*
149-
* @return \Illuminate\Support\Collection
234+
* time = hour zone ; ANSI and Military
235+
*
236+
* hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] ; 00:00:00 - 23:59:59
237+
*
238+
* zone = "UT" / "GMT" ; Universal Time
239+
* ; North American : UT
240+
* = "EST" / "EDT" ; Eastern: - 5/ - 4
241+
* = "CST" / "CDT" ; Central: - 6/ - 5
242+
* = "MST" / "MDT" ; Mountain: - 7/ - 6
243+
* = "PST" / "PDT" ; Pacific: - 8/ - 7
244+
* = 1ALPHA ; Military: Z = UT;
245+
* ; A:-1; (J not used)
246+
* ; M:-12; N:+1; Y:+12
247+
* / ( ("+" / "-") 4DIGIT ) ; Local differential
248+
* ; hours+min. (HHMM)
249+
*
250+
*/
251+
public function searchMessages(array $where, $fetch_options = null, $parse_body = true, $charset = "UTF-8") {
252+
253+
$this->getClient()->checkConnection();
254+
255+
if($this->validateWhereStatements($where)){
256+
throw new MessageSearchValidationException('Invalid imap search criteria provided');
257+
}
258+
259+
try {
260+
$this->getClient()->openFolder($this);
261+
$messages = MessageCollection::make([]);
262+
263+
$query = '';
264+
foreach($where as $statement){
265+
if(count($statement) == 1){
266+
$query .= $statement[0];
267+
}else{
268+
$query .= $statement[0].' "'.$statement[1].'"';
269+
}
270+
}
271+
272+
$availableMessages = imap_search($this->getClient()->getConnection(), $query, SE_UID, $charset);
273+
274+
if ($availableMessages !== false) {
275+
$msglist = 1;
276+
foreach ($availableMessages as $msgno) {
277+
$message = new Message($msgno, $msglist, $this->getClient(), $fetch_options, $parse_body);
278+
279+
$messages->put($message->getMessageId(), $message);
280+
$msglist++;
281+
}
282+
}
283+
284+
return $messages;
285+
} catch (\Exception $e) {
286+
$message = $e->getMessage();
287+
288+
throw new GetMessagesFailedException($message);
289+
}
290+
}
291+
292+
/**
293+
* Validate a given statement array
294+
*
295+
* @param array $statements
296+
*
297+
* @return bool
298+
*
299+
* @doc http://php.net/manual/en/function.imap-search.php
300+
* https://tools.ietf.org/html/rfc1064
301+
* https://tools.ietf.org/html/rfc822
150302
*/
151-
public function getMessages($criteria = 'ALL', $fetch_options = null) {
152-
return collect($this->client->getMessages($this, $criteria, $fetch_options));
303+
protected function validateWhereStatements($statements){
304+
foreach($statements as $statement){
305+
$criteria = $statement[0];
306+
if(in_array(explode($criteria, ' '), [
307+
'OR', 'AND',
308+
'ALL', 'ANSWERED', 'BCC', 'BEFORE', 'BODY', 'CC', 'DELETED', 'FLAGGED', 'FROM', 'KEYWORD',
309+
'NEW', 'OLD', 'ON', 'RECENT', 'SEEN', 'SINCE', 'SUBJECT', 'TEXT', 'TO',
310+
'UNANSWERED', 'UNDELETED', 'UNFLAGGED', 'UNKEYWORD', 'UNSEEN']) == false){
311+
return false;
312+
}
313+
}
314+
315+
return empty($statements) == false;
153316
}
154317

155318
/**
@@ -247,4 +410,13 @@ public function getStatus($options){
247410
public function appendMessage($message, $options = null, $internal_date = null){
248411
return imap_append($this->client->connection, $this->path, $message, $options, $internal_date);
249412
}
413+
414+
/**
415+
* Get the current Client instance
416+
*
417+
* @return Client
418+
*/
419+
public function getClient(){
420+
return $this->client;
421+
}
250422
}

0 commit comments

Comments
 (0)