New chat
2025-01
кто я
T
test*******ous@gmail.com
Решение проблемы неполных multibyte символов
Какие варианты решения Отследить предупреждения и исправить Есть предупреждение приходят иногда [24-Oct-2025 09:12:11 Etc/GMT-3] PHP Notice: iconv(): Detected an incomplete multibyte character in input string in I:\Inetpub\promo-ltd.ru\control.traceline\imap\lib\class.imap.php on line 416 [24-Oct-2025 09:12:11 Etc/GMT-3] PHP Notice: iconv(): Detected an illegal character in input string in I:\Inetpub\promo-ltd.ru\control.traceline\imap\lib\class.imap.php on line 416 [24-Oct-2025 09:12:11 Etc/GMT-3] PHP Notice: iconv(): Detected an incomplete multibyte character in input string in I:\Inetpub\promo-ltd.ru\control.traceline\imap\lib\class.imap.php on line 416 [24-Oct-2025 09:12:11 Etc/GMT-3] PHP Notice: iconv(): Detected an illegal character in input string in I:\Inetpub\promo-ltd.ru\control.traceline\imap\lib\class.imap.php on line 416 приходит файл с именем того вида _utf-8_B_0JPQsNGA0LDQvdGC0LjQudC90L7QtSDQotGA0LXQudGB0..... сам класс <?php /* * This class can be used to retrieve messages from an IMAP, POP3 and NNTP server * @author Kiril Kirkov * GitHub: https://github.com/kirilkirkov * Usage example: 1. $imap = new Imap(); 2. $connection_result = $imap->connect('{imap.gmail.com:993/imap/ssl}INBOX', 'user@gmail.com', 'secret_password'); if ($connection_result !== true) { echo $connection_result; //Error message! exit; } 3. $messages = $imap->getMessages('text'); //Array of messages * in $attachments_dir property set directory for attachments * in the __destructor set errors log */ class Imap { private $imapStream; private $plaintextMessage; private $htmlMessage; private $emails; private $errors = array(); private $attachments = array(); private $attachments_dir = 'attachments';//ERROR public function connect($hostname, $username, $password) { $connection = imap_open($hostname, $username, $password) or die('Cannot connect to Mail: ' . imap_last_error()); $this->imapStream = $connection; return true; } public function getMessages($type = 'text', $uid = null, $setcont='ALL') { $this->attachments_dir = rtrim($this->attachments_dir, '/'); $stream = $this->imapStream; $emails = imap_search($stream, $setcont);//'ALL' $messages = array(); if ($emails) { $this->emails = $emails; $this->attachments = array(); if(is_null($uid)) $uid = $_GET['id']; $messages[] = $this->loadMessage($uid, $type); } return array( "status" => "success", "data" => array_reverse($messages) ); } /* * Give message_numer from * returned array from - getMessages * if you want to delete message by UID, not Message number * set FT_UID to $uid. * Example $imap->deleteMessage(221, FT_UID); - 221 is uid */ public function deleteMessage($messageId, $uid = 0) { imap_delete($this->imapStream, $messageId, $uid); } public function getFiles($r) { //save attachments to directory /* $r['file'] = str_replace("UTF-8''",'',$r['file']); $r['file'] = urldecode($r['file']); $r['file'] = preg_replace("/[^A-Za-zА-Яа-я0-9-_.]+/u", "_", $r['file']);*/ $this->attachments_dir = $r['attachments_dir']; $pullPath = $this->attachments_dir . '/' . $r['path'] .'/'. $r['file']; //file_put_contents('_!!!_DEBUG-86nt8.txt',var_export($pullPath,1),FILE_APPEND); $res = true; if (file_exists($pullPath)) { $res = false; } elseif (!is_dir($this->attachments_dir)) { $this->errors[] = 'Cant find directory for email attachments! Message ID:' . $r['uid']; return false; } elseif (!is_writable($this->attachments_dir)) { $this->errors[] = 'Attachments directory is not writable! Message ID:' . $r['uid']; return false; } if (!is_dir($this->attachments_dir . '/' . $r['path'] .'/')) { mkdir($this->attachments_dir . '/' . $r['path'] .'/');//mkdir($dirname, 0755, true); } if($res && !preg_match('/\.php/i', $r['file']) && !preg_match('/\.cgi/i', $r['file']) && !preg_match('/\.exe/i', $r['file']) && !preg_match('/\.dll/i', $r['file']) && !preg_match('/\.mobileconfig/i', $r['file'])){ //fopen($pullPath, 'w');//!!!!!!!!!!!!!!!!!!!!!!!!! if (($filePointer = fopen($pullPath, 'w')) == false) { $this->errors[] = 'Cant open file at imap class to save attachment file! Message ID:' . $r['uid']; return false; } switch ($r['encoding']) { case 3: //base64 $streamFilter = stream_filter_append($filePointer, 'convert.base64-decode', STREAM_FILTER_WRITE); break; case 4: //quoted-printable $streamFilter = stream_filter_append($filePointer, 'convert.quoted-printable-decode', STREAM_FILTER_WRITE); break; default: $streamFilter = null; } imap_savebody($this->imapStream, $filePointer, $r['uid'], $r['part'], FT_UID); if ($streamFilter) { stream_filter_remove($streamFilter); } fclose($filePointer); return array("status" => "success", "path" => $r['path'], 'file'=>$r['file']);//return array("status" => "success", "path" => $pullPath); }else{ return array("status" => "success", "path" => $r['path'], 'file'=>$r['file']);//return array("status" => "success", "path" => $pullPath); } } private function loadMessage($uid, $type) { $overview = $this->getOverview($uid); $array = array(); $array['uid'] = $overview->uid; $array['subject'] = isset($overview->subject) ? $this->decode($overview->subject) : ''; $array['date'] = date('Y-m-d h:i:sa', strtotime($overview->date)); $headers = $this->getHeaders($uid); $array['from'] = isset($headers->from) ? $this->processAddressObject($headers->from) : array(''); $array['cc'] = isset($headers->cc) ? $this->processAddressObject($headers->cc) : array(''); $array['references'] = isset($overview->references) ? $overview->references : 0; $structure = $this->getStructure($uid); if (!isset($structure->parts)) { // not multipart $this->processStructure($uid, $structure); } else { // multipart foreach ($structure->parts as $id => $part) { $this->processStructure($uid, $part, $id + 1); } } // После обработки всех частей, сохраняем mapping CID → файл //$array['cid_mapping'] = array_column($this->attachments, 'filename_cid', 'cid'); $array['cid_mapping'] = array_column($this->attachments, 'file', 'cid'); // После обработки всех частей: if ($type === 'html' && !empty($this->htmlMessage)) { // Создаем маппинг CID → файлы $cidMapping = []; foreach ($this->attachments as $attachment) { if (!empty($attachment['cid'])) { $cidMapping[$attachment['cid']] = [ 'file' => $attachment['file'] = preg_replace("/[^A-Za-zА-Яа-я0-9-_.]+/u", "_", $attachment['file']), 'path' => date('Y-m-d'), // или другой путь из saveToDirectory 'cidi' => $array['cid_mapping'] ]; } } // Обрабатываем HTML $this->htmlMessage = $this->processCIDImages($this->htmlMessage, $cidMapping); } $array['message'] = $type == 'text' ? $this->plaintextMessage : $this->htmlMessage;// $email->getMessages('html',$value->uid) ..$tab->message $array['attachments'] = $this->attachments; return $array; } private function processStructure($uid, $structure, $partIdentifier = null) { $parameters = $this->getParametersFromStructure($structure); if ((isset($parameters['name']) || (isset($parameters['name*']) || isset($parameters['filename'])) || isset($parameters['filename*'])) || (isset($structure->subtype) && strtolower($structure->subtype) == 'rfc822') ) { if (isset($parameters['filename'])) { $this->setFileName($parameters['filename']); } elseif (isset($parameters['name'])) { $this->setFileName($parameters['name']); } elseif (isset($parameters['filename*'])) { $this->setFileName($parameters['filename*']); } elseif (isset($parameters['name*'])) { $this->setFileName($parameters['name*']); } // Добавляем обработку Content-ID Извлекаем CID (удаляем угловые скобки если есть) $contentId = isset($parameters['id']) ? trim($parameters['id'], '<>') : null; //$filename = $this->getAttachmentFilename($parameters); //$this->setFileName($filename); $this->encoding = $structure->encoding; $result_save = $this->saveToDirectory($uid, $partIdentifier); // Добавляем Content-ID в информацию о вложении $result_save['cid'] = $contentId; $this->attachments[] = $result_save; } elseif ($structure->type == 0 || $structure->type == 1) { $messageBody = isset($partIdentifier) ? imap_fetchbody($this->imapStream, $uid, $partIdentifier, FT_UID | FT_PEEK) : imap_body($this->imapStream, $uid, FT_UID | FT_PEEK); //file_put_contents('../_!!!_DEBUG-imap_fetchbody-index.txt',var_export($messageBody,1),FILE_APPEND); $messageBody = $this->decodeMessage($messageBody, $structure->encoding); //file_put_contents('../_!!!_DEBUG-decodeMessage-index.txt',var_export($messageBody,1),FILE_APPEND); /*$body = imap_qprint($messageBody); file_put_contents('../_!!!_DEBUG-FINALmessageBody1-index.txt',var_export($body,1),FILE_APPEND); $messageBodyTEST0 = imap_utf8($body); file_put_contents('../_!!!_DEBUG-FINALmessageBody2-index.txt',var_export($messageBodyTEST0,1),FILE_APPEND); $messageBodyTEST1 = iconv($parameters['charset'], 'UTF-8//TRANSLIT', $messageBody); file_put_contents('../_!!!_DEBUG-FINALmessageBody3-index.txt',var_export($messageBodyTEST1,1),FILE_APPEND); $messageBodyTEST2 = mb_convert_encoding($messageBody, 'UTF-8', $parameters['charset']); file_put_contents('../_!!!_DEBUG-FINALmessageBody4-index.txt',var_export($messageBodyTEST2,1),FILE_APPEND);*/ // Передаем HTML-контент для проверки метатегов $charset = $this->detectCharset($messageBody, $messageBody);//TEST 28.05.25 // Принудительная проверка русских кодировок //$charset = $this->detectRussianEncoding($messageBody) ?: $this->detectCharset($messageBody, $messageBody); if (mb_check_encoding($messageBody, $charset)) { if(!is_null($charset)) $messageBody = mb_convert_encoding($messageBody, 'UTF-8', $charset); //file_put_contents('../_!!!_DEBUG-FINALmessageBody5-index.txt',var_export($messageBody,1),FILE_APPEND); } $parameters__charset=''; if(!empty($parameters['charset'])) $parameters__charset=$parameters['charset']; file_put_contents(__DIR__ .'/../../crm/__!!!_DEBUG-encodingUIDdetectCharset-index.txt',var_export( ['uid'=>$uid, 'encoding'=>$structure->encoding, 'detectCharset'=>$this->detectCharset($messageBody), 'parameterscharset'=>$parameters__charset,'charsetHTMLtag'=>$charset,'structure'=>$structure ],1),FILE_APPEND); if (!empty($parameters['charset']) && strtolower($parameters['charset']) !== 'UTF-8') { if (function_exists('mb_convert_encoding')) { if (!in_array($parameters['charset'], mb_list_encodings())) {//нет кодировки if ($structure->encoding === 0) { $parameters['charset'] = 'US-ASCII'; } /*else { if ($structure->encoding === 4) { //$parameters['charset'] = 'koi8-r'; $parameters['charset'] = $this->detectCharset($messageBody);//TEST 25.05.25 } if($parameters['charset']!='windows-1251' && $parameters['charset'] != 'koi8-r') $parameters['charset'] = 'UTF-8'; //$parameters['charset'] = $this->detectCharset($messageBody);//TEST 25.05.25 else{ //$body = imap_qprint($messageBody); //$messageBody = imap_utf8($body); //file_put_contents('../_!!!_DEBUG-encoding1111IMAP-index.txt',var_export($body,1),FILE_APPEND); //file_put_contents('../_!!!_DEBUG-encoding222IMAP-index.txt',var_export($messageBody,1),FILE_APPEND); $parameters['charset'] = $this->detectCharset($messageBody);//TEST 25.05.25 } //file_put_contents('../_!!!_DEBUG-parametersIMAP-index.txt',var_export($parameters['charset'],1),FILE_APPEND);//if($parameters['charset']=='koi8-r') //file_put_contents('../_!!!_DEBUG-encodingIMAP-index.txt',var_export($structure->encoding,1),FILE_APPEND);//if($parameters['charset']=='koi8-r') }*/ if($parameters['charset'] !== 'GBK' && $parameters['charset']!='windows-1251' && $parameters['charset'] != 'koi8-r')//03.06.25 добавил && $parameters['charset'] != 'koi8-r' $parameters['charset'] = $this->detectCharset($messageBody);// $parameters['charset'] = 'UTF-8'; } //file_put_contents('../_!!!_DEBUG-encodingIMAP11111111111111-index.txt',var_export($parameters['charset'],1),FILE_APPEND);//if($parameters['charset']=='koi8-r') if(is_null($charset))//if($parameters['charset']!='windows-1251' && $charset != 'Windows-1251') $messageBody = mb_convert_encoding($messageBody, 'UTF-8', $parameters['charset']); /*else $messageBody = iconv($parameters['charset'], 'UTF-8//TRANSLIT', $messageBody);*/ } else { $messageBody = iconv($parameters['charset'], 'UTF-8//TRANSLIT', $messageBody); } } if (strtolower($structure->subtype) === 'plain' || ($structure->type == 1 && strtolower($structure->subtype) !== 'alternative')) { $this->plaintextMessage = ''; $this->plaintextMessage .= trim(htmlentities($messageBody)); $this->plaintextMessage = nl2br($this->plaintextMessage); } elseif (strtolower($structure->subtype) === 'html') { $this->htmlMessage = ''; $this->htmlMessage .= $messageBody; } //file_put_contents('../_!!!_DEBUG-FINALmessageBody-index.txt',var_export($messageBody,1),FILE_APPEND); } if (isset($structure->parts)) { foreach ($structure->parts as $partIndex => $part) { $partId = $partIndex + 1; if (isset($partIdentifier)) $partId = $partIdentifier . '.' . $partId; $this->processStructure($uid, $part, $partId); } } } private function processCIDImages($html, $cidMapping) { foreach ($cidMapping as $cid => $fileInfo) { if (!empty($cid)) { // Формируем относительный путь к файлу //$filePath = $this->attachments_dir . '/' . $fileInfo['path'] . '/' . $fileInfo['file'];// /*$html = str_replace( 'cid:' . $cid, $this->attachments_dir . '/' . date('Y-m-d') . '/' . $fileInfo, //$fileInfo['file'] filename $html );*/ // Заменяем все вхождения CID /*$html = str_replace( 'src="cid:' . $cid . '"', 'src="' . $filePath . '"', $html );*/ $html = str_replace( 'cid:' . $cid, 'cid:'.$fileInfo['file'].'@'.$cid, $html ); } } //file_put_contents('../_!!!_DEBUG-cidMapping.txt',var_export($cidMapping,1),FILE_APPEND); return $html; } private function isAttachment($structure, $parameters) { // Проверяем по наличию имени файла или специальному subtype return (isset($parameters['name']) || isset($parameters['filename']) || (isset($structure->subtype) && strtolower($structure->subtype) == 'rfc822')); } private function getAttachmentFilename($parameters) { if (isset($parameters['filename*'])) { return $this->decode($parameters['filename*']); } elseif (isset($parameters['name*'])) { return $this->decode($parameters['name*']); } elseif (isset($parameters['filename'])) { return $parameters['filename']; } elseif (isset($parameters['name'])) { return $parameters['name']; } return uniqid() . '.dat'; // fallback } private function setFileName($text) { $this->filename = $this->decode($text); } /* * save attachments to directory */ private function saveToDirectory($uid, $partIdentifier) { //save attachments to directory $array = array(); $array['part'] = $partIdentifier; if(empty($this->filename)) $array['file'] = 'noname12.eml'; else $array['file'] = $this->filename; $array['encoding'] = $this->encoding; // Генерируем уникальное имя файла, если нужно /*$fileExt = pathinfo($this->filename, PATHINFO_EXTENSION); $uniqueName = uniqid() . ($fileExt ? '.' . $fileExt : ''); $array['filename_cid'] = $uniqueName;*/ //$array['filename_cid'] = $this->filename; // Сохраняем файл /*$r = [ 'uid' => $uid, 'part' => $partIdentifier, 'file' => $uniqueName, 'path' => date('Y-m-d'), 'encoding' => $this->encoding ];*/ //$this->getFiles($r);//path не известен ? return $array; } private function decodeMessage($data, $encoding) { if (!is_numeric($encoding)) { $encoding = strtolower($encoding); } switch (true) { # 8BIT case $encoding === 1: return quoted_printable_decode(imap_8bit($data)); # BINARY case $encoding === 2: return imap_binary($data); # BASE64 case $encoding === 'base64': case $encoding === 3: return base64_decode($data); # QUOTED-PRINTABLE case $encoding === 'quoted-printable': case $encoding === 4: return quoted_printable_decode($data); default: return $data; } } /*private function decodeMessage($data, $encoding) { // Декодируем содержимое $decoded = parent::decodeMessage($data, $encoding); // Дополнительная обработка кодировки if (!mb_check_encoding($decoded, 'UTF-8')) { $charset = $this->detectCharset($decoded); $decoded = mb_convert_encoding($decoded, 'UTF-8', $charset); } // Чистка невидимых символов return preg_replace('/[^\P{C}\n\r\t]+/u', '', $decoded); }*/ private function getParametersFromStructure($structure) { $parameters = array(); // Основные параметры if (isset($structure->parameters)) { foreach ($structure->parameters as $param) { $parameters[strtolower($param->attribute)] = $param->value; } } // Дополнительные параметры (для RFC 2231) if (isset($structure->dparameters)) { foreach ($structure->dparameters as $param) { $parameters[strtolower($param->attribute)] = $param->value; } } // Специально для Content-ID if (isset($structure->id)) { $parameters['id'] = $structure->id; } return $parameters; } private function getOverview($uid) { $results = imap_fetch_overview($this->imapStream, $uid, FT_UID); $messageOverview = array_shift($results); if (!isset($messageOverview->date)) { $messageOverview->date = null; } return $messageOverview; } private function decode($text) { if (null === $text) { return null;//что то сделать '' типо нет текста } $result = ''; foreach (imap_mime_header_decode($text) as $word) { $ch = 'default' === $word->charset ? 'ascii' : $word->charset; $result .= iconv($ch, 'utf-8', $word->text); //текстировать бывает ошибка //PHP Notice: iconv(): Detected an incomplete multibyte character in input string in line 237 //PHP Notice: iconv(): Detected an illegal character in input string in line 237 /* $encoding = mb_detect_encoding($text, mb_detect_order(), false); if($encoding == "UTF-8") { $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8'); } $out = iconv(mb_detect_encoding($text, mb_detect_order(), false), "UTF-8//IGNORE", $text);*/ } if($result=='') $result = $this->fallbackDecode($text);//'error5g9kd4uniqid'.date('Y-m-d'); return $result; } private function decodeHeader($text) { if (empty($text)) { return ''; } // Обработка MIME-заголовков (RFC 2047) if (preg_match('/=\?([^?]+)\?(Q|B)\?([^?]+)\?=/i', $text)) { $decoded = iconv_mime_decode($text, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); return $decoded !== false ? $decoded : $this->fallbackDecode($text); } return $this->fallbackDecode($text); } private function fallbackDecode($text) { // Попробуем определить кодировку $encoding = mb_detect_encoding($text, [ 'UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-1', 'ISO-8859-5' ], true); if ($encoding === false) { $encoding = 'auto';// 'Windows-1251'; // Дефолтная для русских писем } // Конвертируем в UTF-8 с обработкой ошибок return mb_convert_encoding($text, 'UTF-8', $encoding); } /*private function sanitizeFilename($filename) { // Декодируем заголовок $cleanName = $this->decodeHeader($filename); // Удаляем опасные символы $cleanName = preg_replace([ '/[^\p{L}\p{N}\s\-_\.]/u', // Разрешаем буквы, цифры, пробелы, -_. '/\s+/', // Множественные пробелы '/\.\.+/', // Множественные точки '/^-+/', // Дефисы в начале '/-+$/' // Дефисы в конце ], ['', ' ', '.', '', ''], $cleanName); // Обрезаем длинное имя $cleanName = mb_substr($cleanName, 0, 100); // Если после обработки имя пустое - генерируем if (empty($cleanName)) { return uniqid('file_') . '.dat'; } return $cleanName; } private function getAttachmentFilename($parameters) { $filename = ''; // Приоритеты получения имени файла $sources = [ $parameters['filename*'] ?? null, $parameters['name*'] ?? null, $parameters['filename'] ?? null, $parameters['name'] ?? null ]; foreach ($sources as $source) { if (!empty($source)) { $filename = $this->sanitizeFilename($source); break; } } // Если имя не найдено в параметрах if (empty($filename)) { $ext = $this->getFileExtension($parameters); return uniqid('file_') . ($ext ? '.' . $ext : ''); } return $filename; } private function getFileExtension($parameters) { // Определяем по Content-Type if (isset($parameters['type'])) { $mime = strtolower($parameters['type']); $map = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf' // ... дополните по необходимости ]; return $map[$mime] ?? null; } return null; }*/ /*private function normalizeCharset($charset) { //$charset = strtolower(trim($charset)); // Унифицируем распространённые варианты написания $aliases = [ 'koi8-r' => 'KOI8-R', 'koi8r' => 'KOI8-R', 'koi8u' => 'KOI8-U', 'windows-1251' => 'Windows-1251', 'win-1251' => 'Windows-1251', 'cp1251' => 'Windows-1251', 'iso-8859-5' => 'ISO-8859-5', 'iso8859-5' => 'ISO-8859-5', 'utf-8' => 'UTF-8', 'utf8' => 'UTF-8', 'shift_jis' => 'Shift_JIS', 'euc-jp' => 'EUC-JP', 'gb2312' => 'GB2312', 'big5' => 'BIG-5' ]; return $aliases[$charset] ?? strtoupper($charset); }*/ private function normalizeCharsetEncodings($charset) { //$charset = strtolower(trim($charset)); $extendedAliases = [ 'ibm866' => 'IBM866', 'cp866' => 'IBM866', 'dos-866' => 'IBM866' // ... остальные алиасы ]; // Специальные случаи, которые не совпадают с mb_list_encodings() $specialCases = [ 'koi8-r' => 'KOI8-R', 'koi8r' => 'KOI8-R', 'koi8u' => 'KOI8-U', 'windows-1251' => 'Windows-1251', 'win-1251' => 'Windows-1251', 'cp1251' => 'Windows-1251', 'iso-8859-5' => 'ISO-8859-5', 'iso8859-5' => 'ISO-8859-5', 'utf-8' => 'UTF-8', 'utf8' => 'UTF-8', 'shift_jis' => 'Shift_JIS', 'euc-jp' => 'EUC-JP', 'gb2312' => 'GB2312', 'big5' => 'BIG-5' ]; // Проверяем специальные случаи if (isset($specialCases[$charset])) { //return $specialCases[$charset] ?? strtoupper($charset); return $specialCases[$charset]; } // Получаем все поддерживаемые кодировки //$supportedEncodings = array_map('strtolower', mb_list_encodings()); // Ищем совпадение среди стандартных кодировок foreach (mb_list_encodings() as $enc) { if (strtolower($enc) === $charset) { return $enc; // Возвращаем в оригинальном регистре из mb_list_encodings() } } return null; // Неизвестная кодировка } private function isValidCharset($charset) { // Полный список поддерживаемых кодировок $supported = [ 'UTF-8', 'Windows-1251', 'KOI8-R', 'KOI8-U', 'ISO-8859-5', 'ISO-8859-1', 'Shift_JIS', 'EUC-JP', 'GB2312', 'BIG-5', 'Windows-1252', 'ISO-8859-2', 'ISO-8859-15' ]; return in_array($charset, $supported, true); } private function detectCharsetFromMeta($htmlContent) { if (preg_match('/<meta[^>]+charset=["\']?([^"\'\s>]+)/i', $htmlContent, $matches)) { $rawCharset = strtolower(trim($matches[1]));//trim($matches[1]); //$normalized = $this->normalizeCharset($rawCharset); $normalized = $this->normalizeCharsetEncodings($rawCharset); //какой то определенный стандарт кодировок! /*if ($this->isValidCharset($normalized)) { return $normalized; // Возвращаем в корректном регистре }*/ if ($normalized !== null) { // Лучше исключить эти псевдо-кодировки: $excluded = ['BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable']; //if ($normalized && in_array($normalized, mb_list_encodings())) return $normalized; } } return null; } /*private function detectCharset($text, $htmlContent = '') { // 1. Проверяем HTML-метатег если есть контент if (!empty($htmlContent) && preg_match('/<meta[^>]+charset=["\']?([^"\'\s>]+)/i', $htmlContent, $matches)) { $metaCharset = strtolower(trim($matches[1])); if (in_array($metaCharset, ['koi8-r', 'windows-1251', 'iso-8859-5'])) { return $metaCharset; } } // Автоопределение с приоритетом русских кодировок $charset = mb_detect_encoding($text, [ 'UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-5', 'ISO-8859-1', 'ASCII' ], true); return $charset ?: 'auto';//'Windows-1251'; }*/ private function detectCharset($text, $htmlContent = '') { // 1. Пробуем определить из метатега if (!empty($htmlContent)) { $metaCharset = $this->detectCharsetFromMeta($htmlContent); /*if ($metaCharset !== null) { return $metaCharset; // Уже в правильном регистре }*/ return $metaCharset; } // TEXT !!!!!!!! Проверка русских кодировок в первую очередь /*if ($russianEnc = $this->detectRussianEncoding($text)) { return $russianEnc; }*/ // 2. Автоопределение (существующая логика) //return $this->strictDetectEncoding($text); //НЕ ПРОБОВАЛ! По идеи лучше должно быть /*if ($exotic = $this->detectExoticEncoding($text)) { return $exotic; }*/ // Автоопределение с приоритетом русских кодировок $charset = mb_detect_encoding($text, [ 'UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-5', 'ISO-8859-1' ], true); return $charset ?: 'Windows-1251';//'auto';//'Windows-1251'; } private function isValidEncoding($text, $encoding) { // Конвертируем туда-обратно и сравниваем $converted = mb_convert_encoding( mb_convert_encoding($text, 'UTF-8', $encoding), $encoding, 'UTF-8' ); return $text === $converted; } private function strictDetectEncoding($text) { // Сначала проверяем русские кодировки if ($russianEnc = $this->detectRussianEncoding($text)) { return $russianEnc; } $encodings = ['UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-5', 'ISO-8859-1']; foreach ($encodings as $enc) { if ($this->isValidEncoding($text, $enc)) { return $enc; } } // Fallback с проверкой русских символов /*if (preg_match('/[\x80-\xFF]/', $text)) { return 'Windows-1251'; }*/ return 'UTF-8';//или лучше auto !!!!!! Пробовать тестить нужно } private function detectExoticEncoding($text) { // Специфические паттерны для редких кодировок $patterns = [ '/\xFD[\x80-\xFF]/' => 'IBM866', // Русская DOS-кодировка '/\xA3[\x80-\xFF]/' => 'KOI8-U', // Украинская KOI8 '/\xB3[\x80-\xFF]/' => 'ISO-8859-5' // Альтернативная кириллица ]; foreach ($patterns as $pattern => $encoding) { if (preg_match($pattern, $text)) { return $encoding; } } // Проверка через статистику символов $stats = count_chars($text, 1); $ranges = [ 'Windows-1251' => [192, 255], 'KOI8-R' => [224, 255], 'IBM866' => [128, 175] ]; foreach ($ranges as $enc => $range) { $matches = 0; foreach ($stats as $ord => $count) { if ($ord >= $range[0] && $ord <= $range[1]) { $matches += $count; } } if ($matches / strlen($text) > 0.3) { // 30% символов в диапазоне return $enc; } } return null; } private function detectRussianEncoding($text) { // Специальная проверка для повторяющихся Ї if (preg_match('/\xD2[\x80-\xFF]{5,}/', $text)) { // Ї в Windows-1251 return 'Windows-1251'; } // Fallback с проверкой русских символов if (preg_match('/[\x80-\xFF]/', $text)) { return 'Windows-1251'; } // Статистика символов для русских кодировок $ranges = [ 'Windows-1251' => [192, 255], // Основной диапазон кириллицы 'KOI8-R' => [224, 255], // KOI8-R специфичный диапазон 'IBM866' => [128, 175] // DOS-кодировка ]; $counts = []; foreach ($ranges as $enc => $range) { $counts[$enc] = 0; } // Анализируем каждый символ $length = strlen($text); for ($i = 0; $i < $length; $i++) { $ord = ord($text[$i]); foreach ($ranges as $enc => $range) { if ($ord >= $range[0] && $ord <= $range[1]) { $counts[$enc]++; } } } // Выбираем кодировку с максимальным совпадением arsort($counts); $bestMatch = key($counts); // Если найдено достаточное количество русских символов if ($counts[$bestMatch] > 0 && $counts[$bestMatch] / $length > 0.3) { return $bestMatch; } return null; } private function processAddressObject($addresses) { $outputAddresses = array(); if (is_array($addresses)) foreach ($addresses as $address) { if (property_exists($address, 'mailbox') && $address->mailbox != 'undisclosed-recipients') { $currentAddress = array(); $currentAddress['address'] = $address->mailbox . '@' . $address->host; if (isset($address->personal)) { $currentAddress['name'] = $this->decode($address->personal); } $outputAddresses = $currentAddress; } } return $outputAddresses; } private function getHeaders($uid) { $rawHeaders = $this->getRawHeaders($uid); $headerObject = imap_rfc822_parse_headers($rawHeaders); if (isset($headerObject->date)) { $headerObject->udate = strtotime($headerObject->date); } else { $headerObject->date = null; $headerObject->udate = null; } $this->headers = $headerObject; return $this->headers; } private function getRawHeaders($uid) { $rawHeaders = imap_fetchheader($this->imapStream, $uid, FT_UID); return $rawHeaders; } private function getStructure($uid) { $structure = imap_fetchstructure($this->imapStream, $uid, FT_UID); return $structure; } public function __destruct() { if (!empty($this->errors)) { foreach ($this->errors as $error) { //SAVE YOUR LOG OF ERRORS } } } }

Проблема возникает в методе decode() при обработке невалидных multibyte символов в заголовках писем. Вот несколько вариантов решения:

Вариант 1: Замена iconv на mb_convert_encoding (Рекомендуется)

Замените проблемный метод decode():

php
private function decode($text) {
    if (null === $text) {
        return null;
    }
    
    $result = '';
    $decodedParts = imap_mime_header_decode($text);
    
    if ($decodedParts === false || count($decodedParts) === 0) {
        return $this->fallbackDecode($text);
    }
    
    foreach ($decodedParts as $part) {
        $charset = $part->charset ?? 'us-ascii';
        
        // Пропускаем проблемные части
        if ($charset === 'default') {
            $charset = 'us-ascii';
        }
        
        try {
            // Используем mb_convert_encoding вместо iconv
            if (function_exists('mb_convert_encoding')) {
                $converted = mb_convert_encoding($part->text, 'UTF-8', $charset);
                if ($converted !== false) {
                    $result .= $converted;
                    continue;
                }
            }
            
            // Fallback на iconv с обработкой ошибок
            $converted = @iconv($charset, 'UTF-8//IGNORE//TRANSLIT', $part->text);
            if ($converted !== false) {
                $result .= $converted;
            } else {
                // Если все методы fail, используем исходный текст
                $result .= $part->text;
            }
        } catch (Exception $e) {
            // В случае ошибки используем fallback
            $result .= $this->fallbackDecode($part->text);
        }
    }
    
    return $result ?: $this->fallbackDecode($text);
}

Вариант 2: Улучшенная обработка ошибок с фильтрацией

php
private function decode($text) {
    if (null === $text) {
        return null;
    }
    
    $result = '';
    $decodedParts = imap_mime_header_decode($text);
    
    foreach ($decodedParts as $part) {
        $charset = ($part->charset === 'default') ? 'us-ascii' : $part->charset;
        
        // Фильтруем невалидные символы перед конвертацией
        $cleanText = $this->filterInvalidChars($part->text);
        
        try {
            $converted = $this->safeCharsetConversion($cleanText, $charset, 'UTF-8');
            $result .= $converted;
        } catch (Exception $e) {
            // Логируем ошибку, но продолжаем обработку
            error_log("Charset conversion failed: " . $e->getMessage());
            $result .= $cleanText;
        }
    }
    
    return $result ?: $this->fallbackDecode($text);
}

private function safeCharsetConversion($text, $fromCharset, $toCharset) {
    // Пробуем mb_convert_encoding сначала
    if (function_exists('mb_convert_encoding')) {
        $result = @mb_convert_encoding($text, $toCharset, $fromCharset);
        if ($result !== false && $result !== '') {
            return $result;
        }
    }
    
    // Затем iconv с флагами игнорирования ошибок
    $result = @iconv($fromCharset, $toCharset . '//IGNORE//TRANSLIT', $text);
    if ($result === false) {
        // Если все методы fail, возвращаем очищенный текст
        return $this->removeInvalidUtf8($text);
    }
    
    return $result;
}

private function filterInvalidChars($text) {
    // Удаляем невалидные UTF-8 последовательности
    $text = $this->removeInvalidUtf8($text);
    
    // Удаляем контрольные символы кроме табуляции и переноса строк
    $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
    
    return $text;
}

private function removeInvalidUtf8($text) {
    $regex = '/(
        [\xC0-\xC1] # Invalid UTF-8 Bytes
        | [\xF5-\xFF] # Invalid UTF-8 Bytes
        | \xE0[\x80-\x9F] # Overlong encoding of prior code point
        | \xF0[\x80-\x8F] # Overlong encoding of prior code point
        | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
        | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
        | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
        | (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
        | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
        | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
        | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
        | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
    )/x';
    
    return preg_replace($regex, '', $text);
}

Вариант 3: Простое решение с @ suppressor

Если хотите быстро решить проблему, просто добавьте подавление ошибок:

php
private function decode($text) {
    if (null === $text) {
        return null;
    }
    
    $result = '';
    foreach (imap_mime_header_decode($text) as $word) {
        $ch = 'default' === $word->charset ? 'ascii' : $word->charset;
        // Подавляем предупреждения
        $converted = @iconv($ch, 'utf-8', $word->text);
        $result .= ($converted !== false) ? $converted : $word->text;
    }
    
    return $result ?: $this->fallbackDecode($text);
}

Дополнительные улучшения

Также рекомендую добавить в метод fallbackDecode():

php
private function fallbackDecode($text) {
    if (empty($text)) {
        return '';
    }
    
    // Пробуем разные методы декодирования
    $methods = [
        function($t) { return mb_convert_encoding($t, 'UTF-8', 'auto'); },
        function($t) { return utf8_encode($t); },
        function($t) { return $t; } // последний fallback
    ];
    
    foreach ($methods as $method) {
        $result = @$method($text);
        if (!empty($result) && mb_check_encoding($result, 'UTF-8')) {
            return $result;
        }
    }
    
    return preg_replace('/[^\x20-\x7E]/', '', $text); // только ASCII
}

Рекомендация

Используйте Вариант 1 или Вариант 2, так как они обеспечивают лучшую обработку ошибок и не маскируют реальные проблемы. Вариант 3 подходит для быстрого фикса, но может скрывать другие проблемы с кодировками.

AI-generated, for reference only