Files
whatsapp/api/get_user_messages.php
T
2026-01-24 08:48:58 -05:00

226 lines
9.5 KiB
PHP

<?php
/**
* API - Obtener mensajes de un usuario específico
* Fecha: 4 de enero de 2026
*/
require_once '../config/config.php';
// Verificar autenticación
requireAuthentication();
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$userId = intval($_GET['user_id'] ?? 0);
if (!$userId) {
http_response_code(400);
echo json_encode(['error' => 'user_id es requerido']);
exit;
}
// Paginación: limit y before (timestamp) - cargamos N mensajes anteriores a 'before'
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 50;
$limit = max(1, min(200, $limit));
$before = !empty($_GET['before']) ? $_GET['before'] : null; // expect timestamp string
// Validar formato de 'before' si se proporcionó (evitar SQL/parse errors por input malformado)
if (!is_null($before)) {
// Aceptar formato 'YYYY-MM-DD HH:MM:SS' (espacio entre fecha y hora)
if (!preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $before)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'before timestamp inválido']);
exit;
}
}
$db = Database::getInstance();
// Construir consulta: obtener mensajes ordenados DESC (más recientes primero) y limitar
// Evitar ambigüedades prefijando columnas con el alias de la tabla (c)
$sql = "SELECT
c.id as id,
c.user_id as user_id,
c.content as content,
c.message_id as message_id,
c.media_url,
u.phone_number as user_phone,
c.direction as direction,
c.message_type as message_type,
c.status as status,
c.created_at as created_at,
COALESCE(c.is_read, 0) as is_read,
c.reply_to_message_id as reply_to_message_id,
c.reaction_emoji as reaction_emoji,
c.reaction_to_message_id as reaction_to_message_id
FROM conversations c
LEFT JOIN users u ON c.user_id = u.id
WHERE c.user_id = :user_id";
// Soporte para paginación estable: before (timestamp) y before_id (id del mensaje más antiguo del bloque)
$beforeId = isset($_GET['before_id']) ? intval($_GET['before_id']) : null;
$params = ['user_id' => $userId];
if ($before) {
$sql .= " AND (c.created_at < :before";
$params['before'] = $before;
if ($beforeId) {
// Incluir mensajes con mismo timestamp pero id menor (paginación estable)
$sql .= " OR (c.created_at = :before AND c.id < :before_id)";
$params['before_id'] = $beforeId;
}
$sql .= ")";
}
$sql .= " ORDER BY c.created_at DESC, c.id DESC LIMIT " . $limit;
// Log params/consulta para diagnóstico si algo falla (no sensible)
error_log('get_user_messages.php - params: ' . json_encode(['user_id' => $userId, 'limit' => $limit, 'before' => $before]));
error_log('get_user_messages.php - executing SQL: ' . $sql . ' params: ' . json_encode($params));
$conversations = $db->fetchAll($sql, $params);
// Si no hay mensajes, intentar fallback si se pidió paginación con 'before'
if (empty($conversations) && $before) {
error_log('get_user_messages.php - initial query returned empty, attempting fallback with inclusive <= for before param');
try {
$sqlFallback = str_replace('c.created_at < :before', 'c.created_at <= :before', $sql);
$conversationsFallback = $db->fetchAll($sqlFallback, $params);
if (!empty($conversationsFallback)) {
error_log('get_user_messages.php - fallback query returned ' . count($conversationsFallback) . ' rows');
$conversations = $conversationsFallback;
}
} catch (Exception $e) {
error_log('get_user_messages.php - fallback query failed: ' . $e->getMessage());
}
}
// Si todavía no hay mensajes, devolver array vacío
if (empty($conversations)) {
echo json_encode(['success' => true, 'data' => [], 'has_more' => false], JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);
exit;
}
// Queremos devolver los mensajes en orden cronológico ascendente para la UI
$conversations = array_reverse($conversations);
// Recalcular usando el resultado final para decidir paginación
$finalCount = is_array($conversations) ? count($conversations) : 0;
$hasMore = ($finalCount > 0 && $finalCount == $limit) ? true : false;
// Si no hay mensajes en conversations, intentar con conversaciones (compatibilidad antigua)
if (empty($conversations)) {
$conversations = $db->fetchAll(
"SELECT
id,
user_id,
message_text as content,
direction,
message_type,
status,
created_at
FROM conversations
WHERE user_id = :user_id
ORDER BY created_at ASC",
['user_id' => $userId]
);
}
// Formatear fechas y limpiar datos
$conversations = array_map(function($msg) {
$external = (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null;
$contentStr = $msg['content'] ?? '';
if (!$external && $contentStr) {
$j = json_decode($contentStr, true);
if (is_array($j)) {
foreach (['image','document','audio','video'] as $k) {
if (isset($j[$k]) && is_array($j[$k])) {
if (!empty($j[$k]['link'])) { $external = $j[$k]['link']; break; }
if (!empty($j[$k]['url'])) { $external = $j[$k]['url']; break; }
if (!empty($j[$k]['id'])) { $external = "api/get_media.php?id=" . urlencode($j[$k]['id']); break; }
}
}
if (!$external && !empty($j['link'])) $external = $j['link'];
}
}
return [
'id' => intval($msg['id']),
'user_id' => intval($msg['user_id']),
'content' => $msg['content'] ?? '',
'message_id' => $msg['message_id'] ?? null,
'media_url' => null,
'local_file' => $msg['local_file'] ?? null,
'local_thumb' => $msg['local_thumb'] ?? null,
'media_url_external' => $external,
'filename' => $msg['filename'] ?? null,
'mime_type' => $msg['mime_type'] ?? null,
'user_phone' => $msg['user_phone'] ?? null,
'direction' => $msg['direction'] ?? 'incoming',
'message_type' => $msg['message_type'] ?? 'text',
'status' => $msg['status'] ?? 'sent',
'created_at' => $msg['created_at'],
'reply_to_message_id' => $msg['reply_to_message_id'] ?? null,
'reaction_emoji' => $msg['reaction_emoji'] ?? null,
'reaction_to_message_id' => $msg['reaction_to_message_id'] ?? null
];
}, $conversations);
// earliest message timestamp (para paginación hacia atrás)
$earliest = $conversations[0]['created_at'] ?? null;
$earliestId = $conversations[0]['id'] ?? null;
// Si usamos fallback y el earliest coincide o es >= al 'before' solicitado,
// decrementamos 1 segundo para evitar que la próxima petición repita el mismo bloque
$fallbackUsed = (isset($conversationsFallback) && !empty($conversationsFallback));
if ($fallbackUsed && $earliest && $before) {
$earliest_ts = strtotime($earliest);
$before_ts = strtotime($before);
if ($earliest_ts !== false && $before_ts !== false && $earliest_ts >= $before_ts) {
// restar 1 segundo
$earliest = date('Y-m-d H:i:s', $earliest_ts - 1);
error_log('get_user_messages.php - adjusted earliest to avoid duplicate pagination: ' . $earliest);
}
}
// Recalculate final_count for debug
$finalCount = is_array($conversations) ? count($conversations) : 0;
// Añadir debug information cuando se solicita (solo con ?debug=1)
$debugInfo = null;
if (!empty($_GET['debug']) && $_GET['debug'] == '1') {
$debugInfo = [
'original_sql' => substr($sql, 0, 2000),
'params' => $params,
'final_count' => $finalCount,
'fallback_used' => $fallbackUsed,
'fallback_count' => $fallbackUsed ? count($conversationsFallback) : 0
];
}
$out = ['success' => true, 'data' => $conversations, 'has_more' => $hasMore, 'earliest' => $earliest, 'earliest_id' => $earliestId];
if ($debugInfo) $out['debug'] = $debugInfo;
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);
} catch (Exception $e) {
// Log detallado con stack trace para diagnóstico
error_log("Error in get_user_messages.php: " . $e->getMessage() . "\n" . $e->getTraceAsString());
http_response_code(500);
// Responder con detalle sólo si se pasa ?debug=1 (NO activar en producción)
if (!empty($_GET['debug']) && $_GET['debug'] == '1') {
echo json_encode([
'error' => 'Error interno del servidor',
'details' => $e->getMessage(),
'trace' => explode("\n", $e->getTraceAsString())
]);
} else {
echo json_encode(['error' => 'Error interno del servidor']);
}
}
?>