<?php
/**
 * Helper class for imap access
 *
 * @package    protocols
 * @copyright  Copyright (c) Tobias Zeising (http://www.aditu.de)
 * @license    GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
 * @author     Tobias Zeising <tobias.zeising@aditu.de>
 */
class Imap
{
    private static $charsetAliases = [
        '5601'                     => 'EUC-KR',
        '646'                      => 'us-ascii',
        '850'                      => 'IBM850',
        '852'                      => 'IBM852',
        '855'                      => 'IBM855',
        '857'                      => 'IBM857',
        '862'                      => 'IBM862',
        '864'                      => 'IBM864',
        '864i'                     => 'IBM864i',
        '866'                      => 'IBM866',
        'ansi-1251'                => 'windows-1251',
        'ansi_x3.4-1968'           => 'us-ascii',
        'arabic'                   => 'ISO-8859-6',
        'ascii'                    => 'us-ascii',
        'asmo-708'                 => 'ISO-8859-6',
        'chinese'                  => 'GB2312',
        'cns11643'                 => 'x-euc-tw',
        'cp-866'                   => 'IBM866',
        'cp1250'                   => 'windows-1250',
        'cp1251'                   => 'windows-1251',
        'cp1252'                   => 'windows-1252',
        'cp1253'                   => 'windows-1253',
        'cp1254'                   => 'windows-1254',
        'cp1255'                   => 'windows-1255',
        'cp1256'                   => 'windows-1256',
        'cp1257'                   => 'windows-1257',
        'cp1258'                   => 'windows-1258',
        'cp819'                    => 'ISO-8859-1',
        'cp850'                    => 'IBM850',
        'cp852'                    => 'IBM852',
        'cp855'                    => 'IBM855',
        'cp857'                    => 'IBM857',
        'cp862'                    => 'IBM862',
        'cp864'                    => 'IBM864',
        'cp864i'                   => 'IBM864i',
        'cp866'                    => 'IBM866',
        'cp932'                    => 'Shift_JIS',
        'csIBM850'                 => 'IBM850',
        'csIBM852'                 => 'IBM852',
        'csIBM855'                 => 'IBM855',
        'csIBM857'                 => 'IBM857',
        'csIBM862'                 => 'IBM862',
        'csIBM864'                 => 'IBM864',
        'csIBM866'                 => 'IBM866',
        'csMacintosh'              => 'x-mac-roman',
        'csbig5'                   => 'Big5',
        'cseucjpkdfmtjapanese'     => 'EUC-JP',
        'csgb2312'                 => 'GB2312',
        'csibm864i'                => 'IBM864i',
        'csiso103t618bit'          => 'T.61-8bit',
        'csiso111ecmacyrillic'     => 'ISO-IR-111',
        'csiso2022jp'              => 'ISO-2022-JP',
        'csiso2022jp2'             => 'ISO-2022-JP',
        'csiso2022kr'              => 'ISO-2022-KR',
        'csiso58gb231280'          => 'GB2312',
        'csiso88596e'              => 'ISO-8859-6-E',
        'csiso88596i'              => 'ISO-8859-6-I',
        'csiso88598e'              => 'ISO-8859-8-E',
        'csiso88598i'              => 'ISO-8859-8-I',
        'csisolatin1'              => 'ISO-8859-1',
        'csisolatin2'              => 'ISO-8859-2',
        'csisolatin3'              => 'ISO-8859-3',
        'csisolatin4'              => 'ISO-8859-4',
        'csisolatin5'              => 'ISO-8859-9',
        'csisolatin6'              => 'ISO-8859-10',
        'csisolatinarabic'         => 'ISO-8859-6',
        'csisolatincyrillic'       => 'ISO-8859-5',
        'csisolatingreek'          => 'ISO-8859-7',
        'csisolatinhebrew'         => 'ISO-8859-8',
        'csksc56011987'            => 'EUC-KR',
        'csshiftjis'               => 'Shift_JIS',
        'csueckr'                  => 'EUC-KR',
        'csunicode'                => 'UTF-16BE',
        'csunicode11'              => 'UTF-16BE',
        'csunicode11utf7'          => 'UTF-7',
        'csunicodeascii'           => 'UTF-16BE',
        'csunicodelatin1'          => 'UTF-16BE',
        'csviqr'                   => 'VIQR',
        'csviscii'                 => 'VISCII',
        'cyrillic'                 => 'ISO-8859-5',
        'ecma-114'                 => 'ISO-8859-6',
        'ecma-118'                 => 'ISO-8859-7',
        'ecma-cyrillic'            => 'ISO-IR-111',
        'elot_928'                 => 'ISO-8859-7',
        'gb_2312'                  => 'GB2312',
        'gb_2312-80'               => 'GB2312',
        'gbk'                      => 'x-gbk',
        'greek'                    => 'ISO-8859-7',
        'greek8'                   => 'ISO-8859-7',
        'hebrew'                   => 'ISO-8859-8',
        'ibm-864'                  => 'IBM864',
        'ibm-864i'                 => 'IBM864i',
        'ibm819'                   => 'ISO-8859-1',
        'ibm874'                   => 'windows-874',
        'iso-10646'                => 'UTF-16BE',
        'iso-10646-j-1'            => 'UTF-16BE',
        'iso-10646-ucs-2'          => 'UTF-16BE',
        'iso-10646-ucs-4'          => 'UTF-32BE',
        'iso-10646-ucs-basic'      => 'UTF-16BE',
        'iso-10646-unicode-latin1' => 'UTF-16BE',
        'iso-2022-cn-ext'          => 'ISO-2022-CN',
        'iso-2022-jp-2'            => 'ISO-2022-JP',
        'iso-8859-8i'              => 'ISO-8859-8-I',
        'iso-ir-100'               => 'ISO-8859-1',
        'iso-ir-101'               => 'ISO-8859-2',
        'iso-ir-103'               => 'T.61-8bit',
        'iso-ir-109'               => 'ISO-8859-3',
        'iso-ir-110'               => 'ISO-8859-4',
        'iso-ir-126'               => 'ISO-8859-7',
        'iso-ir-127'               => 'ISO-8859-6',
        'iso-ir-138'               => 'ISO-8859-8',
        'iso-ir-144'               => 'ISO-8859-5',
        'iso-ir-148'               => 'ISO-8859-9',
        'iso-ir-149'               => 'EUC-KR',
        'iso-ir-157'               => 'ISO-8859-10',
        'iso-ir-58'                => 'GB2312',
        'iso8859-1'                => 'ISO-8859-1',
        'iso8859-10'               => 'ISO-8859-10',
        'iso8859-11'               => 'ISO-8859-11',
        'iso8859-13'               => 'ISO-8859-13',
        'iso8859-14'               => 'ISO-8859-14',
        'iso8859-15'               => 'ISO-8859-15',
        'iso8859-2'                => 'ISO-8859-2',
        'iso8859-3'                => 'ISO-8859-3',
        'iso8859-4'                => 'ISO-8859-4',
        'iso8859-5'                => 'ISO-8859-5',
        'iso8859-6'                => 'ISO-8859-6',
        'iso8859-7'                => 'ISO-8859-7',
        'iso8859-8'                => 'ISO-8859-8',
        'iso8859-9'                => 'ISO-8859-9',
        'iso88591'                 => 'ISO-8859-1',
        'iso885910'                => 'ISO-8859-10',
        'iso885911'                => 'ISO-8859-11',
        'iso885912'                => 'ISO-8859-12',
        'iso885913'                => 'ISO-8859-13',
        'iso885914'                => 'ISO-8859-14',
        'iso885915'                => 'ISO-8859-15',
        'iso88592'                 => 'ISO-8859-2',
        'iso88593'                 => 'ISO-8859-3',
        'iso88594'                 => 'ISO-8859-4',
        'iso88595'                 => 'ISO-8859-5',
        'iso88596'                 => 'ISO-8859-6',
        'iso88597'                 => 'ISO-8859-7',
        'iso88598'                 => 'ISO-8859-8',
        'iso88599'                 => 'ISO-8859-9',
        'iso_8859-1'               => 'ISO-8859-1',
        'iso_8859-15'              => 'ISO-8859-15',
        'iso_8859-2'               => 'ISO-8859-2',
        'iso_8859-3'               => 'ISO-8859-3',
        'iso_8859-4'               => 'ISO-8859-4',
        'iso_8859-5'               => 'ISO-8859-5',
        'iso_8859-6'               => 'ISO-8859-6',
        'iso_8859-7'               => 'ISO-8859-7',
        'iso_8859-8'               => 'ISO-8859-8',
        'iso_8859-9'               => 'ISO-8859-9',
        'korean'                   => 'EUC-KR',
        'ks_c_5601-1987'           => 'EUC-KR',
        'ks_c_5601-1989'           => 'EUC-KR',
        'ksc5601'                  => 'EUC-KR',
        'ksc_5601'                 => 'EUC-KR',
        'l1'                       => 'ISO-8859-1',
        'l2'                       => 'ISO-8859-2',
        'l3'                       => 'ISO-8859-3',
        'l4'                       => 'ISO-8859-4',
        'l5'                       => 'ISO-8859-9',
        'l6'                       => 'ISO-8859-10',
        'latin1'                   => 'ISO-8859-1',
        'latin2'                   => 'ISO-8859-2',
        'latin3'                   => 'ISO-8859-3',
        'latin4'                   => 'ISO-8859-4',
        'latin5'                   => 'ISO-8859-9',
        'latin6'                   => 'ISO-8859-10',
        'mac'                      => 'x-mac-roman',
        'macintosh'                => 'x-mac-roman',
        'ms_kanji'                 => 'Shift_JIS',
        'shift-jis'                => 'Shift_JIS',
        'sun_eu_greek'             => 'ISO-8859-7',
        't.61'                     => 'T.61-8bit',
        'tis620'                   => 'TIS-620',
        'unicode-1-1-utf-7'        => 'UTF-7',
        'unicode-1-1-utf-8'        => 'UTF-8',
        'unicode-2-0-utf-7'        => 'UTF-7',
        'visual'                   => 'ISO-8859-8',
        'windows-31j'              => 'Shift_JIS',
        'x-cp1250'                 => 'windows-1250',
        'x-cp1251'                 => 'windows-1251',
        'x-cp1252'                 => 'windows-1252',
        'x-cp1253'                 => 'windows-1253',
        'x-cp1254'                 => 'windows-1254',
        'x-cp1255'                 => 'windows-1255',
        'x-cp1256'                 => 'windows-1256',
        'x-cp1257'                 => 'windows-1257',
        'x-cp1258'                 => 'windows-1258',
        'x-euc-jp'                 => 'EUC-JP',
        'x-iso-10646-ucs-2-be'     => 'UTF-16BE',
        'x-iso-10646-ucs-2-le'     => 'UTF-16LE',
        'x-iso-10646-ucs-4-be'     => 'UTF-32BE',
        'x-iso-10646-ucs-4-le'     => 'UTF-32LE',
        'x-sjis'                   => 'Shift_JIS',
        'x-unicode-2-0-utf-7'      => 'UTF-7',
        'x-x-big5'                 => 'Big5',
        'zh_cn.euc'                => 'GB2312',
        'zh_tw-big5'               => 'Big5',
        'zh_tw-euc'                => 'x-euc-tw',
    ];

