feat(correo): SMTP desde la configuracion del panel, y fuera el proxy

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=<el del panel>", 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) <noreply@anthropic.com>
This commit is contained in:
Jordan Diaz
2026-09-03 20:34:05 +00:00
co-authored by Claude Opus 5
parent 3615d85bc0
commit e4ab0567df
+68 -66
View File
@@ -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<strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)+ord($keychar));
$result.=$char;
/**
* Vuelca las credenciales SMTP de la tabla `configuracion` sobre la clase.
*
* La tabla lleva desde siempre los campos email_smtp, email_host,
* email_port, email_secure, email_username, email_password, email_from y
* email_from_name... y NADIE los leia: estaban solo en el schema, o sea que
* el panel te dejaba rellenarlos, se guardaban en la BD y el envio seguia
* usando los defaults de esta clase (con la contrasena VACIA) o lo que
* hubiera a mano en lib/variables.php. Un formulario decorativo.
*
* El check `email_smtp` es quien manda, que es como se lee la interfaz:
* marcado -> 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="") {
if (!@$configuracionRecord["email_smtp"] || !@$configuracionRecord["email_host"]) return;
$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
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 = "