diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 index eb5580b2..97eb3d4d --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,29 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip ### Affected Classes - NaN +## [1.4.5] - 2019-01-23 +### Fixed +- .csv attachement is not processed +- mail part structure property comparison changed to lowercase +- Replace helper functions for Laravel 6.0 #4 (@koenhoeijmakers) +- Date handling in Folder::appendMessage() fixed +- Carbon Exception Parse Data +- Convert sender name from non-utf8 to uf8 (@hwilok) +- Convert encoding of personal data struct + +### Added +- Path prefix option added to Client::getFolder() method +- Attachment size handling added +- Find messages by custom search criteria + +### Affected Classes +- [Query::class](src/Query/WhereQuery.php) +- [Mask::class](src/Support/Masks/Mask.php) +- [Attachment::class](src/Attachment.php) +- [Client::class](src/Client.php) +- [Folder::class](src/Folder.php) +- [Message::class](src/Message.php) + ## [1.4.2.1] - 2019-07-03 ### Fixed - Error in Attachment::__construct #3 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 4453110c..68cebd51 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Detailed [config/imap.php](src/config/imap.php) configuration: ## Usage #### Basic usage example This is a basic example, which will echo out all Mails within all imap folders -and will move every message into INBOX.read. Please be aware that this should not ben +and will move every message into INBOX.read. Please be aware that this should not be tested in real live but it gives an impression on how things work. ``` php @@ -178,6 +178,8 @@ For an easier access please take a look at the new config option `imap.options.d method takes three options: the required (string) $folder_name and two optional variables. An integer $attributes which seems to be sometimes 32 or 64 (I honestly have no clue what this number does, so feel free to enlighten me and anyone else) and a delimiter which if it isn't set will use the default option configured inside the [config/imap.php](src/config/imap.php) file. +> If you are using Exchange you might want to set all parameter and the last `$prefix_address` to `false` e.g. `$oClient->getFolder('name', 32, null, false)` #234 + ``` php /** @var \Webklex\PHPIMAP\Client $oClient */ @@ -244,6 +246,10 @@ $aMessage = $oFolder->query() $aMessage = $oFolder->query()->notText('hello world')->get(); $aMessage = $oFolder->query()->not_text('hello world')->get(); $aMessage = $oFolder->query()->not()->text('hello world')->get(); + +//Get all messages by custom search criteria +/** @var \Webklex\PHPIMAP\Support\MessageCollection $aMessage */ +$aMessage = $oFolder->query()->where(["CUSTOM_FOOBAR" => "fooBar"]])->get(); ``` Available search aliases for a better code reading: @@ -298,7 +304,7 @@ Limiting the request emails: ``` php /** @var \Webklex\PHPIMAP\Folder $oFolder */ -//Get all messages for page 2 since march 15 2018 where each apge contains 10 messages +//Get all messages for page 2 since march 15 2018 where each page contains 10 messages /** @var \Webklex\PHPIMAP\Support\MessageCollection $aMessage */ $aMessage = $oFolder->query()->since('15.03.2018')->limit(10, 2)->get(); ``` @@ -564,7 +570,7 @@ if you're just wishing a feature ;) | checkConnection | | | Determine if connection was established and connect if not. | | connect | int $attempts | | Connect to server. | | disconnect | | | Disconnect from server. | -| getFolder | string $folder_name, int $attributes = 32, int or null $delimiter | Folder | Get a Folder instance by name | +| getFolder | string $folder_name, int $attributes = 32, int or null $delimiter, bool $prefix_address | Folder | Get a Folder instance by name | | getFolders | bool $hierarchical, string or null $parent_folder | FolderCollection | Get folders list. If hierarchical order is set to true, it will make a tree of folders, otherwise it will return flat array. | | openFolder | string or Folder $folder, integer $attempts | | Open a given folder. | | createFolder | string $name | boolean | Create a new folder. | @@ -712,7 +718,11 @@ if you're just wishing a feature ;) | getContent | | string or null | Get attachment content | | getMimeType | | string or null | Get attachment mime type | | getExtension | | string or null | Get a guessed attachment extension | +| getId | | string or null | Get attachment id | | getName | | string or null | Get attachment name | +| getPartNumber | | int or null | Get attachment part number | +| getContent | | string or null | Get attachment content | +| setSize | | int or null | Get attachment size | | getType | | string or null | Get attachment type | | getDisposition | | string or null | Get attachment disposition | | getContentType | | string or null | Get attachment content type | @@ -785,6 +795,8 @@ Extends [Illuminate\Support\Collection::class](https://laravel.com/api/5.4/Illum | DateTime::__construct(): Failed to parse time string (...) | Please report any new invalid timestamps to [#45](https://github.com/Webklex/php-imap/issues/45) | | imap_open(): Couldn't open (...) Please log in your web browser: (...) | In order to use IMAP on some services (such as Gmail) you need to enable it first. [Google help page]( https://support.google.com/mail/answer/7126229?hl=en) | | imap_headerinfo(): Bad message number | This happens if no Message number is available. Please make sure Message::parseHeader() has run before | +| imap_fetchheader(): Bad message number | Try to change the `message_key` [#243](https://github.com/Webklex/laravel-imap/issues/243) + ## Milestones & upcoming features * Wiki!! diff --git a/src/Attachment.php b/src/Attachment.php old mode 100644 new mode 100755 index 8c9f7052..1996fb61 --- a/src/Attachment.php +++ b/src/Attachment.php @@ -12,6 +12,7 @@ namespace Webklex\PHPIMAP; +use Illuminate\Support\Str; use Illuminate\Support\Facades\File; use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; use Webklex\PHPIMAP\Exceptions\MaskNotFoundException; @@ -24,6 +25,7 @@ * @package Webklex\PHPIMAP * * @property integer part_number + * @property integer size * @property string content * @property string type * @property string content_type @@ -42,6 +44,8 @@ * @method string setContentType(string $content_type) * @method string getId() * @method string setId(string $id) + * @method string getSize() + * @method string setSize(integer $size) * @method string getName() * @method string getDisposition() * @method string setDisposition(string $disposition) @@ -68,6 +72,7 @@ class Attachment { 'name' => null, 'disposition' => null, 'img_src' => null, + 'size' => null, ]; /** @@ -111,7 +116,7 @@ public function __construct(Message $oMessage, $structure, $part_number = 1) { */ public function __call($method, $arguments) { if(strtolower(substr($method, 0, 3)) === 'get') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); if(isset($this->attributes[$name])) { return $this->attributes[$name]; @@ -119,7 +124,7 @@ public function __call($method, $arguments) { return null; }elseif (strtolower(substr($method, 0, 3)) === 'set') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); $this->attributes[$name] = array_pop($arguments); @@ -205,6 +210,10 @@ protected function fetch() { $this->id = str_replace(['<', '>'], '', $this->structure->id); } + if (property_exists($this->structure, 'bytes')) { + $this->size = $this->structure->bytes; + } + if (property_exists($this->structure, 'dparameters')) { foreach ($this->structure->dparameters as $parameter) { if (strtolower($parameter->attribute) == "filename") { diff --git a/src/Client.php b/src/Client.php old mode 100644 new mode 100755 index a401e0f5..43c28b0a --- a/src/Client.php +++ b/src/Client.php @@ -325,15 +325,18 @@ public function disconnect() { * @param string $folder_name * @param int $attributes * @param null|string $delimiter + * @param boolean $prefix_address * * @return Folder */ - public function getFolder($folder_name, $attributes = 32, $delimiter = null) { + public function getFolder($folder_name, $attributes = 32, $delimiter = null, $prefix_address = true) { $delimiter = $delimiter === null ? ClientManager::get('imap.options.delimiter', '/') : $delimiter; + $folder_name = $prefix_address ? $this->getAddress().$folder_name : $folder_name; + $oFolder = new Folder($this, (object) [ - 'name' => $this->getAddress().$folder_name, + 'name' => $folder_name, 'attributes' => $attributes, 'delimiter' => $delimiter ]); diff --git a/src/Folder.php b/src/Folder.php old mode 100644 new mode 100755 index 386c5cb4..c4639ac6 --- a/src/Folder.php +++ b/src/Folder.php @@ -12,6 +12,7 @@ namespace Webklex\PHPIMAP; +use Carbon\Carbon; use Webklex\PHPIMAP\Exceptions\GetMessagesFailedException; use Webklex\PHPIMAP\Exceptions\MessageSearchValidationException; use Webklex\PHPIMAP\Query\WhereQuery; @@ -426,7 +427,20 @@ public function getStatus($options) { * @throws Exceptions\ConnectionFailedException */ public function appendMessage($message, $options = null, $internal_date = null) { - return imap_append($this->client->getConnection(), $this->path, $message, $options, $internal_date); + /** + * Check if $internal_date is parsed. If it is null it should not be set. Otherwise the message can't be stored. + * If this parameter is set, it will set the INTERNALDATE on the appended message. The parameter should be a + * date string that conforms to the rfc2060 specifications for a date_time value or be a Carbon object. + */ + + if ($internal_date != null) { + if ($internal_date instanceof Carbon){ + $internal_date = $internal_date->format('d-M-Y H:i:s O'); + } + return imap_append($this->client->getConnection(), $this->path, $message, $options, $internal_date); + } + + return imap_append($this->client->getConnection(), $this->path, $message, $options); } /** diff --git a/src/Message.php b/src/Message.php old mode 100644 new mode 100755 index b54d326c..2b3cd59b --- a/src/Message.php +++ b/src/Message.php @@ -19,6 +19,7 @@ use Webklex\PHPIMAP\Support\AttachmentCollection; use Webklex\PHPIMAP\Support\FlagCollection; use Webklex\PHPIMAP\Support\Masks\MessageMask; +use Illuminate\Support\Str; /** * Class Message @@ -239,14 +240,14 @@ public function __construct($uid, $msglist, Client $client, $fetch_options = nul */ public function __call($method, $arguments) { if(strtolower(substr($method, 0, 3)) === 'get') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); if(in_array($name, array_keys($this->attributes))) { return $this->attributes[$name]; } }elseif (strtolower(substr($method, 0, 3)) === 'set') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); if(in_array($name, array_keys($this->attributes))) { $this->attributes[$name] = array_pop($arguments); @@ -499,6 +500,7 @@ private function parseDate($header) { case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: $date .= 'C'; break; + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ \+[0-9]{2,4}\ \(\+[0-9]{1,2}\))+$/i', $date) > 0: case preg_match('/([A-Z]{2,3}[\,|\ \,]\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}.*)+$/i', $date) > 0: case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: case preg_match('/([A-Z]{2,3}\, \ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: @@ -601,12 +603,18 @@ private function parseAddresses($list) { if (!property_exists($address, 'personal')) { $address->personal = false; } + if (!property_exists($address, 'personal')) { + $address->personal = false; + } else { + $personalParts = imap_mime_header_decode($address->personal); - $personalParts = imap_mime_header_decode($address->personal); - - $address->personal = ''; - foreach ($personalParts as $p) { - $address->personal .= $p->text; + if(is_array($personalParts)) { + $address->personal = ''; + foreach ($personalParts as $p) { + $encoding = $this->getEncoding($p->text); + $address->personal .= $this->convertEncoding($p->text, $encoding); + } + } } $address->mail = ($address->mailbox && $address->host) ? $address->mailbox.'@'.$address->host : false; @@ -663,10 +671,10 @@ private function fetchStructure($structure, $partNumber = null) { if ($structure->type == IMAP::MESSAGE_TYPE_TEXT && ($structure->ifdisposition == 0 || - ($structure->ifdisposition == 1 && !isset($structure->parts) && $partNumber != null) + (empty($structure->disposition) || strtolower($structure->disposition) != 'attachment') ) ) { - if ($structure->subtype == "PLAIN") { + if (strtolower($structure->subtype) == "plain" || strtolower($structure->subtype) == "csv") { if (!$partNumber) { $partNumber = 1; } @@ -699,7 +707,7 @@ private function fetchStructure($structure, $partNumber = null) { $this->fetchAttachment($structure, $partNumber); - } elseif ($structure->subtype == "HTML") { + } elseif (strtolower($structure->subtype) == "html") { if (!$partNumber) { $partNumber = 1; } @@ -717,6 +725,10 @@ private function fetchStructure($structure, $partNumber = null) { $body->content = $content; $this->bodies['html'] = $body; + } elseif ($structure->ifdisposition == 1 && strtolower($structure->disposition) == 'attachment') { + if ($this->getFetchAttachmentOption() === true) { + $this->fetchAttachment($structure, $partNumber); + } } } elseif ($structure->type == IMAP::MESSAGE_TYPE_MULTIPART) { foreach ($structure->parts as $index => $subStruct) { diff --git a/src/Query/WhereQuery.php b/src/Query/WhereQuery.php old mode 100644 new mode 100755 index bb53c314..54a5add3 --- a/src/Query/WhereQuery.php +++ b/src/Query/WhereQuery.php @@ -12,6 +12,7 @@ namespace Webklex\PHPIMAP\Query; +use Illuminate\Support\Str; use Webklex\PHPIMAP\Exceptions\InvalidWhereQueryCriteriaException; use Webklex\PHPIMAP\Exceptions\MethodNotFoundException; use Webklex\PHPIMAP\Exceptions\MessageSearchValidationException; @@ -72,7 +73,7 @@ class WhereQuery extends Query { public function __call($name, $arguments) { $that = $this; - $name = camel_case($name); + $name = Str::camel($name); if(strtolower(substr($name, 0, 3)) === 'not') { $that = $that->whereNot(); @@ -97,6 +98,9 @@ public function __call($name, $arguments) { protected function validate_criteria($criteria) { $criteria = strtoupper($criteria); + if (substr($criteria, 0, 6) === "CUSTOM") { + return substr($criteria, 6); + } if(in_array($criteria, $this->available_criteria) === false) { throw new InvalidWhereQueryCriteriaException(); } diff --git a/src/Support/Masks/Mask.php b/src/Support/Masks/Mask.php old mode 100644 new mode 100755 index 50afe262..f679d090 --- a/src/Support/Masks/Mask.php +++ b/src/Support/Masks/Mask.php @@ -12,6 +12,7 @@ namespace Webklex\PHPIMAP\Support\Masks; +use Illuminate\Support\Str; use Webklex\PHPIMAP\Exceptions\MethodNotFoundException; /** @@ -60,14 +61,14 @@ protected function boot(){} */ public function __call($method, $arguments) { if(strtolower(substr($method, 0, 3)) === 'get') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); if(isset($this->attributes[$name])) { return $this->attributes[$name]; } }elseif (strtolower(substr($method, 0, 3)) === 'set') { - $name = snake_case(substr($method, 3)); + $name = Str::snake(substr($method, 3)); if(isset($this->attributes[$name])) { $this->attributes[$name] = array_pop($arguments);