    private $attachments = [];

    /**
     * imap connection
     */
    protected $imap = false;

    /**
     * mailbox url string
     */
    protected $mailbox = '';

    /**
     * currentfolder
     */
    protected $folder = 'Inbox';

    /**
     * initialize imap helper
     *
     * @return void
     * @param $mailbox imap_open string
     * @param $username
     * @param $password
     * @param $encryption ssl or tls
     */
    public function __construct($mailbox, $username, $password, $encryption = false)
    {
        $enc = '';
        if ($encryption != null && isset($encryption) && $encryption == 'ssl') {
            $enc = '/imap/ssl/novalidate-cert';
        } elseif ($encryption != null && isset($encryption) && $encryption == 'tls') {
            $enc = '/imap/tls/novalidate-cert';
        }
        $this->mailbox = '{' . $mailbox . $enc . '}';
        $this->imap    = @imap_open($this->mailbox, $username, $password);
    }

    /**
     * close connection
     */
    public function __destruct()
    {
        if ($this->imap !== false) {
            imap_close($this->imap);
        }
    }

    /**
     * returns true after successfull connection
     *
     * @return bool true on success
     */
    public function isConnected()
    {
        return $this->imap !== false;
    }

    /**
     * returns last imap error
     *
     * @return string error message
     */
    public function getError()
    {
        return imap_last_error();
    }

