Files
lastwar/admin_send_message.php
nickpons666 223c44f1d8 Feature: Agregar selección de idiomas por plataforma (Telegram/Discord)
- Agregar columnas telegram_enabled y discord_enabled a supported_languages
- Nueva interfaz en admin/languages.php con checkboxes para Telegram y Discord
- Los bots ahora solo muestran botones de traducción para los idiomas seleccionados por plataforma
2026-03-10 18:52:30 -06:00

396 lines
16 KiB
PHP
Executable File

<?php
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/session_check.php';
require_once __DIR__ . '/includes/i18n.php';
require_once __DIR__ . '/includes/message_handler.php';
require_once __DIR__ . '/common/helpers/sender_factory.php';
require_once __DIR__ . '/common/helpers/converter_factory.php';
requireAdmin();
function getTranslationButtons(PDO $pdo, string $text): array
{
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
$telegramLanguages = $stmtTelegram->fetchAll();
$stmtDiscord = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$discordLanguages = $stmtDiscord->fetchAll();
if (count($telegramLanguages) <= 1 && count($discordLanguages) <= 1) {
return [];
}
return [
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $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 = t('Enviar Mensaje Directo');
$recipients = [];
$galleryImages = [];
try {
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT * FROM recipients ORDER BY platform, name");
$recipients = $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();
}
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$content = $_POST['content'];
$recipientId = $_POST['recipient_id'];
if ($content && $recipientId) {
$messageId = createMessage([
'user_id' => getCurrentUserId(),
'content' => $content
]);
// Obtener hora actual de MySQL para sincronización
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT NOW() as now");
$now = $stmt->fetch()['now'];
$scheduleId = createSchedule([
'message_id' => $messageId,
'recipient_id' => $recipientId,
'send_time' => $now,
'status' => 'pending'
]);
// Procesar el mensaje inmediatamente
// Obtener datos del schedule recién creado
$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();
$results = ['processed' => 0, 'sent' => 0, 'failed' => 0];
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'];
// Marcar donde hay imágenes
$plainText = preg_replace('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', "\n[IMAGEN]\n", $plainText);
// Convertir saltos de párrafo y br a saltos de línea dobles
$plainText = preg_replace('/<\/p>/i', "\n\n", $plainText);
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
// Eliminar HTML restante
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
$plainText = trim($plainText);
$translationButtons = getTranslationButtons($pdo, $plainText);
// Parsear el contenido HTML en segmentos manteniendo el orden
$segments = $sender->parseContent($schedule['content']);
$messageCount = 0;
$totalSegments = count($segments);
$currentSegment = 0;
// Enviar cada segmento en el orden correcto
foreach ($segments as $segment) {
$currentSegment++;
$isLastSegment = ($currentSegment === $totalSegments);
if ($segment['type'] === 'text') {
// Convertir el texto al formato de la plataforma
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
if (!empty(trim($textContent))) {
$buttons = null;
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$buttons = $translationButtons['discord'];
}
if ($schedule['platform'] === 'telegram') {
$sender->sendMessage($schedule['platform_id'], $textContent);
} else {
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
}
$messageCount++;
}
} elseif ($segment['type'] === 'image') {
$imagePath = $segment['src'];
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
$baseUrl = rtrim($appUrl, '/');
$buttons = null;
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$buttons = $translationButtons['discord'];
}
if ($schedule['platform'] === 'telegram') {
if (strpos($imagePath, 'http') !== 0) {
$imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
} else {
$imageUrl = $imagePath;
}
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
$messageCount++;
} else {
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) {
$sender->sendMessageWithImages($schedule['platform_id'], '', [$imgPath], $buttons);
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) {
$embed = ['image' => ['url' => $imagePath]];
$sender->sendMessage($schedule['platform_id'], '', $embed, $buttons);
$messageCount++;
}
}
}
}
// Enviar botones de traducción en mensaje separado para Telegram
if ($schedule['platform'] === 'telegram' && !empty($translationButtons['telegram'])) {
$translationMessage = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
$sender->sendMessage($schedule['platform_id'], $translationMessage, $translationButtons['telegram']);
$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']]);
$results['sent']++;
} catch (Exception $e) {
$stmt = $pdo->prepare("UPDATE schedules SET status = 'failed', error_message = ? WHERE id = ?");
$stmt->execute([$e->getMessage(), $schedule['id']]);
$results['failed']++;
}
$results['processed']++;
}
$success = "Mensaje enviado. Procesados: {$results['processed']}, Enviados: {$results['sent']}, Fallidos: {$results['failed']}";
}
}
require_once __DIR__ . '/templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-send"></i> <?= t('Enviar Mensaje Directo') ?></h2>
</div>
<?php if ($success): ?>
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="mb-3">
<label class="form-label"><?= t('Plataforma') ?></label>
<select name="platform" id="platformSelect" class="form-select" required>
<option value="">-- <?= t('Seleccionar') ?> --</option>
<option value="discord">Discord</option>
<option value="telegram">Telegram</option>
</select>
</div>
<div class="mb-3">
<label class="form-label"><?= t('Destinatario') ?></label>
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
<option value=""><?= t('Selecciona una plataforma primero') ?></option>
</select>
</div>
<div class="mb-3">
<label class="form-label"><?= t('Mensaje') ?></label>
<textarea name="content" id="messageContent" class="form-control" rows="10" required></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-send"></i> <?= t('Enviar Ahora') ?>
</button>
</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> <?= t('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"><?= t('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: 300,
toolbar: [
['style', ['bold', 'italic', 'underline', 'clear']],
['font', ['strikethrough', 'superscript', 'subscript']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['insert', ['link', 'video', 'gallery']],
['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('platformSelect').addEventListener('change', function() {
const platform = this.value;
const select = document.getElementById('recipientSelect');
select.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 + ')';
select.appendChild(option);
});
select.disabled = false;
} else {
select.disabled = true;
select.innerHTML = '<option value="">Selecciona una plataforma primero</option>';
}
});
</script>
<?php require_once __DIR__ . '/templates/footer.php'; ?>