- 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
396 lines
16 KiB
PHP
Executable File
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'; ?>
|