    /**
     * select given folder
     *
     * @return bool successfull opened folder
     * @param $folder name
     */
    public function selectFolder($folder)
    {
        $result = imap_reopen($this->imap, $this->mailbox . $folder);
        if ($result === true) {
            $this->folder = $folder;
        }

        return $result;
    }

    /**
     * returns all available folders
     *
     * @return array with foldernames
     */
    public function getFolders()
    {
        $folders = imap_list($this->imap, $this->mailbox, '*');

        return str_replace($this->mailbox, '', $folders);
    }

    /**
     * returns the number of messages in the current folder
     *
     * @return int message count
     */
    public function countMessages()
    {
        return imap_num_msg($this->imap);
    }

    /**
     * returns the number of unread messages in the current folder
     *
     * @return int message count
     */
    public function countUnreadMessages()
    {
        $result = imap_search($this->imap, 'UNSEEN');
        if ($result === false) {
            return 0;
        }

        return count($result);
    }

    /**
     * returns unseen emails in the current folder
     *
     * @return array messages
     * @param $withbody without body
     */
    public function getUnreadMessages($withbody = true)
    {
        $emails = [];
        $result = imap_search($this->imap, 'UNSEEN');
        if ($result) {
            foreach ($result as $k => $i) {
                $emails[] = $this->formatMessage($i, $withbody);
            }
        }

        return $emails;
    }

