PHP QQ邮箱Imap
<?php
namespace app\common\email;
class QqEmailImap
{
protected $host = '{imap.qq.com:993/imap/ssl}'; // IMAP服务器地址
protected $username; // 邮箱地址,例如:xxx@qq.com
protected $password; // 授权码
protected $connection;
protected $currentFolder = 'INBOX'; // 默认是收件箱
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
/**
* 测试连接并登录
*/
public function connect()
{
$this->connection = @imap_open($this->host . $this->currentFolder, $this->username, $this->password);
if (!$this->connection) {
return false;
}
return true;
}
/**
* 切换邮箱文件夹
*/
public function selectFolder($folder)
{
$this->currentFolder = $folder;
if ($this->connection) {
return imap_reopen($this->connection, $this->host . $folder);
}
return $this->connect();
}
public function listFolders(): array
{
if (!$this->connection) {
return [];
}
$folders = imap_list($this->connection, $this->host, '*');
if (!is_array($folders)) {
return [];
}
$y = [];
$j = array_map(function ($folder) use (&$y) {
// 提取文件夹名部分
$pos = strrpos($folder, '}');
$encodedName = $pos !== false ? substr($folder, $pos + 1) : $folder;
$y[] = $encodedName;
// 尝试多种方式解码 Modified UTF-7
$decoded = $this->decodeImapFolderName($encodedName);
return $decoded ?: $encodedName;
}, $folders);
return [
'y' => $y, // 原始
'j' => $j, // 解码
];
}
/**
* 获取邮件总数
*/
public function getTotalEmails(): int
{
if (!$this->connection) {
return 0;
}
$num = imap_num_msg($this->connection);
return $num ?: 0;
}
/**
* 分页获取邮件列表
*/
public function getEmailsByPage(int $page = 1, int $pageSize = 10, bool $newestFirst = true): array
{
if (!$this->connection) {
return [];
}
$total = $this->getTotalEmails();
$emails = [];
if ($total === 0) {
return [];
}
$totalPages = ceil($total / $pageSize);
$page = max(1, min($page, $totalPages));
if ($newestFirst) {
// 最新在前
$start = max(1, $total - ($page - 1) * $pageSize);
$end = max(1, $total - $page * $pageSize + 1);
for ($i = $start; $i >= $end; $i--) {
$overview = imap_fetch_overview($this->connection, $i, 0);
if (!empty($overview)) {
$emails[] = $overview[0];
}
}
} else {
// 最老在前
$start = ($page - 1) * $pageSize + 1;
$end = min($page * $pageSize, $total);
for ($i = $start; $i <= $end; $i++) {
$overview = imap_fetch_overview($this->connection, $i, 0);
if (!empty($overview)) {
$emails[] = $overview[0];
}
}
}
return $emails;
}
/**
* 获取某封邮件的详细内容
*/
public function getEmailContent($msgNumber)
{
$structure = imap_fetchstructure($this->connection, $msgNumber);
if (!$structure) {
return '无法获取邮件结构';
}
// 调试用:输出整个邮件结构
// dump($structure);
if (empty($structure->parts)) {
// 单部分邮件
$content = imap_body($this->connection, $msgNumber);
$content = $this->decodeContent($content, $structure->encoding);
return $content;
} else {
// 多部分邮件:遍历找到第一个 text/plain 或 text/html
foreach ($structure->parts as $partNum => $partStructure) {
$partNum++;
if (($partStructure->type == 0 || $partStructure->type == 1) &&
stristr($partStructure->subtype, 'HTML') === false) {
// text/plain 类型
$content = imap_fetchbody($this->connection, $msgNumber, $partNum);
return $this->decodeContent($content, $partStructure->encoding);
}
if (($partStructure->type == 0 || $partStructure->type == 1) &&
stristr($partStructure->subtype, 'HTML') !== false) {
// 如果没找到 text/plain,尝试返回 HTML 正文
$content = imap_fetchbody($this->connection, $msgNumber, $partNum);
return $this->decodeContent($content, $partStructure->encoding);
}
}
}
return '未找到正文内容';
}
protected function decodeContent($content, $encoding)
{
switch ($encoding) {
case 3: // base64
$content = base64_decode($content);
break;
case 4: // quoted-printable
$content = quoted_printable_decode($content);
break;
default:
// 不做处理
break;
}
// 自动检测并转为 UTF-8
if (mb_detect_encoding($content, 'UTF-8', true) === false) {
$content = mb_convert_encoding($content, 'UTF-8', 'UTF-7, UTF-8, ASCII, GBK, GB2312', 'ignore');
}
return $content;
}
/**
* 解析邮件内容(支持多部分邮件)
*/
protected function decodeEmailBody($structure, $msgNumber)
{
if (isset($structure->parts)) {
foreach ($structure->parts as $partNum => $partStructure) {
$partNum++;
$data = imap_fetchbody($this->connection, $msgNumber, $partNum);
if ($partStructure->type == 0) {
if ($partStructure->encoding == 3) {
$data = base64_decode($data);
} elseif ($partStructure->encoding == 4) {
$data = quoted_printable_decode($data);
}
return $data;
}
}
}
return '';
}
/**
* 获取某封邮件的所有附件信息
*/
public function getAttachments($msgNumber): array
{
$structure = imap_fetchstructure($this->connection, $msgNumber);
if (!$structure) {
return [];
}
return $this->findAttachments($structure, $msgNumber);
}
protected function findAttachments($structure, $msgNumber, $prefix = ''): array
{
$attachments = [];
if (isset($structure->parts)) {
foreach ($structure->parts as $index => $part) {
$partNum = $prefix ? $prefix . '.' . ($index + 1) : ($index + 1);
// 判断是否为附件
if ($part->type == 5 || (isset($part->disposition) && strtolower($part->disposition) === 'attachment')) {
$filename = $this->getPartFilename($part);
$encoding = $part->encoding ?? 0;
$content = imap_fetchbody($this->connection, $msgNumber, $partNum);
switch ($encoding) {
case 3: // base64
$content = base64_decode($content);
break;
case 4: // quoted-printable
$content = quoted_printable_decode($content);
break;
}
$attachments[] = [
'filename' => $filename,
'content_type' => $this->getContentType($part),
'content' => $content,
'size' => strlen($content),
'part_num' => $partNum,
];
}
// 递归处理子结构
if (!empty($part->parts)) {
$attachments = array_merge($attachments, $this->findAttachments($part, $msgNumber, $partNum));
}
}
}
return $attachments;
}
protected function getPartFilename($part): string
{
$filename = '';
if (!empty($part->dparameters)) {
foreach ($part->dparameters as $object) {
if (strtolower($object->attribute) === 'filename') {
$filename = $object->value;
break;
}
}
}
if (!$filename && !empty($part->parameters)) {
foreach ($part->parameters as $object) {
if (strtolower($object->attribute) === 'name') {
$filename = $object->value;
break;
}
}
}
// 解码 UTF-8 文件名(可能被 RFC2231 编码)
return $this->decodeRFC2231($filename);
}
protected function decodeRFC2231($string)
{
if (preg_match("/^(.*?)''(.*?)$/i", $string, $matches)) {
$charset = $matches[1];
$encoded = $matches[2];
return rawurldecode($encoded);
}
return $string;
}
protected function getContentType($part): string
{
$primary_mime_type = [
"TEXT",
"MULTIPART",
"MESSAGE",
"APPLICATION",
"AUDIO",
"IMAGE",
"VIDEO",
"OTHER"
];
$contentType = $primary_mime_type[$part->type] ?? "OTHER";
if (!empty($part->subtype)) {
$contentType .= "/" . strtoupper($part->subtype);
}
return $contentType;
}
/**
* 自定义 IMAP 文件夹名解码函数(兼容 Modified UTF-7)
*/
public function decodeImapFolderName(string $encoded): string
{
// 方法 1: 使用 imap 内置解码
$decoded = @imap_utf7_decode($encoded);
if (mb_detect_encoding($decoded, 'UTF-8', true)) {
return $decoded;
}
// 方法 2: 手动替换 Modified UTF-7 中的特殊编码
$modified = preg_replace_callback('/&([^-~]*)-/', function ($matches) {
$utf16be = base64_decode(str_pad(strtr($matches[1], '-_', '+/'), strlen($matches[1]) % 4, '=', STR_PAD_RIGHT));
return mb_convert_encoding($utf16be, 'UTF-8', 'UTF-16BE');
}, $encoded);
// 方法 3: 如果还是没解出来,尝试从 UTF-7 转换到 UTF-8
if (mb_detect_encoding($modified, 'UTF-8', true) === false) {
$modified = mb_convert_encoding($modified, 'UTF-8', 'UTF-7');
}
return $modified;
}
/**
* 邮件内容解码函数
*/
public function decodeEmailContent($content, $charset = null)
{
// 如果没有指定字符集,尝试自动检测
if (empty($charset)) {
$charset = mb_detect_encoding($content, array('UTF-8', 'GBK', 'GB2312', 'ASCII', 'BIG5'), true);
}
// 如果检测不出字符集,默认使用 UTF-8
if (!$charset) {
$charset = 'UTF-8';
}
// 转换到 UTF-8
if ($charset != 'UTF-8') {
$content = mb_convert_encoding($content, 'UTF-8', $charset);
}
// 去除多余空白
$content = trim($content);
return $content;
}
/**
* 关闭连接
*/
public function close()
{
if ($this->connection) {
imap_close($this->connection);
}
}
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