![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/EmailBundle/MonitoredEmail/ |
<?php namespace Mautic\EmailBundle\MonitoredEmail; use Mautic\CoreBundle\Helper\CoreParametersHelper; use Mautic\CoreBundle\Helper\PathsHelper; use Mautic\EmailBundle\Exception\MailboxException; use Mautic\EmailBundle\MonitoredEmail\Exception\NotConfiguredException; class Mailbox { /** * Return all mails matching the rest of the criteria. */ public const CRITERIA_ALL = 'ALL'; /** * Match mails with the \\ANSWERED flag set. */ public const CRITERIA_ANSWERED = 'ANSWERED'; /** * CRITERIA_BCC "string" - match mails with "string" in the Bcc: field. */ public const CRITERIA_BCC = 'BCC'; /** * CRITERIA_BEFORE "date" - match mails with Date: before "date". */ public const CRITERIA_BEFORE = 'BEFORE'; /** * CRITERIA_BODY "string" - match mails with "string" in the body of the mail. */ public const CRITERIA_BODY = 'BODY'; /** * CRITERIA_CC "string" - match mails with "string" in the Cc: field. */ public const CRITERIA_CC = 'CC'; /** * Match deleted mails. */ public const CRITERIA_DELETED = 'DELETED'; /** * Match mails with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set. */ public const CRITERIA_FLAGGED = 'FLAGGED'; /** * CRITERIA_FROM "string" - match mails with "string" in the From: field. */ public const CRITERIA_FROM = 'FROM'; /** * CRITERIA_KEYWORD "string" - match mails with "string" as a keyword. */ public const CRITERIA_KEYWORD = 'KEYWORD'; /** * Match new mails. */ public const CRITERIA_NEW = 'NEW'; /** * Match old mails. */ public const CRITERIA_OLD = 'OLD'; /** * CRITERIA_ON "date" - match mails with Date: matching "date". */ public const CRITERIA_ON = 'ON'; /** * Match mails with the \\RECENT flag set. */ public const CRITERIA_RECENT = 'RECENT'; /** * Match mails that have been read (the \\SEEN flag is set). */ public const CRITERIA_SEEN = 'SEEN'; /** * CRITERIA_SINCE "date" - match mails with Date: after "date". */ public const CRITERIA_SINCE = 'SINCE'; /** * CRITERIA_SUBJECT "string" - match mails with "string" in the Subject:. */ public const CRITERIA_SUBJECT = 'SUBJECT'; /** * CRITERIA_TEXT "string" - match mails with text "string". */ public const CRITERIA_TEXT = 'TEXT'; /** * CRITERIA_TO "string" - match mails with "string" in the To:. */ public const CRITERIA_TO = 'TO'; /** * Get messages since a specific UID. Eg. UID 2:* will return all messages with UID 2 and above (IMAP includes the given UID). */ public const CRITERIA_UID = 'UID'; /** * Match mails that have not been answered. */ public const CRITERIA_UNANSWERED = 'UNANSWERED'; /** * Match mails that are not deleted. */ public const CRITERIA_UNDELETED = 'UNDELETED'; /** * Match mails that are not flagged. */ public const CRITERIA_UNFLAGGED = 'UNFLAGGED'; /** * CRITERIA_UNKEYWORD "string" - match mails that do not have the keyword "string". */ public const CRITERIA_UNKEYWORD = 'UNKEYWORD'; /** * Match mails which have not been read yet. */ public const CRITERIA_UNSEEN = 'UNSEEN'; /** * Match mails which have not been read yet - alias of CRITERIA_UNSEEN. */ public const CRITERIA_UNREAD = 'UNSEEN'; protected $imapPath; protected $imapFullPath; protected $imapStream; protected $imapFolder = 'INBOX'; protected $imapOptions = 0; protected $imapRetriesNum = 0; protected $imapParams = []; protected $serverEncoding = 'UTF-8'; protected $attachmentsDir; protected $settings; protected $isGmail = false; protected $mailboxes; /** * @var mixed[] */ private array $folders = []; public function __construct(CoreParametersHelper $parametersHelper, PathsHelper $pathsHelper) { $this->mailboxes = $parametersHelper->get('monitored_email', []); if (isset($this->mailboxes['general'])) { $this->settings = $this->mailboxes['general']; } else { $this->settings = [ 'host' => '', 'port' => '', 'password' => '', 'user' => '', 'encryption' => '', 'use_attachments' => false, ]; } $this->createAttachmentsDir($pathsHelper); if ('imap.gmail.com' == $this->settings['host']) { $this->isGmail = true; } } /** * Returns if a mailbox is configured. * * @throws MailboxException */ public function isConfigured($bundleKey = null, $folderKey = null): bool { if (null !== $bundleKey) { try { $this->switchMailbox($bundleKey, $folderKey); } catch (MailboxException) { return false; } } return !empty($this->settings['host']) && !empty($this->settings['port']) && !empty($this->settings['user']) && !empty($this->settings['password']); } /** * Switch to another configured monitored mailbox. * * @param string $mailbox * * @throws MailboxException */ public function switchMailbox($bundle, $mailbox = ''): void { $key = $bundle.(!empty($mailbox) ? '_'.$mailbox : ''); if (isset($this->mailboxes[$key])) { $this->settings = (!empty($this->mailboxes[$key]['override_settings'])) ? $this->mailboxes[$key] : $this->mailboxes['general']; $this->imapFolder = $this->mailboxes[$key]['folder']; $this->settings['folder'] = $this->mailboxes[$key]['folder']; // Disconnect so that new mailbox settings are used $this->disconnect(); // Setup new connection $this->setImapPath(); } else { throw new MailboxException($key.' not found'); } } /** * Returns if this is a Gmail connection. * * @return mixed */ public function isGmail() { return $this->isGmail(); } /** * Set imap path based on mailbox settings. */ public function setImapPath($settings = null): void { if (null == $settings) { $settings = $this->settings; } $paths = $this->getImapPath($settings); $this->imapPath = $paths['path']; $this->imapFullPath = $paths['full']; } public function getImapPath($settings): array { if (!isset($settings['encryption'])) { $settings['encryption'] = (!empty($settings['ssl'])) ? '/ssl' : ''; } $path = "{{$settings['host']}:{$settings['port']}/imap{$settings['encryption']}}"; $fullPath = $path; if (isset($settings['folder'])) { $fullPath .= $settings['folder']; } return ['path' => $path, 'full' => $fullPath]; } /** * Override mailbox settings. */ public function setMailboxSettings(array $settings): void { $this->settings = array_merge($this->settings, $settings); $this->isGmail = ('imap.gmail.com' == $this->settings['host']); $this->setImapPath(); } /** * Get settings. * * @param string $mailbox * * @return mixed * * @throws MailboxException */ public function getMailboxSettings($bundle = null, $mailbox = '') { if (null == $bundle) { return $this->settings; } $key = $bundle.(!empty($mailbox) ? '_'.$mailbox : ''); if (isset($this->mailboxes[$key])) { $settings = (!empty($this->mailboxes[$key]['override_settings'])) ? $this->mailboxes[$key] : $this->mailboxes['general']; $settings['folder'] = $this->mailboxes[$key]['folder']; $this->setImapPath($settings); $imapPath = $this->getImapPath($settings); $settings['imap_path'] = $imapPath['full']; } else { throw new MailboxException($key.' not found'); } return $settings; } /** * Set custom connection arguments of imap_open method. See http://php.net/imap_open. * * @param int $options * @param int $retriesNum */ public function setConnectionArgs($options = 0, $retriesNum = 0, array $params = null): void { $this->imapOptions = $options; $this->imapRetriesNum = $retriesNum; $this->imapParams = $params; } /** * Switch to another box. */ public function switchFolder($folder): void { if ($folder != $this->imapFolder) { $this->imapFullPath = $this->imapPath.$folder; $this->imapFolder = $folder; } $this->getImapStream(); } /** * Get IMAP mailbox connection stream. * * @return resource|null */ public function getImapStream() { if (!$this->isConnected()) { $this->imapStream = $this->initImapStream(); } else { @imap_reopen($this->imapStream, $this->imapFullPath); } return $this->imapStream; } /** * @return resource * * @throws MailboxException */ protected function initImapStream() { imap_timeout(IMAP_OPENTIMEOUT, 15); imap_timeout(IMAP_CLOSETIMEOUT, 15); imap_timeout(IMAP_READTIMEOUT, 15); imap_timeout(IMAP_WRITETIMEOUT, 15); $imapStream = @imap_open( $this->imapFullPath, $this->settings['user'], $this->settings['password'], $this->imapOptions, $this->imapRetriesNum, $this->imapParams ); if (!$imapStream) { throw new MailboxException(); } return $imapStream; } /** * Check if the stream is connected. */ protected function isConnected(): bool { return $this->isConfigured() && $this->imapStream && is_resource($this->imapStream) && @imap_ping($this->imapStream); } /** * Get information about the current mailbox. * * Returns the information in an object with following properties: * Date - current system time formatted according to RFC2822 * Driver - protocol used to access this mailbox: POP3, IMAP, NNTP * Mailbox - the mailbox name * Nmsgs - number of mails in the mailbox * Recent - number of recent mails in the mailbox * * @return \stdClass */ public function checkMailbox(): \stdClass|bool { return imap_check($this->getImapStream()); } /** * Creates a new mailbox specified by mailbox. */ public function createMailbox(): bool { return imap_createmailbox($this->getImapStream(), imap_utf7_encode($this->imapFullPath)); } /** * Gets status information about the given mailbox. * * This function returns an object containing status information. * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. * * @return \stdClass if the box doesn't exist */ public function statusMailbox(): \stdClass|bool { return imap_status($this->getImapStream(), $this->imapFullPath, SA_ALL); } /** * Gets listing the folders. * * This function returns an object containing listing the folders. * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. * * @return array listing the folders */ public function getListingFolders() { if (!$this->isConfigured()) { throw new NotConfiguredException('mautic.email.config.monitored_email.not_configured'); } if (!isset($this->folders[$this->imapFullPath])) { $tempFolders = @imap_list($this->getImapStream(), $this->imapPath, '*'); if (!empty($tempFolders)) { foreach ($tempFolders as $key => $folder) { $folder = str_replace($this->imapPath, '', imap_utf8($folder)); $tempFolders[$key] = $folder; } } else { $tempFolders = []; } $this->folders[$this->imapFullPath] = $tempFolders; } return $this->folders[$this->imapFullPath]; } public function fetchUnread($folder = null): array { if (null !== $folder) { $this->switchFolder($folder); } return $this->searchMailBox(self::CRITERIA_UNSEEN); } /** * This function performs a search on the mailbox currently opened in the given IMAP stream. * For example, to match all unanswered mails sent by Mom, you'd use: "UNANSWERED FROM mom". * Searches appear to be case insensitive. This list of criteria is from a reading of the UW * c-client source code and may be incomplete or inaccurate (see also RFC2060, section 6.4.4). * * @param string $criteria String, delimited by spaces, in which the following keywords are allowed. Any multi-word arguments (e.g. FROM "joey * smith") must be quoted. Results will match all criteria entries. * ALL - return all mails matching the rest of the criteria * ANSWERED - match mails with the \\ANSWERED flag set * BCC "string" - match mails with "string" in the Bcc: field * BEFORE "date" - match mails with Date: before "date" * BODY "string" - match mails with "string" in the body of the mail * CC "string" - match mails with "string" in the Cc: field * DELETED - match deleted mails * FLAGGED - match mails with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set * FROM "string" - match mails with "string" in the From: field * KEYWORD "string" - match mails with "string" as a keyword * NEW - match new mails * OLD - match old mails * ON "date" - match mails with Date: matching "date" * RECENT - match mails with the \\RECENT flag set * SEEN - match mails that have been read (the \\SEEN flag is set) * SINCE "date" - match mails with Date: after "date" * SUBJECT "string" - match mails with "string" in the Subject: * TEXT "string" - match mails with text "string" * TO "string" - match mails with "string" in the To: * UNANSWERED - match mails that have not been answered * UNDELETED - match mails that are not deleted * UNFLAGGED - match mails that are not flagged * UNKEYWORD "string" - match mails that do not have the keyword "string" * UNSEEN - match mails which have not been read yet * * @return array Mails ids */ public function searchMailbox($criteria = self::CRITERIA_ALL): array { if (preg_match('/'.self::CRITERIA_UID.' ((\d+):(\d+|\*))/', $criteria, $matches)) { // PHP imap_search does not support UID n:* so use imap_fetch_overview instead $messages = imap_fetch_overview($this->getImapStream(), $matches[1], FT_UID); $mailIds = []; foreach ($messages as $message) { $mailIds[] = $message->uid; } } else { $mailIds = imap_search($this->getImapStream(), $criteria, SE_UID); } return $mailIds ?: []; } /** * Save mail body. * * @param string $filename */ public function saveMail($mailId, $filename = 'email.eml'): bool { return imap_savebody($this->getImapStream(), $filename, $mailId, '', FT_UID); } /** * Marks mails listed in mailId for deletion. */ public function deleteMail($mailId): bool { return imap_delete($this->getImapStream(), $mailId, FT_UID); } /** * Move mail to another box. */ public function moveMail($mailId, $mailBox): bool { return imap_mail_move($this->getImapStream(), $mailId, $mailBox, CP_UID) && $this->expungeDeletedMails(); } /** * Deletes all the mails marked for deletion by imap_delete(), imap_mail_move(), or imap_setflag_full(). */ public function expungeDeletedMails(): bool { return imap_expunge($this->getImapStream()); } /** * Add the flag \Seen to a mail. */ public function markMailAsRead($mailId): bool { return $this->setFlag([$mailId], '\\Seen'); } /** * Remove the flag \Seen from a mail. */ public function markMailAsUnread($mailId): bool { return $this->clearFlag([$mailId], '\\Seen'); } /** * Add the flag \Flagged to a mail. */ public function markMailAsImportant($mailId): bool { return $this->setFlag([$mailId], '\\Flagged'); } /** * Add the flag \Seen to a mails. */ public function markMailsAsRead(array $mailIds): bool { return $this->setFlag($mailIds, '\\Seen'); } /** * Remove the flag \Seen from some mails. */ public function markMailsAsUnread(array $mailIds): bool { return $this->clearFlag($mailIds, '\\Seen'); } /** * Add the flag \Flagged to some mails. */ public function markMailsAsImportant(array $mailIds): bool { return $this->setFlag($mailIds, '\\Flagged'); } /** * Causes a store to add the specified flag to the flags set for the mails in the specified sequence. * * @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060 */ public function setFlag(array $mailsIds, $flag): bool { return imap_setflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID); } /** * Cause a store to delete the specified flag to the flags set for the mails in the specified sequence. * * @param string $flag which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060 */ public function clearFlag(array $mailsIds, $flag): bool { return imap_clearflag_full($this->getImapStream(), implode(',', $mailsIds), $flag, ST_UID); } /** * Fetch mail headers for listed mails ids. * * Returns an array of objects describing one mail header each. The object will only define a property if it exists. The possible properties are: * subject - the mails subject * from - who sent it * to - recipient * date - when was it sent * message_id - Mail-ID * references - is a reference to this mail id * in_reply_to - is a reply to this mail id * size - size in bytes * uid - UID the mail has in the mailbox * msgno - mail sequence number in the mailbox * recent - this mail is flagged as recent * flagged - this mail is flagged * answered - this mail is flagged as answered * deleted - this mail is flagged for deletion * seen - this mail is flagged as already read * draft - this mail is flagged as being a draft * * @return array */ public function getMailsInfo(array $mailsIds) { $mails = imap_fetch_overview($this->getImapStream(), implode(',', $mailsIds), FT_UID); if (is_array($mails) && count($mails)) { foreach ($mails as &$mail) { if (isset($mail->subject)) { $mail->subject = $this->decodeMimeStr($mail->subject, $this->serverEncoding); } if (isset($mail->from)) { $mail->from = $this->decodeMimeStr($mail->from, $this->serverEncoding); } if (isset($mail->to)) { $mail->to = $this->decodeMimeStr($mail->to, $this->serverEncoding); } } } return $mails; } /** * Get information about the current mailbox. * * Returns an object with following properties: * Date - last change (current datetime) * Driver - driver * Mailbox - name of the mailbox * Nmsgs - number of messages * Recent - number of recent messages * Unread - number of unread messages * Deleted - number of deleted messages * Size - mailbox size */ public function getMailboxInfo(): \stdClass { return imap_mailboxmsginfo($this->getImapStream()); } /** * Gets mails ids sorted by some criteria. * * Criteria can be one (and only one) of the following constants: * SORTDATE - mail Date * SORTARRIVAL - arrival date (default) * SORTFROM - mailbox in first From address * SORTSUBJECT - mail subject * SORTTO - mailbox in first To address * SORTCC - mailbox in first cc address * SORTSIZE - size of mail in octets * * @param int $criteria * @param bool $reverse * * @return array Mails ids */ public function sortMails($criteria = SORTARRIVAL, $reverse = true): array|bool { return imap_sort($this->getImapStream(), $criteria, $reverse, SE_UID); } /** * Get mails count in mail box. */ public function countMails(): int|bool { return imap_num_msg($this->getImapStream()); } /** * Retrieve the quota settings per user. * * @return array - FALSE in the case of call failure */ protected function getQuota(): array|bool { return imap_get_quotaroot($this->getImapStream(), 'INBOX'); } /** * Return quota limit in KB. * * @return int - FALSE in the case of call failure */ public function getQuotaLimit() { $quota = $this->getQuota(); if (is_array($quota)) { $quota = $quota['STORAGE']['limit']; } return $quota; } /** * Return quota usage in KB. * * @return int - FALSE in the case of call failure */ public function getQuotaUsage() { $quota = $this->getQuota(); if (is_array($quota)) { $quota = $quota['STORAGE']['usage']; } return $quota; } /** * Get mail data. * * @param bool $markAsSeen */ public function getMail($mailId, $markAsSeen = true): Message { $header = imap_fetchheader($this->getImapStream(), $mailId, FT_UID); $headObject = imap_rfc822_parse_headers($header); $mail = new Message(); $mail->id = $mailId; $mail->date = date('Y-m-d H:i:s', isset($headObject->date) ? strtotime(preg_replace('/\(.*?\)/', '', $headObject->date)) : time()); $mail->subject = isset($headObject->subject) ? $this->decodeMimeStr($headObject->subject, $this->serverEncoding) : null; $mail->fromName = isset($headObject->from[0]->personal) ? $this->decodeMimeStr($headObject->from[0]->personal, $this->serverEncoding) : null; $mail->fromAddress = strtolower($headObject->from[0]->mailbox.'@'.$headObject->from[0]->host); if (isset($headObject->to)) { $toStrings = []; foreach ($headObject->to as $to) { if (!empty($to->mailbox) && !empty($to->host)) { $toEmail = strtolower($to->mailbox.'@'.$to->host); $toName = isset($to->personal) ? $this->decodeMimeStr($to->personal, $this->serverEncoding) : null; $toStrings[] = $toName ? "$toName <$toEmail>" : $toEmail; $mail->to[$toEmail] = $toName; } } $mail->toString = implode(', ', $toStrings); } if (isset($headObject->cc)) { foreach ($headObject->cc as $cc) { $mail->cc[strtolower($cc->mailbox.'@'.$cc->host)] = isset($cc->personal) ? $this->decodeMimeStr($cc->personal, $this->serverEncoding) : null; } } if (isset($headObject->reply_to)) { foreach ($headObject->reply_to as $replyTo) { $mail->replyTo[strtolower($replyTo->mailbox.'@'.$replyTo->host)] = isset($replyTo->personal) ? $this->decodeMimeStr( $replyTo->personal, $this->serverEncoding ) : null; } } if (isset($headObject->in_reply_to)) { $mail->inReplyTo = $headObject->in_reply_to; } if (isset($headObject->return_path)) { $mail->returnPath = $headObject->return_path; } if (isset($headObject->references)) { $mail->references = explode("\n", $headObject->references); } $mailStructure = imap_fetchstructure($this->getImapStream(), $mailId, FT_UID); if (empty($mailStructure->parts)) { $this->initMailPart($mail, $mailStructure, 0, $markAsSeen); } else { foreach ($mailStructure->parts as $partNum => $partStructure) { $this->initMailPart($mail, $partStructure, $partNum + 1, $markAsSeen); } } // Parse X headers $tempArray = explode("\n", $header); $headers = []; foreach ($tempArray as $line) { if (preg_match('/^X-(.*?): (.*?)$/is', trim($line), $matches)) { $headers['x-'.strtolower($matches[1])] = $matches[2]; } } $mail->xHeaders = $headers; return $mail; } /** * @param bool|true $markAsSeen * @param bool|false $isDsn * @param bool|false $isFbl */ protected function initMailPart(Message $mail, $partStructure, $partNum, $markAsSeen = true, $isDsn = false, $isFbl = false) { $options = FT_UID; if (!$markAsSeen) { $options |= FT_PEEK; } $data = $partNum ? imap_fetchbody($this->getImapStream(), $mail->id, $partNum, $options) : imap_body( $this->getImapStream(), $mail->id, $options ); if (1 == $partStructure->encoding) { $data = imap_utf8($data); } elseif (2 == $partStructure->encoding) { $data = imap_binary($data); } elseif (3 == $partStructure->encoding) { $data = imap_base64($data); } elseif (4 == $partStructure->encoding) { $data = quoted_printable_decode($data); } $params = $this->getParameters($partStructure); // attachments $attachmentId = $partStructure->ifid ? trim($partStructure->id, ' <>') : (isset($params['filename']) || isset($params['name']) ? mt_rand().mt_rand() : null); // ignore contentId on body when mail isn't multipart (https://github.com/barbushin/php-imap/issues/71) if (!$partNum && TYPETEXT === $partStructure->type) { $attachmentId = null; } if ($attachmentId) { if (isset($this->settings['use_attachments']) && $this->settings['use_attachments']) { if (empty($params['filename']) && empty($params['name'])) { $fileName = $attachmentId.'.'.strtolower($partStructure->subtype); } else { $fileName = !empty($params['filename']) ? $params['filename'] : $params['name']; $fileName = $this->decodeMimeStr($fileName, $this->serverEncoding); $fileName = $this->decodeRFC2231($fileName, $this->serverEncoding); } $attachment = new Attachment(); $attachment->id = $attachmentId; $attachment->name = $fileName; if ($this->attachmentsDir) { $replace = [ '/\s/' => '_', '/[^0-9a-zа-яіїє_\.]/iu' => '', '/_+/' => '_', '/(^_)|(_$)/' => '', ]; $fileSysName = preg_replace( '~[\\\\/]~', '', $mail->id.'_'.$attachmentId.'_'.preg_replace(array_keys($replace), $replace, $fileName) ); $attachment->filePath = $this->attachmentsDir.DIRECTORY_SEPARATOR.$fileSysName; file_put_contents($attachment->filePath, $data); } $mail->addAttachment($attachment); } } else { if (!empty($params['charset'])) { $data = $this->convertStringEncoding($data, $params['charset'], $this->serverEncoding); } if (!empty($data)) { $subtype = !empty($partStructure->ifsubtype) ? strtolower($partStructure->subtype) : ''; switch ($partStructure->type) { case TYPETEXT: match ($subtype) { 'plain' => $mail->textPlain .= $data, // no break default => $mail->textHtml .= $data, }; break; case TYPEMULTIPART: if ( 'report' != $subtype || empty($params['report-type']) ) { break; } $reportType = strtolower($params['report-type']); switch ($reportType) { case 'delivery-status': $mail->dsnMessage = trim($data); $isDsn = true; break; case 'feedback-report': $mail->fblMessage = trim($data); $isFbl = true; break; default: // Just pass through. } break; case TYPEMESSAGE: if ($isDsn || ('delivery-status' == $subtype)) { $mail->dsnReport = $data; } elseif ($isFbl || ('feedback-report' == $subtype)) { $mail->fblReport = $data; } else { $mail->textPlain .= trim($data); } break; default: // Just pass through. } } } if (!empty($partStructure->parts)) { foreach ($partStructure->parts as $subPartNum => $subPartStructure) { if (2 == $partStructure->type && 'RFC822' == $partStructure->subtype) { $this->initMailPart($mail, $subPartStructure, $partNum, $markAsSeen, $isDsn, $isFbl); } else { $this->initMailPart($mail, $subPartStructure, $partNum.'.'.($subPartNum + 1), $markAsSeen, $isDsn, $isFbl); } } } } protected function getParameters($partStructure): array { $params = []; if (!empty($partStructure->parameters)) { foreach ($partStructure->parameters as $param) { $params[strtolower($param->attribute)] = $param->value; } } if (!empty($partStructure->dparameters)) { foreach ($partStructure->dparameters as $param) { $paramName = strtolower(preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute); if (isset($params[$paramName])) { $params[$paramName] .= $param->value; } else { $params[$paramName] = $param->value; } } } return $params; } /** * @param string $charset */ protected function decodeMimeStr($string, $charset = 'utf-8'): string { $newString = ''; $elements = imap_mime_header_decode($string); for ($i = 0; $i < count($elements); ++$i) { if ('default' == $elements[$i]->charset) { $elements[$i]->charset = 'iso-8859-1'; } $newString .= $this->convertStringEncoding($elements[$i]->text, $elements[$i]->charset, $charset); } return $newString; } protected function isUrlEncoded($string): bool { $hasInvalidChars = preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $string); $hasEscapedChars = preg_match('#%[a-zA-Z0-9]{2}#', $string); return !$hasInvalidChars && $hasEscapedChars; } /** * @param string $charset * * @return string */ protected function decodeRFC2231($string, $charset = 'utf-8') { if (preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) { $encoding = $matches[1]; $data = $matches[2]; if ($this->isUrlEncoded($data)) { $string = $this->convertStringEncoding(urldecode($data), $encoding, $charset); } } return $string; } /** * Converts a string from one encoding to another. * * @param string $string * @param string $fromEncoding * @param string $toEncoding * * @return string Converted string if conversion was successful, or the original string if not */ protected function convertStringEncoding($string, $fromEncoding, $toEncoding) { $convertedString = null; if ($string && $fromEncoding != $toEncoding) { $convertedString = @iconv($fromEncoding, $toEncoding.'//IGNORE', $string); if (!$convertedString && extension_loaded('mbstring')) { $convertedString = @mb_convert_encoding($string, $toEncoding, $fromEncoding); } } return $convertedString ?: $string; } /** * Close IMAP connection. */ protected function disconnect() { if ($this->isConnected()) { // Prevent these from throwing notices such as "SECURITY PROBLEM: insecure server advertised" imap_errors(); imap_alerts(); @imap_close($this->imapStream, CL_EXPUNGE); } } private function createAttachmentsDir(PathsHelper $pathsHelper): void { if (!isset($this->settings['use_attachments']) || !$this->settings['use_attachments']) { return; } $this->attachmentsDir = $pathsHelper->getSystemPath('tmp', true); if (!file_exists($this->attachmentsDir)) { mkdir($this->attachmentsDir); } $this->attachmentsDir .= '/attachments'; if (!file_exists($this->attachmentsDir)) { mkdir($this->attachmentsDir); } } /** * Disconnect on destruct. */ public function __destruct() { $this->disconnect(); } }