    /**
     * returns all emails in the current folder
     *
     * @return array messages
     * @param $withbody without body
     */
    public function getMessages($withbody = true)
    {
        $count  = $this->countMessages();
        $emails = [];
        for ($i = 1; $i <= $count; $i++) {
            $emails[] = $this->formatMessage($i, $withbody);
        }
        // sort emails descending by date
        // usort($emails, function($a, $b) {
        // try {
        // $datea = new \DateTime($a['date']);
        // $dateb = new \DateTime($b['date']);
        // } catch(\Exception $e) {
        // return 0;
        // }
        // if ($datea == $dateb)
        // return 0;
        // return $datea < $dateb ? 1 : -1;
        // });
        return $emails;
    }

    /**
     * returns email by given id
     *
     * @return array messages
     * @param $id
     * @param $withbody without body
     */
    public function getMessage($id, $withbody = true)
    {
        return $this->formatMessage($id, $withbody);
    }

    /**
     * @param $id
     * @param bool $withbody
     * @return array
     */
    protected function formatMessage($id, $withbody = true)
    {
        $header = imap_headerinfo($this->imap, $id);
        // fetch unique uid
        $uid = imap_uid($this->imap, $id);

        // get email data
        $subject = '';
        $charset = '';
        if (isset($header->subject) && strlen($header->subject) > 0) {
            foreach (imap_mime_header_decode($header->subject) as $obj) {
                $charset .= $obj->charset;
                $subject .= $obj->text;
            }
        }
        $subject = $this->convertToUtf8($subject, $charset);
        $email   = [
        'to'       => isset($header->to) ? $this->arrayToAddress($header->to) : '',
        'from'     => $this->toAddress($header->from[0]),
        'date'     => $header->date,
        'subject'  => $subject,
        'uid'      => $uid,
        'unread'   => strlen(trim($header->Unseen)) > 0,
        'answered' => strlen(trim($header->Answered)) > 0,
        'deleted'  => strlen(trim($header->Deleted)) > 0,
    ];
        if (isset($header->cc)) {
            $email['cc'] = $this->arrayToAddress($header->cc);
        }
        // get email body
        if ($withbody === true) {
            $body          = $this->getBody($uid);
            $email['body'] = $body['body'];
            $email['html'] = $body['html'];
        }
        // get attachments
        $mailStruct = imap_fetchstructure($this->imap, $id);

        $this->getAttachments($this->imap, $id, $uid, $mailStruct, '');
        $attachments = isset($this->attachments[$uid]) ? $this->attachments2name($this->attachments[$uid]) : [];

        if (count($attachments) > 0) {
            foreach ($attachments as $val) {
                foreach ($val as $k => $t) {
                    if ($k == 'name') {
                        $decodedName = imap_mime_header_decode($t);
                        $t           = $this->convertToUtf8($decodedName[0]->text, $decodedName[0]->charset);
                    }
                    $arr[$k] = $t;
                }
                $email['attachments'][] = $arr;
            }
        }

        return $email;
    }

