From e4ab0567df8ba99ed65d7a3760e1ab857b1ee32b Mon Sep 17 00:00:00 2001 From: Jordan Diaz Date: Thu, 3 Sep 2026 20:34:05 +0000 Subject: [PATCH] feat(correo): SMTP desde la configuracion del panel, y fuera el proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dos cambios que van juntos porque atacan el mismo fallo: en las webs de Forge no salia ni un correo. 1. LOS CAMPOS DEL PANEL AHORA SIRVEN PARA ALGO La tabla `configuracion` lleva desde siempre email_smtp, email_host, email_port, email_secure, email_username, email_password, email_from y email_from_name... y no los leia NADIE. Aparecian solo en el schema: el panel te dejaba rellenarlos, se guardaban en la BD, y el envio seguia usando los defaults de la clase —con la contrasena VACIA— o lo que hubiera a mano en lib/variables.php. Un formulario decorativo. `cargarSmtpDeConfiguracion()` los vuelca sobre la clase cuando el check email_smtp esta marcado y email_host tiene valor. Se llama desde send() antes de enviar, no solo desde send_email(). El schema de `configuracion` es PER-WEB y no todas tienen esas columnas (villagrancanaria no las tiene): se lee con @ sobre el array, asi que una web sin ellas se comporta exactamente igual que antes. 2. FUERA EL PROXY send_email_coco_proxy() mandaba a cocosolution.com/?sendQuantumEmail=1. Se quita por dos motivos medidos hoy: - Ese endpoint RECHAZA TODO. Probados seis formatos (JSON crudo, form-urlencoded, anidado, y el payload cifrado por tres vias) y siempre contesta {"error":1,"message":"Need SMTP data and Params"}, incluso con smtp_data completo. Es decir: ninguna web de Forge ha podido enviar por ahi, y eso es anterior a los cambios de hoy. - No hace falta. El container alcanza los SMTP por su cuenta: verificado contra smtp.gmail.com:465 y :587 y smtp.ionos.es:465, con banner 220. PHPMailer conecta solo cuando $smtp = true. Se va con el encrypt(), que solo usaba el proxy y en una linea que ya estaba comentada dentro de esa misma funcion. Cero llamadas a ambas fuera de la clase, comprobado en los cuatro arboles. Verificado en cdn-ecommerce: con el check marcado, la traza pasa de "smtp=no host=smtp.gmail.com pass=VACIA" a "smtp=si host=", y PHPMailer intenta la conexion. Falta solo poner credenciales de verdad. OJO, queda pendiente: send_email() devuelve NULL siempre, tanto si entrega como si no. Un fallo de envio es hoy indistinguible de un exito. Co-Authored-By: Claude Opus 5 (1M context) --- cms/lib/classes/CocoEmail.php | 136 +++++++++++++++++----------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/cms/lib/classes/CocoEmail.php b/cms/lib/classes/CocoEmail.php index a11f8f2..0a47583 100755 --- a/cms/lib/classes/CocoEmail.php +++ b/cms/lib/classes/CocoEmail.php @@ -163,35 +163,30 @@ class CocoEmail { if (!$subject && $record) $subject = t($record, self::$field_subject); - // El correo sale por dos caminos segun el entorno, y esta era la unica - // divergencia real entre el core de Forge y el de las webs legacy: - // web-base mandaba SIEMPRE por el proxy y core_client SIEMPRE directo, - // asi que el fichero no se podia unificar sin cambiarle el - // comportamiento a media plataforma. Con la condicion delante, el mismo - // fichero vale para los dos canales. - // - // En local/test hace falta el proxy: el envio directo no sale de ahi. - // En produccion se mantiene el directo, que es lo que han hecho siempre - // esas webs. - // - // Mismo criterio de deteccion que header.php:81 — un `.acai` en la raiz - // de la web marca entorno local. Se comprueba en linea a proposito, sin - // depender de que otro fichero haya definido nada (ver el commit - // f81c5a2 de web-base, "el entorno se detecta en linea"). - // Desde cms/lib/classes/ la raiz esta tres niveles arriba. - $esEntornoLocal = is_file(dirname(__FILE__) . '/../../../.acai'); + // Si la web tiene SMTP configurado (panel o variables.php), esas + // credenciales mandan. Va aqui, antes del bucle, para que valga tanto si + // se envia una vez como a varios destinatarios. + self::cargarSmtpDeConfiguracion(); - // Se devuelve array en los dos caminos. El directo no devolvia nada - // (core_client) y el del proxy si (web-base): unificar hacia el array es - // aditivo — quien ignoraba el retorno lo sigue ignorando. + // Envio DIRECTO, siempre. Aqui hubo un proxy (send_email_coco_proxy, a + // cocosolution.com/?sendQuantumEmail=1) que se quito el 2026-09-03 por + // dos motivos comprobados: + // + // 1. Su endpoint rechaza TODO. Probados seis formatos —JSON crudo, + // form-urlencoded, anidado, y el payload cifrado por tres vias + // distintas— y siempre contesta {"error":1,"message":"Need SMTP + // data and Params"}, incluso con smtp_data completo. + // 2. No hacia falta: el container de las webs alcanza los servidores + // SMTP por su cuenta (verificado contra smtp.gmail.com:465 y :587 y + // smtp.ionos.es:465, con banner 220). PHPMailer conecta solo. + // + // Con $smtp = true y credenciales, PHPMailer habla SMTP directo y no + // necesita MTA local. Sin credenciales cae a mail(), que en un hosting + // normal entrega por su postfix y en el container de Forge NO puede + // (no hay /usr/sbin/sendmail): ahi la unica salida es configurar el SMTP. $result = []; foreach ($to as $destinatario) { - if ($esEntornoLocal) { - $resultString = self::send_email_coco_proxy($destinatario, $subject, $content); - $result[] = @json_decode($resultString,true) ?: ["success" => false, "message" => "Error decoding response", "raw_response" => $resultString]; - } else { - $result[] = self::send_email($destinatario, $subject, $content); - } + $result[] = self::send_email($destinatario, $subject, $content); } self::$attach_files = []; return $result; @@ -282,50 +277,55 @@ class CocoEmail { }, array_change_key_case($arr)); } - function encrypt($string, $key) { - $result = ''; - for($i=0; $i SMTP con estas credenciales + * sin marcar -> no se toca nada y el envio sale por mail() del sistema + * + * Se exige ADEMAS que email_host tenga valor: un check marcado con los + * campos vacios dejaria a PHPMailer sin servidor, y fallar por una + * configuracion a medias es peor que no aplicarla. + * + * OJO: el schema de `configuracion` es PER-WEB y no todas las webs tienen + * estas columnas (villagrancanaria, por ejemplo, no las tiene). Por eso se + * lee con @ sobre el array: si la columna no existe la clave no esta, el if + * no entra y esa web se comporta igual que siempre. + * + * Se llama desde send() ANTES de elegir camino, no solo desde send_email(): + * en las webs de Forge el envio sale por el proxy y send_email() no llega a + * ejecutarse nunca, asi que ponerlo solo alli dejaba fuera justo a las webs + * que lo necesitan. El proxy tambien reenvia con este smtp_data. + */ + static function cargarSmtpDeConfiguracion() { + global $configuracionRecord; + + if (!isset($configuracionRecord)){ + $configuracionRecord = @CocoDB::get("configuracion","num != 0")[0]; } - return base64_encode($result); - } - static function send_email_coco_proxy($destinatario="soporte@cocosolution.com", $asunto="Error al enviar correo", $contenido="", $respuesta="") { - - $data = [ - "smtp_data" => self::$smtp_data, - "mail_data" => self::$mail_data, - "replyTo" => self::$replyTo, - "send_copy_to" => self::$send_copy_to, - "params" => [ - "to" => $destinatario, - "subject" => $asunto, - "body" => $contenido - ], - "bloqued_emails" => self::$bloqued_emails, - "encode_with_base64" => self::$encode_with_base64, - "template" => self::$template, - "styles" => self::$styles, - "header" => self::$header, - "footer" => self::$footer + if (!@$configuracionRecord["email_smtp"] || !@$configuracionRecord["email_host"]) return; + + self::$smtp = true; + self::$smtp_data = [ + "host" => $configuracionRecord["email_host"], + "secure" => @$configuracionRecord["email_secure"] ?: "ssl", + "port" => @$configuracionRecord["email_port"] ?: 465, + "username" => @$configuracionRecord["email_username"], + "password" => @$configuracionRecord["email_password"], + "from" => @$configuracionRecord["email_from"] ?: @$configuracionRecord["email_username"], + "from_name" => @$configuracionRecord["email_from_name"] ?: @$configuracionRecord["tienda_nombre_empresa"], ]; - - //$encryptData = self::encrypt(json_encode($data), "Analiticaempresas17"); - - $opts = array('http' => - array( - 'method' => 'POST', - 'header' => 'Content-Type: application/json', - 'content' => json_encode($data) - ) - ); - - $context = stream_context_create($opts); - $result = file_get_contents("https://cocosolution.com/?sendQuantumEmail=1", false, $context); - return $result; } static function send_email($destinatario="soporte@cocosolution.com", $asunto="Error al enviar correo", $contenido="", $respuesta="") { @@ -337,6 +337,8 @@ class CocoEmail { $configuracionRecord = @CocoDB::get("configuracion","num != 0")[0]; } + self::cargarSmtpDeConfiguracion(); + require_once __DIR__ . '/../vendor/PHPMailer/PHPMailerAutoload.php'; /*$mensaje = "