Webklex / php-imap

PHP-IMAP is a wrapper for common IMAP communication without the need to have the php-imap module installed / enabled. The protocol is completely integrated and therefore supports IMAP IDLE operation and the "new" oAuth authentication process as well.
https://www.php-imap.com
MIT License
321 stars 151 forks source link

leaveUnread() does not work if an exception happens during fetching #507

Open freescout-help opened 4 months ago

freescout-help commented 4 months ago

One user reported an issue https://github.com/freescout-help-desk/freescout/issues/4159. If an error occurs during fetching the email in the mailbox stays "read" even though leaveUnread() is set.

Here is the example of headers which can be used to reproduce the issue:

X-Mailer: CodeIgniter
Date: Thu, 1 Aug 2024 10:55:28 +0100
    Precedence: bulk
    Auto-Submitted: auto-replied
X-Priority: 1 (Highest)

We've found out that the following line marks an email as "read": https://github.com/Webklex/php-imap/blob/master/src/Query/Query.php#L246

if ($this->getFetchBody()) {
    $contents = $this->client->getConnection()->content($uids, "RFC822", $this->sequence)->validatedData();
}

So apparently somewhere in the code there should a place marking an email as "unread" if leaveUnread() is set. Is it so? If yes, can someone help to find that place in order to try to find a solution.

freescout-help commented 4 months ago

It looks like messages are marked as "unread" here: https://github.com/Webklex/php-imap/blob/master/src/Message.php#L300

freescout-help commented 4 months ago

Here is the fix for Query->populate() function https://github.com/Webklex/php-imap/blob/master/src/Query/Query.php#L339C24-L339C32:

+       $exception = null;
        foreach ($raw_messages["headers"] as $uid => $header) {
            $content = $raw_messages["contents"][$uid] ?? "";
            $flag = $raw_messages["flags"][$uid] ?? [];
            $extensions = $raw_messages["extensions"][$uid] ?? [];

+       try {
                 $message = $this->make($uid, $msglist, $header, $content, $flag);
+           } catch (\Exception $e) {
+                $exception = $e;
+                continue;
+           }
            foreach ($extensions as $key => $extension) {
                $message->getHeader()->set($key, $extension);
            }
            if ($message !== null) {
                $key = $this->getMessageKey($message_key, $msglist, $message);
                $messages->put("$key", $message);
            }
            $msglist++;
        }

+       if ($exception) {
+           throw $exception;
+       }

It's quite an important thing as if someone fetches only unread emails (like us) and an exception happens for some email in a bunch all other emails will stay marked as "unread" and will be unprocessed.

freescout-help commented 4 months ago

And here is the fix allowing to leave emails unread even if "Allowed memory size exhausted" fatal error occurs:

https://github.com/freescout-help-desk/freescout/commit/b9391d87dc0c7f5e10913f629646435d7ee07e8c