    /**
     * delete given message
     *
     * @return bool success or not
     * @param $id of the message
     */
    public function deleteMessage($id)
    {
        return $this->deleteMessages([$id]);
    }

    /**
     * delete messages
     *
     * @return bool success or not
     * @param $ids array of ids
     */
    public function deleteMessages($ids)
    {
        if (imap_mail_move($this->imap, implode(',', $ids), $this->getTrash(), CP_UID) == false) {
            return false;
        }

        return imap_expunge($this->imap);
    }

    /**
     * move given message in new folder
     *
     * @return bool success or not
     * @param $id of the message
     * @param $target new folder
     */
    public function moveMessage($id, $target)
    {
        return $this->moveMessages([$id], $target);
    }

    /**
     * move given message in new folder
     *
     * @return bool success or not
     * @param $ids array of message ids
     * @param $target new folder
     */
    public function moveMessages($ids, $target)
    {
        if (imap_mail_move($this->imap, implode(',', $ids), $target, CP_UID) === false) {
            return false;
        }

        return imap_expunge($this->imap);
    }

    /**
     * mark message as read
     *
     * @return bool success or not
     * @param $id of the message
     * @param $seen true = message is read, false = message is unread
     */
    public function setUnseenMessage($id, $seen = true)
    {
        $header = $this->getMessageHeader($id);
        if ($header == false) {
            return false;
        }

        $flags = '';
        $flags .= (strlen(trim($header->Answered)) > 0 ? '\\Answered ' : '');
        $flags .= (strlen(trim($header->Flagged)) > 0 ? '\\Flagged ' : '');
        $flags .= (strlen(trim($header->Deleted)) > 0 ? '\\Deleted ' : '');
        $flags .= (strlen(trim($header->Draft)) > 0 ? '\\Draft ' : '');

        $flags .= (($seen == true) ? '\\Seen ' : ' ');
        //echo "\n<br />".$id.": ".$flags;
        imap_clearflag_full($this->imap, $id, '\\Seen', ST_UID);

        return imap_setflag_full($this->imap, $id, trim($flags), ST_UID);
    }

    /**
     * return content of messages attachment
     *
     * @return binary attachment
     * @param $uid of the message
     * @param $index of the attachment (default: first attachment)
     */
    public function getAttachment($uid, $index = 0)
    {
        // find message
        $attachments = isset($this->attachments[$uid]) ? $this->attachments[$uid] : [];

        // find attachment
        if ($index > count($attachments)) {
            return false;
        }

        $attachment = $attachments[$index];

        // get attachment body
        $partStruct = imap_bodystruct($this->imap, $attachment['mailNum'], $attachment['partNum']);

        $message = imap_fetchbody($this->imap, $attachment['mailNum'], $attachment['partNum'], FT_PEEK);

        switch ($attachment['enc']) {
            case 0:
            case 1:
                $message = imap_8bit($message);

                break;
            case 2:
                $message = imap_binary($message);

                break;
            case 3:
                $message = imap_base64($message);

                break;
            case 4:
                $message = quoted_printable_decode($message);

                break;
        }

        return [
                'name'    => $attachment['name'],
                'size'    => $attachment['size'],
                'content' => $message, ];
    }

