barbushin / php-imap

Manage mailboxes, filter/get/delete emails in PHP (supports IMAP/POP3/NNTP)
MIT License
1.65k stars 459 forks source link

[BUG] For some emails, the body is empty #712

Open tsschulz opened 12 months ago

tsschulz commented 12 months ago

Environment (please complete the following information):

For some emails (seems like when they are send from Mac), the body is empty, for html and also for plain.

The used code:

$this->mail->textHtml ?? $this->mail->textPlain;

var_dump($this->mail):

object(PhpImap\IncomingMail)#7 (45) {
  ["id"]=>
  int(133)
  ["imapPath"]=>...
  }
  ["textPlain":"PhpImap\IncomingMail":private]=>
  NULL
  ["textHtml":"PhpImap\IncomingMail":private]=>
  NULL
}

Expected behavior Email body has text in it, because in email client for the same email there is a body

bducha commented 11 months ago

I had a similar problem and I found the cause of it. Maybe it's the same thing for you :

If you look in the IncomingMail class, you can see that acessing to the textHtml and textPlain properties is done via the "magic" __get method. If you look at it, you can see that the properties are nerver initialized until you access them. This will trigger the $data->fetch() and store the result into the properties :

/**
     * __get() is utilized for reading data from inaccessible (protected
     * or private) or non-existing properties.
     *
     * @param string $name Name of the property (eg. textPlain)
     *
     * @return string Value of the property (eg. Plain text message)
     */
    public function __get(string $name): string
    {
        $type = false;
        if ('textPlain' == $name) {
            $type = DataPartInfo::TEXT_PLAIN;
        }
        if ('textHtml' == $name) {
            $type = DataPartInfo::TEXT_HTML;
        }
        if (('textPlain' === $name || 'textHtml' === $name) && isset($this->$name)) {
            return (string) $this->$name;
        }
        if (false === $type) {
            \trigger_error("Undefined property: IncomingMail::$name");
        }
        if (!isset($this->$name)) {
            $this->$name = '';
        }
        foreach ($this->dataInfo[$type] as $data) {
            $this->$name .= \trim($data->fetch());
        }

        /** @var string */
        return $this->$name;
    } 

The thing is, if you var_dump the IncomingMail just after retrieving it, the textHtml and textPlain will be empty, because the __get() was never called :

$mail = $mailbox->getMail($mailId, $markAsSeen);
var_dump($mail); // textHtml and textPlain are null

But if you access the properties before printing the mail object, they will be defined :


$mail = $mailbox->getMail($mailId, $markAsSeen);
$textHtml = $mail->textHtml;
$textPlain = $mail->textPlain;
var_dump($mail); // textHtml and textPlain are defined with my mail body

And in my case, what was happening is that I would get all my emails and store them in an array for later, then I was iterating over multiple mailboxes, which reopened the imap stream to other mailboxes. And then when I finally finished iterating over all the mailboxes, I would try to read the textHtml/textPlain values but they where null, probably because the imap stream was already closed.

So accessing the two properties at least once just after retrieving the email did the trick for me. I find this a bit sketchy if you ask me 😅

I hope this solves your problem !

tsschulz commented 11 months ago

Thank you for your answer :) I tried it, but still have the same problem. And it is not on all emails, but only the ones that are send from MacBooks. From other email-clients, it is working fine.

But I'll have a look into the implementation too when I find the time for it.

voicecode-bv commented 8 months ago

I can confirm, I'm having the same issue too. Unfortunately @bducha's suggestion didn't work for me neither.