Update CorreoImapService.php

This commit is contained in:
Lizandro Guarnizo
2026-04-25 15:10:02 -05:00
parent 0e5ff957eb
commit 88a42ec629
+38 -27
View File
@@ -171,12 +171,15 @@ class CorreoImapService
/** /**
* Descarga cabeceras + cuerpo de un rango de secuencia (ej: "1:5"). * Descarga cabeceras + cuerpo de un rango de secuencia (ej: "1:5").
*
* Usamos BODY.PEEK[1] en lugar de BODY.PEEK[TEXT]:
* - Para emails multipart/alternative (como Bancolombia), BODY[1] devuelve
* ÚNICAMENTE la parte text/plain, sin boundaries ni HTML.
* - Para emails simples (solo texto), BODY[1] también devuelve el cuerpo.
*/ */
private function fetchMessages(string $set): array private function fetchMessages(string $set): array
{ {
// Pedimos FLAGS, ENVELOPE y BODY para extraer remitente, asunto y cuerpo $raw = $this->command("FETCH {$set} (BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)] BODY.PEEK[1])");
$raw = $this->command("FETCH {$set} (FLAGS BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)] BODY.PEEK[TEXT])");
return $this->parseMessages($raw); return $this->parseMessages($raw);
} }
@@ -215,19 +218,22 @@ class CorreoImapService
$email['date'] = trim($m[1]); $email['date'] = trim($m[1]);
} }
// BODY: tomamos el texto entre las dos secciones BODY // BODY: la respuesta IMAP usa literales {N}\r\n<contenido>
// La respuesta tiene dos literales {N}\r\n<contenido> // Con dos secciones pedidas habrá al menos 3 partes al dividir por "}\r\n".
// La tercera parte es BODY[1] (texto plano directo).
$parts = preg_split('/\}\r?\n/', $block); $parts = preg_split('/\}\r?\n/', $block);
$rawBody = '';
if (count($parts) >= 3) { if (count($parts) >= 3) {
// La tercera sección suele ser el cuerpo de texto
$rawBody = $parts[2]; $rawBody = $parts[2];
// Limpiar hasta el fin del literal (antes del siguiente *) // Quitar el cierre del literal IMAP (\r\n) y la línea "OK"
$rawBody = preg_replace('/\r?\n\)\s*\*.*$/s', '', $rawBody); $rawBody = preg_replace('/\r?\n\s*\)\s*(\r?\n[A-Z0-9]+ OK.*)?$/s', '', $rawBody);
$rawBody = preg_replace('/\r?\n\)\s*[A-Z0-9]+ OK.*/s', '', $rawBody);
$email['body'] = $this->cleanBody(trim($rawBody));
} elseif (count($parts) === 2) { } elseif (count($parts) === 2) {
$rawBody = $parts[1]; $rawBody = $parts[1];
$rawBody = preg_replace('/\r?\n\).*$/s', '', $rawBody); $rawBody = preg_replace('/\r?\n\s*\).*$/s', '', $rawBody);
}
if ($rawBody !== '') {
$email['body'] = $this->cleanBody(trim($rawBody)); $email['body'] = $this->cleanBody(trim($rawBody));
} }
@@ -298,33 +304,38 @@ class CorreoImapService
private function cleanBody(string $body): string private function cleanBody(string $body): string
{ {
// ── 1. MIME multipart: extraer SOLO la parte text/plain ────────── // ── 1. Decoded quoted-printable ──────────────────────────────────
// Los emails de Bancolombia son multipart/alternative. // BODY.PEEK[1] retorna directamente el contenido de la parte (sin
// Detectamos el boundary en la primera línea --boundary // boundaries), pero puede estar codificado en quoted-printable.
if (preg_match('/^--([^\r\n]+)/m', $body, $bm)) {
$plainPart = $this->extractMimePlainPart($body, $bm[1]);
if ($plainPart !== null) {
$body = $plainPart;
}
}
// ── 2. Decodificar quoted-printable ──────────────────────────────
if (preg_match('/=[0-9A-F]{2}/i', $body) || preg_match('/=\r?\n/m', $body)) { if (preg_match('/=[0-9A-F]{2}/i', $body) || preg_match('/=\r?\n/m', $body)) {
$body = quoted_printable_decode($body); $body = quoted_printable_decode($body);
} }
// ── 3. Eliminar cabeceras MIME residuales ─────────────────────── // ── 2. Si aún hay boundaries MIME residuales, extraer text/plain
if (preg_match('/^--([^\r\n]+)/m', $body, $bm)) {
$plain = $this->extractMimePlainPart($body, $bm[1]);
if ($plain !== null) {
// Decodificar QP en la parte extraída también
if (preg_match('/=[0-9A-F]{2}/i', $plain) || preg_match('/=\r?\n/m', $plain)) {
$plain = quoted_printable_decode($plain);
}
$body = $plain;
}
}
// ── 3. Eliminar cabeceras MIME residuales de parte ───────────────
// (Content-Type:, Content-Transfer-Encoding:, etc.)
$body = preg_replace('/^(Content-[^\r\n]+\r?\n)+/mi', '', $body); $body = preg_replace('/^(Content-[^\r\n]+\r?\n)+/mi', '', $body);
// ── 4. Limpiar URLs de imágenes entre corchetes: "Logo [http://...]" // ── 4. Limpiar URLs entre corchetes: "Logo [http://...]" ─────────
$body = preg_replace('/\[https?:\/\/[^\]]*\]/i', '', $body); $body = preg_replace('/\[https?:\/\/[^\]]*\]/i', '', $body);
// ── 5. Si aún contiene HTML (fallback) quitar etiquetas ────────── // ── 5. Fallback HTML: si queda HTML, quitar etiquetas ────────────
if (preg_match('/<[a-z][\s>]/i', $body)) { if (preg_match('/<[a-z][\s>]/i', $body)) {
$body = self::stripHtmlFull($body); $body = self::stripHtmlFull($body);
} }
// ── 6. Normalizar espacios y líneas ────────────────────────────── // ── 6. Normalizar saltos de línea y espacios ─────────────────────
$body = str_replace(["\r\n", "\r"], "\n", $body); $body = str_replace(["\r\n", "\r"], "\n", $body);
$body = preg_replace('/\n{3,}/', "\n\n", $body); $body = preg_replace('/\n{3,}/', "\n\n", $body);