    /**
     * add new folder
     *
     * @return bool success or not
     * @param $name of the folder
     * @param $subscribe immediately subscribe to folder
     */
    public function addFolder($name, $subscribe = false)
    {
        $success = imap_createmailbox($this->imap, $this->mailbox . $name);
        if ($success && $subscribe) {
            $success = imap_subscribe($this->imap, $this->mailbox . $name);
        }

        return $success;
    }

    /**
     * remove folder
     *
     * @return bool success or not
     * @param $name of the folder
     */
    public function removeFolder($name)
    {
        return imap_deletemailbox($this->imap, $this->mailbox . $name);
    }

    /**
     * rename folder
     *
     * @return bool success or not
     * @param $name of the folder
     * @param $newname of the folder
     */
    public function renameFolder($name, $newname)
    {
        return imap_renamemailbox($this->imap, $this->mailbox . $name, $this->mailbox . $newname);
    }

    /**
     * clean folder content of selected folder
     *
     * @return bool success or not
     */
    public function purge()
    {
        // delete trash and spam
        if ($this->folder == $this->getTrash() || strtolower($this->folder) == 'spam') {
            if (imap_delete($this->imap, '1:*') === false) {
                return false;
            }

            return imap_expunge($this->imap);

            // move others to trash
        }
        if (imap_mail_move($this->imap, '1:*', $this->getTrash()) == false) {
            return false;
        }

        return imap_expunge($this->imap);
    }

    /**
     * returns all email addresses
     *
     * @return array with all email addresses or false on error
     */
    public function getAllEmailAddresses()
    {
        $saveCurrentFolder = $this->folder;
        $emails            = [];
        foreach ($this->getFolders() as $folder) {
            $this->selectFolder($folder);
            foreach ($this->getMessages(false) as $message) {
                $emails[] = $message['from'];
                $emails   = array_merge($emails, $message['to']);
                if (isset($message['cc'])) {
                    $emails = array_merge($emails, $message['cc']);
                }
            }
        }
        $this->selectFolder($saveCurrentFolder);

        return array_unique($emails);
    }

    /**
     * Get Reply to Addresses
     * @param  mixed $id email uid
     * @return array
     */
    public function getReplyToAddresses($id)
    {
        $header       = $this->getMessageHeader($id);
        $replyToArray = [];
        if (isset($header->reply_toaddress) && '' != $header->reply_toaddress) {
            $replyToArray = explode(', ', $header->reply_toaddress);
        }

        return $replyToArray;
    }

    /**
     * save email in sent
     *
     * @return void
     * @param $header
     * @param $body
     */
    public function saveMessageInSent($header, $body)
    {
        return imap_append($this->imap, $this->mailbox . $this->getSent(), $header . "\r\n" . $body . "\r\n", '\\Seen');
    }

    /**
     * explicitly close imap connection
     */
    public function close()
    {
        if ($this->imap !== false) {
            imap_close($this->imap);
        }
    }

    // protected helpers

    /**
     * get trash folder name or create new trash folder
     *
     * @return trash folder name
     */
    protected function getTrash()
    {
        foreach ($this->getFolders() as $folder) {
            if (strtolower($folder) === 'trash' || strtolower($folder) === 'papierkorb' || strtolower($folder) === 'inbox.trash') {
                return $folder;
            }
        }
        $trash_folder_name = 'Trash';
        if (_startsWith($folder, 'INBOX')) {
            $trash_folder_name = 'INBOX.Trash';
        }
        // no trash folder found? create one
        $this->addFolder($trash_folder_name);

        return $trash_folder_name;
    }

    /**
     * get sent folder name or create new sent folder
     *
     * @return sent folder name
     */
    protected function getSent()
    {
        foreach ($this->getFolders() as $folder) {
            if (strtolower($folder) === 'sent' || strtolower($folder) === 'gesendet') {
                return $folder;
            }
        }

        // no sent folder found? create one
        $this->addFolder('Sent');

        return 'Sent';
    }

