Files
lastwar/create_message.php
nickpons666 3519853bb6 Fix: Preservar saltos de línea en traducciones de Telegram
- El patrón /\s+/ estaba colapsando TODOS los espacios en blanco incluyendo \n
- Cambiar a /[ \t]+/ para colapsar solo espacios horizontales
- Agregar limpieza de saltos de línea múltiples preservando estructura
- Aplicado en: create_message.php, process_queue.php, discord_bot.php, telegram_bot_webhook.php

Soluciona: 'Hola a todos, El orden de la lista\n\nMiguel\n\nnickpons\n\nLuis'
se traducía como 'Olá a todos, A ordem da listaMiguelnickponsLuis' (sin saltos)
2026-02-19 15:58:39 -06:00

431 lines
18 KiB
PHP
Executable File

<?php
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/session_check.php';
checkSession();
require_once __DIR__ . '/includes/message_handler.php';
require_once __DIR__ . '/includes/schedule_helpers.php';
require_once __DIR__ . '/common/helpers/sender_factory.php';
require_once __DIR__ . '/common/helpers/converter_factory.php';
function getTranslationButtons(PDO $pdo, string $text): array
{
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
$languages = $stmt->fetchAll();
if (count($languages) <= 1) {
return [];
}
return [
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
'discord' => buildDiscordTranslationButtons($languages, $text)
];
}
function buildTelegramTranslationButtons(PDO $pdo, array $languages, string $text): array
{
if (count($languages) <= 1) {
return [];
}
// Guardar texto en la base de datos con hash consistente
$textHash = md5($text);
$stmt = $pdo->prepare("INSERT INTO translation_cache (text_hash, original_text) VALUES (?, ?) ON DUPLICATE KEY UPDATE original_text = VALUES(original_text)");
$stmt->execute([$textHash, $text]);
$buttons = [];
foreach ($languages as $lang) {
$buttons[] = [
'text' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
'callback_data' => 'translate:' . $lang['language_code'] . ':' . $textHash
];
}
return [
'inline_keyboard' => array_chunk($buttons, 3)
];
}
function buildDiscordTranslationButtons(array $languages, string $text): array
{
$buttons = [];
$textHash = md5($text);
foreach ($languages as $lang) {
$buttons[] = [
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
'style' => 1
];
}
return [
[
'type' => 1,
'components' => array_slice($buttons, 0, 5)
]
];
}
$pageTitle = 'Crear Mensaje - Sistema de Mensajería';
$recipients = [];
$templates = [];
$galleryImages = [];
try {
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT * FROM recipients ORDER BY platform, name");
$recipients = $stmt->fetchAll();
$stmt = $pdo->query("SELECT * FROM recurrent_messages ORDER BY name");
$templates = $stmt->fetchAll();
// Cargar imágenes de la galería
$galleryPath = __DIR__ . '/galeria';
if (is_dir($galleryPath)) {
$files = scandir($galleryPath);
foreach ($files as $file) {
if (in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
$galleryImages[] = $file;
}
}
}
} catch (Exception $e) {
$error = $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create') {
$result = handleCreateMessage($_POST);
if ($result['success']) {
// Si es "enviar ahora", procesar inmediatamente
if ($_POST['send_type'] === 'now') {
$scheduleId = $result['schedule_id'];
// Obtener datos del schedule
$stmt = $pdo->prepare("
SELECT s.*, m.content, r.platform_id, r.platform, r.name as recipient_name
FROM schedules s
JOIN messages m ON s.message_id = m.id
JOIN recipients r ON s.recipient_id = r.id
WHERE s.id = ?
");
$stmt->execute([$scheduleId]);
$schedule = $stmt->fetch();
if ($schedule) {
$stmt = $pdo->prepare("UPDATE schedules SET status = 'processing' WHERE id = ?");
$stmt->execute([$schedule['id']]);
try {
$sender = \Common\Helpers\SenderFactory::create($schedule['platform']);
// Obtener botones de traducción (convertir HTML a texto plano)
$plainText = $schedule['content'];
// Convertir saltos de párrafo a saltos de línea
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
// Eliminar HTML
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limpiar espacios múltiples pero preservar saltos de línea
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
$plainText = trim($plainText);
$translationButtons = getTranslationButtons($pdo, $plainText);
$segments = $sender->parseContent($schedule['content']);
$messageCount = 0;
foreach ($segments as $segment) {
if ($segment['type'] === 'text') {
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
if (!empty(trim($textContent))) {
// Agregar botones de traducción al último segmento de texto
$buttons = null;
if ($segment === end($segments)) {
$buttons = $schedule['platform'] === 'telegram'
? $translationButtons['telegram']
: $translationButtons['discord'];
}
if ($schedule['platform'] === 'telegram') {
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
} else {
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
}
$messageCount++;
}
} elseif ($segment['type'] === 'image') {
$imagePath = $segment['src'];
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) {
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) {
$embed = ['image' => ['url' => $imagePath]];
$sender->sendMessage($schedule['platform_id'], '', $embed);
$messageCount++;
}
}
}
$stmt = $pdo->prepare("
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
VALUES (?, ?, '', ?, NOW())
");
$stmt->execute([$schedule['id'], $schedule['recipient_id'], $messageCount]);
$stmt = $pdo->prepare("UPDATE schedules SET status = 'sent', sent_at = NOW() WHERE id = ?");
$stmt->execute([$schedule['id']]);
} catch (Exception $e) {
$stmt = $pdo->prepare("UPDATE schedules SET status = 'failed', error_message = ? WHERE id = ?");
$stmt->execute([$e->getMessage(), $schedule['id']]);
}
}
}
header('Location: scheduled_messages.php?success=1');
exit;
} else {
$error = $result['error'];
}
}
require_once __DIR__ . '/templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-plus-circle"></i> Crear Mensaje</h2>
<div class="btn-group">
<a href="preview_message.php" class="btn btn-outline-secondary" target="_blank">
<i class="bi bi-eye"></i> Previsualizar
</a>
</div>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" id="messageForm">
<input type="hidden" name="action" value="create">
<div class="row">
<div class="col-md-8">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Contenido del Mensaje</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Plantilla (opcional)</label>
<select class="form-select" id="templateSelect">
<option value="">-- Seleccionar plantilla --</option>
<?php foreach ($templates as $template): ?>
<option value="<?= htmlspecialchars($template['message_content']) ?>">
<?= htmlspecialchars($template['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Mensaje</label>
<textarea name="content" id="messageContent" class="form-control" rows="12"></textarea>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Destinatario</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Plataforma</label>
<select name="platform" id="platformSelect" class="form-select" required>
<option value="">-- Seleccionar --</option>
<option value="discord">Discord</option>
<option value="telegram">Telegram</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Destinatario</label>
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
<option value="">Selecciona una plataforma primero</option>
</select>
</div>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Programación</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Tipo de envío</label>
<select name="send_type" id="sendType" class="form-select" required>
<option value="now">Enviar ahora</option>
<option value="later">Programar para después</option>
<option value="recurring">Recurrente</option>
</select>
</div>
<div class="mb-3" id="datetimeField" style="display: none;">
<label class="form-label">Fecha y hora</label>
<input type="datetime-local" name="send_datetime" class="form-control">
</div>
<div id="recurringFields" style="display: none;">
<div class="mb-3">
<label class="form-label">Días</label>
<select name="recurring_days" class="form-select">
<option value="monday">Lunes</option>
<option value="tuesday">Martes</option>
<option value="wednesday">Miércoles</option>
<option value="thursday">Jueves</option>
<option value="friday">Viernes</option>
<option value="saturday">Sábado</option>
<option value="sunday">Domingo</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Hora</label>
<input type="time" name="recurring_time" class="form-control" value="09:00">
</div>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-send"></i> Enviar
</button>
<a href="index.php" class="btn btn-outline-secondary">Cancelar</a>
</div>
</div>
</div>
</form>
<!-- Modal de Galería -->
<div class="modal fade" id="galleryModal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-images"></i> Galería de Imágenes</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<?php if (empty($galleryImages)): ?>
<div class="col-12 text-center text-muted py-5">
<i class="bi bi-images" style="font-size: 3rem;"></i>
<p class="mt-3">No hay imágenes en la galería</p>
</div>
<?php else: ?>
<?php foreach ($galleryImages as $image): ?>
<div class="col-md-3 col-sm-4 col-6">
<div class="card h-100 cursor-pointer" onclick="insertImage('galeria/<?= urlencode($image) ?>')" style="cursor: pointer;">
<img src="galeria/<?= urlencode($image) ?>" class="card-img-top" alt="<?= htmlspecialchars($image) ?>" style="height: 120px; object-fit: cover;">
<div class="card-body p-2">
<small class="text-muted text-truncate d-block"><?= htmlspecialchars($image) ?></small>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script>
<script>
const recipients = <?= json_encode($recipients) ?>;
$(document).ready(function() {
// Inicializar Summernote con botón de galería personalizado
$('#messageContent').summernote({
height: 350,
toolbar: [
['style', ['bold', 'italic', 'underline', 'clear']],
['font', ['strikethrough', 'superscript', 'subscript']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['insert', ['link', 'video', 'gallery']], // 'gallery' es nuestro botón personalizado
['view', ['fullscreen', 'codeview']]
],
buttons: {
gallery: function() {
return $.summernote.ui.button({
contents: '<i class="bi bi-images"></i> Galería',
tooltip: 'Insertar imagen desde galería',
click: function() {
$('#galleryModal').modal('show');
}
}).render();
}
}
});
});
function insertImage(url) {
const imgTag = '<img src="' + url + '" style="max-width: 100%; height: auto;">';
$('#messageContent').summernote('editor.pasteHTML', imgTag);
$('#galleryModal').modal('hide');
}
document.getElementById('templateSelect').addEventListener('change', function() {
if (this.value) {
$('#messageContent').summernote('code', this.value);
}
});
document.getElementById('platformSelect').addEventListener('change', function() {
const platform = this.value;
const recipientSelect = document.getElementById('recipientSelect');
recipientSelect.innerHTML = '<option value="">-- Seleccionar --</option>';
if (platform) {
const filtered = recipients.filter(r => r.platform === platform);
filtered.forEach(r => {
const option = document.createElement('option');
option.value = r.id;
option.textContent = r.name + ' (' + r.type + ')';
recipientSelect.appendChild(option);
});
recipientSelect.disabled = false;
} else {
recipientSelect.disabled = true;
recipientSelect.innerHTML = '<option value="">Selecciona una plataforma primero</option>';
}
});
document.getElementById('sendType').addEventListener('change', function() {
const datetimeField = document.getElementById('datetimeField');
const recurringFields = document.getElementById('recurringFields');
datetimeField.style.display = this.value === 'later' ? 'block' : 'none';
recurringFields.style.display = this.value === 'recurring' ? 'block' : 'none';
});
</script>
<?php require_once __DIR__ . '/templates/footer.php'; ?>