    /**
     * fetch message by id
     *
     * @return header
     * @param $id of the message
     */
    protected function getMessageHeader($id)
    {
        $count = $this->countMessages();
        for ($i = 1; $i <= $count; $i++) {
            $uid = imap_uid($this->imap, $i);
            if ($uid == $id) {
                $header = imap_headerinfo($this->imap, $i);

                return $header;
            }
        }

        return false;
    }

    /**
     * convert attachment in array(name => ..., size => ...).
     *
     * @return array
     * @param $attachments with name and size
     */
    protected function attachments2name($attachments)
    {
        $names = [];
        foreach ($attachments as $attachment) {
            $names[] = [
                'name' => $attachment['name'],
                'size' => $attachment['size'],
            ];
        }

        return $names;
    }

    /**
     * convert imap given address in string
     *
     * @return string in format "Name <email@bla.de>"
     * @param $headerinfos the infos given by imap
     */
    protected function toAddress($headerinfos)
    {
        $email   = '';
        $name    = '';
        $charset = '';
        if (isset($headerinfos->mailbox) && isset($headerinfos->host)) {
            $email = $headerinfos->mailbox . '@' . $headerinfos->host;
        }
        if (!empty($headerinfos->personal)) {
            $name    = imap_mime_header_decode($headerinfos->personal);
            $charset = $name[0]->charset;
            $name    = $name[0]->text;
        } else {
            $name = $email;
        }

        $name = $this->convertToUtf8($name, $charset);

        return $name . ' <' . $email . '>';
    }

    /**
     * converts imap given array of addresses in strings
     *
     * @return array with strings (e.g. ["Name <email@bla.de>", "Name2 <email2@bla.de>"]
     * @param $addresses imap given addresses as array
     */
    protected function arrayToAddress($addresses)
    {
        $addressesAsString = [];
        foreach ($addresses as $address) {
            $addressesAsString[] = $this->toAddress($address);
        }

        return $addressesAsString;
    }

    /**
     * returns body of the email. First search for html version of the email, then the plain part.
     *
     * @return string email body
     * @param $uid message id
     */
    protected function getBody($uid)
    {
        $body                 = '';
        list($body, $charset) = $this->get_part($this->imap, $uid, 'TEXT/HTML');
        $html                 = true;
        // if HTML body is empty, try getting text body
        if ($body == '') {
            list($body, $charset) = $this->get_part($this->imap, $uid, 'TEXT/PLAIN');
            $html                 = false;
        }
        $body = $this->convertToUtf8($body, $charset);

        return [ 'body' => $body, 'html' => $html];
    }

    /**
     * Get email plain text
     * @param  mixed $uid email uid
     * @return mied
     */
    public function getPlainTextBody($uid)
    {
        list($body, $charset) = $this->get_part($this->imap, $uid, 'TEXT/PLAIN');
        $body                 = $this->convertToUtf8($body, $charset);

        return $body;
    }

    /**
     * convert to utf8 if necessary.
     *
     * @return true or false
     * @param $string utf8 encoded string
     */
    public function convertToUtf8($text, $fromCharset)
    {
        $utf8Aliases = [
            'utf8'       => true,
            'utf-8'      => true,
            'UTF8'       => true,
            'UTF-8'      => true,
            'utf-8utf-8' => true,
            'default'    => true,
        ];

        if (isset($utf8Aliases[$fromCharset])) {
            return $text;
        }

        $originalFromCharset  = $fromCharset;
        $lowercaseFromCharset = strtolower($fromCharset);
        if (isset(self::$charsetAliases[$lowercaseFromCharset])) {
            $fromCharset = self::$charsetAliases[$lowercaseFromCharset];
        }

        if ($fromCharset == 'iso-8859-8-i') {
            $fromCharset = 'iso-8859-8';
        }

        $iconvDecodedText = iconv($fromCharset, 'UTF-8//IGNORE', $text);
        if (false === $iconvDecodedText) {
            $iconvDecodedText = iconv($originalFromCharset, 'UTF-8', $text);
        }

        if (false !== $iconvDecodedText) {
            return $iconvDecodedText;
        }

        $decodedText = mb_convert_encoding($text, 'UTF-8', $fromCharset);

        return $decodedText;
    }

    /**
     * returns a part with a given mimetype
     * taken from http://www.sitepoint.com/exploring-phps-imap-library-2/
     *
     * @return string email body
     * @param $imap imap stream
     * @param $uid message id
     * @param $mimetype
     */
    protected function get_part($imap, $uid, $mimetype, $structure = false, $partNumber = false)
    {
        if (!$structure) {
            $structure = imap_fetchstructure($imap, $uid, FT_UID);
        }
        if ($structure) {
            if ($structure->parameters) {
                foreach ($structure->parameters as $x) {
                    $params[strtolower($x->attribute)] = $x->value;
                }
            }

            $charset = isset($params['charset']) ? $params['charset'] : null;

            if ($mimetype == $this->get_mime_type($structure)) {
                if (!$partNumber) {
                    $partNumber = 1;
                }
                $text = imap_fetchbody($imap, $uid, $partNumber, FT_UID | FT_PEEK);
                switch ($structure->encoding) {
                case 3: return [imap_base64($text), $charset];
                case 4: return [imap_qprint($text), $charset];
                default: return [$text, $charset];
           }
            }

            // multipart
            if ($structure->type == 1) {
                foreach ($structure->parts as $index => $subStruct) {
                    $prefix = '';
                    if ($partNumber) {
                        $prefix = $partNumber . '.';
                    }
                    list($data, $charset2) = $this->get_part($imap, $uid, $mimetype, $subStruct, $prefix . ($index + 1));
                    $charset               = ($charset) ? $charset : $charset2;
                    if ($data) {
                        return [$data, $charset];
                    }
                }
            }
        }

        return false;
    }

    /**
     * extract mimetype
     * taken from http://www.sitepoint.com/exploring-phps-imap-library-2/
     *
     * @return string mimetype
     * @param $structure
     */
    protected function get_mime_type($structure)
    {
        $primaryMimetype = ['TEXT', 'MULTIPART', 'MESSAGE', 'APPLICATION', 'AUDIO', 'IMAGE', 'VIDEO', 'OTHER'];

        if ($structure->subtype) {
            return $primaryMimetype[(int) $structure->type] . '/' . $structure->subtype;
        }

        return 'TEXT/PLAIN';
    }

    /**
     * get attachments of given email
     * taken from http://www.sitepoint.com/exploring-phps-imap-library-2/
     *
     * @return array of attachments
     * @param $imap stream
     * @param $mailNum email
     * @param $part
     * @param $partNum
     */
    protected function getAttachments($imap, $mailNum, $uID, $part, $partNum)
    {
        $attachments = [];

        if (isset($part->parts)) {
            foreach ($part->parts as $key => $subpart) {
                if ($partNum != '') {
                    $newPartNum = $partNum . '.' . ($key + 1);
                } else {
                    $newPartNum = ($key + 1);
                }
                $result = $this->getAttachments(
                    $imap,
                    $mailNum,
                    $uID,
                    $subpart,
                    $newPartNum
                );
                if (!empty($result) && isset($result['name'])) {
                    $this->attachments[$uID][] = $result;
                }
            }
        } elseif (isset($part->disposition)) {
            if (strtolower($part->disposition) == 'attachment' || strtolower($part->disposition) == 'inline') {
                $partStruct = imap_bodystruct(
                    $imap,
                    $mailNum,
                    $partNum
                );

                $attachmentDetails = [
                    'name'    => $part->dparameters[0]->value,
                    'partNum' => $partNum,
                    'enc'     => $partStruct->encoding,
                    'size'    => $part->bytes,
                    'mailNum' => $mailNum,
                    'uid'     => $uID,
                ];

                return $attachmentDetails;
            }
        }
    }

    /**
     * Return general mailbox statistics
     *
     * @return bool | StdClass object
     */
    public function getMailboxStatistics()
    {
        return $this->isConnected() ? imap_mailboxmsginfo($this->imap) : false ;
    }
}
