Compare commits
20 Commits
aa98d9185f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 223c44f1d8 | |||
| 8170931f3d | |||
| bf960f3fc3 | |||
| 082e01b358 | |||
| c439ecd689 | |||
| 2dcff94a22 | |||
| 880940f515 | |||
| 509c7e1709 | |||
| e8213b916b | |||
| 2dd99c04dd | |||
| 65de04209f | |||
| f3e9c26338 | |||
| 72ea19e00d | |||
| c24544360c | |||
| d0478d949b | |||
| a44a64a01f | |||
| 6a50e64b42 | |||
| 4437deb505 | |||
| e8912bdb63 | |||
| a55c45ef94 |
48
.dockerignore
Normal file
48
.dockerignore
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker
|
||||||
|
|
||||||
|
# Node modules (si hubiera)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/*
|
||||||
|
!logs/.gitkeep
|
||||||
|
|
||||||
|
# Archivos temporales
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.sublime-*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Archivos de desarrollo
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
tests
|
||||||
|
phpunit.xml
|
||||||
|
.phpunit.result.cache
|
||||||
|
|
||||||
|
# Composer
|
||||||
|
vendor
|
||||||
|
!vendor/.gitkeep
|
||||||
|
|
||||||
|
# Archivos de upload temporales
|
||||||
|
galeria/*
|
||||||
|
!galeria/.gitkeep
|
||||||
81
README.md
Normal file → Executable file
81
README.md
Normal file → Executable file
@@ -1,14 +1,30 @@
|
|||||||
# Last War - Sistema de Mensajería Multiplataforma
|
# Last War - Sistema de Mensajería Multiplataforma
|
||||||
|
|
||||||
Sistema de mensajería automatizada para Discord y Telegram con traducción automática y asistente IA.
|
Sistema de mensajería automatizada para Discord y Telegram con traducción automática, asistente IA y soporte multi-idioma.
|
||||||
|
|
||||||
## Características
|
## Características
|
||||||
|
|
||||||
|
### Mensajería
|
||||||
- **Discord Bot**: Envío de mensajes, traducción automática, comandos (#lista)
|
- **Discord Bot**: Envío de mensajes, traducción automática, comandos (#lista)
|
||||||
- **Telegram Bot**: Webhook para mensajes, traducción con botones inline
|
- **Telegram Bot**: Webhook para mensajes, traducción con botones inline
|
||||||
- **Traducción Automática**: LibreTranslate con detección de idioma
|
- **Traducción Automática**: LibreTranslate con detección de idioma
|
||||||
- **Asistente IA**: Integración con Groq para respuestas inteligentes
|
- **Asistente IA**: Integración con Groq para respuestas inteligentes
|
||||||
- **Panel de Administración**: Gestiona usuarios, mensajes, plantillas y configuración
|
|
||||||
|
### Panel de Administración
|
||||||
|
- Gestión de usuarios y permisos (admin/user)
|
||||||
|
- Administración de destinatarios por plataforma
|
||||||
|
- Gestión de idiomas con banderas personalizables
|
||||||
|
- Mensajes programados y recurrentes
|
||||||
|
- Galería de imágenes integrada
|
||||||
|
- Configuración del bot de Telegram
|
||||||
|
- Configuración del agente IA
|
||||||
|
|
||||||
|
### Diseño y UX
|
||||||
|
- **Tema Militar/Táctico**: Diseño inspirado en interfaces de videojuegos
|
||||||
|
- **Multi-idioma**: Soporte completo para traducción de la interfaz vía LibreTranslate
|
||||||
|
- **Tema Claro/Oscuro**: Cambio dinámico de tema
|
||||||
|
- **Navbar Horizontal**: Navegación optimizada con dropdown para admin
|
||||||
|
- **Responsive**: Adaptado para móviles y tablets
|
||||||
|
|
||||||
## Requisitos
|
## Requisitos
|
||||||
|
|
||||||
@@ -16,6 +32,9 @@ Sistema de mensajería automatizada para Discord y Telegram con traducción auto
|
|||||||
- MySQL 8.0+
|
- MySQL 8.0+
|
||||||
- Composer
|
- Composer
|
||||||
- Servidor web (Apache/Nginx)
|
- Servidor web (Apache/Nginx)
|
||||||
|
- LibreTranslate (para traducciones)
|
||||||
|
- Cuenta de Discord Developer
|
||||||
|
- Bot de Telegram
|
||||||
|
|
||||||
## Instalación
|
## Instalación
|
||||||
|
|
||||||
@@ -24,10 +43,16 @@ Sistema de mensajería automatizada para Discord y Telegram con traducción auto
|
|||||||
3. Configurar `.env` con las variables de entorno
|
3. Configurar `.env` con las variables de entorno
|
||||||
4. Importar estructura de base de datos
|
4. Importar estructura de base de datos
|
||||||
5. Configurar webhooks de Telegram y Discord
|
5. Configurar webhooks de Telegram y Discord
|
||||||
|
6. Configurar idiomas activos en el panel admin
|
||||||
|
|
||||||
## Variables de Entorno
|
## Variables de Entorno
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
# Aplicación
|
||||||
|
APP_ENV=production
|
||||||
|
APP_URL=https://tudominio.com
|
||||||
|
TZ=America/Mexico_City
|
||||||
|
|
||||||
# Base de datos
|
# Base de datos
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
@@ -37,8 +62,12 @@ DB_PASS=
|
|||||||
|
|
||||||
# Telegram
|
# Telegram
|
||||||
TELEGRAM_BOT_TOKEN=
|
TELEGRAM_BOT_TOKEN=
|
||||||
|
TELEGRAM_WEBHOOK_TOKEN=
|
||||||
|
|
||||||
# Discord
|
# Discord
|
||||||
|
DISCORD_GUILD_ID=
|
||||||
|
DISCORD_CLIENT_ID=
|
||||||
|
DISCORD_CLIENT_SECRET=
|
||||||
DISCORD_BOT_TOKEN=
|
DISCORD_BOT_TOKEN=
|
||||||
|
|
||||||
# LibreTranslate
|
# LibreTranslate
|
||||||
@@ -47,38 +76,76 @@ LIBRETRANSLATE_URL=http://localhost:5000
|
|||||||
# IA (Groq)
|
# IA (Groq)
|
||||||
GROQ_API_KEY=
|
GROQ_API_KEY=
|
||||||
|
|
||||||
# Knowledge Base
|
# Knowledge Base (para IA)
|
||||||
KB_DB_HOST=
|
KB_DB_HOST=
|
||||||
KB_DB_PORT=
|
KB_DB_PORT=
|
||||||
KB_DB_NAME=
|
KB_DB_NAME=
|
||||||
KB_DB_USER=
|
KB_DB_USER=
|
||||||
KB_DB_PASS=
|
KB_DB_PASS=
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET=
|
||||||
```
|
```
|
||||||
|
|
||||||
## Comandos
|
## Comandos
|
||||||
|
|
||||||
### Telegram
|
### Telegram
|
||||||
- `#lista` - Enviar plantilla de lista
|
- `/start` - Iniciar el bot
|
||||||
- `hola` - Mostrar botones de traducción
|
- `#plantilla` - Enviar plantilla por nombre
|
||||||
|
- `/comandos` - Ver comandos disponibles
|
||||||
|
- `/setlang [código]` - Cambiar idioma
|
||||||
|
- `/bienvenida` - Mensaje de bienvenida
|
||||||
|
- `/agente` - Activar modo IA
|
||||||
|
|
||||||
### Discord
|
### Discord
|
||||||
- `#lista` - Enviar plantilla de lista
|
- `#plantilla` - Enviar plantilla por nombre
|
||||||
- `/comandos` - Ver comandos disponibles
|
- `/comandos` - Ver comandos disponibles
|
||||||
|
- `/setlang [código]` - Cambiar idioma
|
||||||
|
- `/bienvenida` - Mensaje de bienvenida
|
||||||
- `/agente` - Activar modo IA
|
- `/agente` - Activar modo IA
|
||||||
|
|
||||||
## Estructura
|
## Estructura
|
||||||
|
|
||||||
```
|
```
|
||||||
├── admin/ # Panel de administración
|
├── admin/ # Panel de administración
|
||||||
|
│ ├── users.php # Gestión de usuarios
|
||||||
|
│ ├── recipients.php # Gestión de destinatarios
|
||||||
|
│ ├── languages.php # Gestión de idiomas
|
||||||
|
│ ├── comandos.php # Lista de comandos
|
||||||
|
│ ├── ia_agent.php # Configuración IA
|
||||||
|
│ └── system.php # Info del sistema
|
||||||
├── discord/ # Archivos de Discord
|
├── discord/ # Archivos de Discord
|
||||||
|
│ ├── senders/ # Enviadores de mensajes
|
||||||
|
│ └── converters/ # Conversores de formato
|
||||||
├── includes/ # Funciones principales
|
├── includes/ # Funciones principales
|
||||||
├── src/ # Clases (IA, Translate)
|
│ ├── db.php # Conexión BD
|
||||||
|
│ ├── i18n.php # Sistema de traducción
|
||||||
|
│ ├── auth.php # Autenticación
|
||||||
|
│ └── session_check.php
|
||||||
|
├── src/ # Clases principales
|
||||||
|
│ ├── IA/ # Agente IA
|
||||||
|
│ └── Translate.php # Traductor
|
||||||
├── telegram/ # Archivos de Telegram
|
├── telegram/ # Archivos de Telegram
|
||||||
|
│ ├── admin/ # Configuración Telegram
|
||||||
|
│ ├── senders/ # Enviadores de mensajes
|
||||||
|
│ └── converters/ # Conversores de formato
|
||||||
├── templates/ # Plantillas HTML
|
├── templates/ # Plantillas HTML
|
||||||
|
│ ├── header.php # Cabecera con navbar
|
||||||
|
│ └── footer.php # Pie de página
|
||||||
|
├── galeria/ # Imágenes para mensajes
|
||||||
├── logs/ # Logs del sistema
|
├── logs/ # Logs del sistema
|
||||||
└── *.php # Archivos principales
|
└── *.php # Archivos principales
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Sistema Multi-idioma
|
||||||
|
|
||||||
|
El sistema traduce automáticamente toda la interfaz usando LibreTranslate:
|
||||||
|
|
||||||
|
- Los idiomas se gestionan desde `Admin > Idiomas`
|
||||||
|
- Solo los idiomas activos aparecen en el selector
|
||||||
|
- El idioma base es español
|
||||||
|
- Las traducciones se cachean automáticamente
|
||||||
|
|
||||||
## Licencia
|
## Licencia
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Gestión de Comandos';
|
$pageTitle = t('Gestión de Comandos');
|
||||||
|
|
||||||
$templates = [];
|
$templates = [];
|
||||||
try {
|
try {
|
||||||
@@ -19,11 +20,11 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-terminal"></i> Gestión de Comandos</h2>
|
<h2><i class="bi bi-terminal"></i> <?= t('Gestión de Comandos') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="bi bi-info-circle"></i> Los comandos se usan en Discord y Telegram anteponiendo <code>#</code> al nombre del comando.
|
<i class="bi bi-info-circle"></i> <?= t('Los comandos se usan en Discord y Telegram anteponiendo') ?> <code>#</code> <?= t('al nombre del comando') ?>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -31,16 +32,16 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if (empty($templates)): ?>
|
<?php if (empty($templates)): ?>
|
||||||
<p class="text-muted text-center py-4">No hay plantillas con comandos</p>
|
<p class="text-muted text-center py-4"><?= t('No hay plantillas con comandos') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Nombre</th>
|
<th><?= t('Nombre') ?></th>
|
||||||
<th>Comando</th>
|
<th><?= t('Comando') ?></th>
|
||||||
<th>Uso</th>
|
<th><?= t('Uso') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -52,7 +53,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<?php if ($template['telegram_command']): ?>
|
<?php if ($template['telegram_command']): ?>
|
||||||
<code>#<?= htmlspecialchars($template['telegram_command']) ?></code>
|
<code>#<?= htmlspecialchars($template['telegram_command']) ?></code>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="text-muted">Sin comando</span>
|
<span class="text-muted"><?= t('Sin comando') ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -73,37 +74,37 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-discord"></i> Comandos de Discord</h5>
|
<h5 class="mb-0"><i class="bi bi-discord"></i> <?= t('Comandos de Discord') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Comando</th>
|
<th><?= t('Comando') ?></th>
|
||||||
<th>Descripción</th>
|
<th><?= t('Descripción') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>#comando</code></td>
|
<td><code>#comando</code></td>
|
||||||
<td>Envía la plantilla asociada</td>
|
<td><?= t('Envía la plantilla asociada') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/comandos</code></td>
|
<td><code>/comandos</code></td>
|
||||||
<td>Lista de comandos disponibles</td>
|
<td><?= t('Lista de comandos disponibles') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/setlang [código]</code></td>
|
<td><code>/setlang [código]</code></td>
|
||||||
<td>Establece el idioma del usuario</td>
|
<td><?= t('Establece el idioma del usuario') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/bienvenida</code></td>
|
<td><code>/bienvenida</code></td>
|
||||||
<td>Envía mensaje de bienvenida</td>
|
<td><?= t('Envía mensaje de bienvenida') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/agente</code></td>
|
<td><code>/agente</code></td>
|
||||||
<td>Cambia a modo IA</td>
|
<td><?= t('Cambia a modo IA') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -115,41 +116,41 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-telegram"></i> Comandos de Telegram</h5>
|
<h5 class="mb-0"><i class="bi bi-telegram"></i> <?= t('Comandos de Telegram') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Comando</th>
|
<th><?= t('Comando') ?></th>
|
||||||
<th>Descripción</th>
|
<th><?= t('Descripción') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>#comando</code></td>
|
<td><code>#comando</code></td>
|
||||||
<td>Envía la plantilla asociada</td>
|
<td><?= t('Envía la plantilla asociada') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/start</code></td>
|
<td><code>/start</code></td>
|
||||||
<td>Inicia el bot</td>
|
<td><?= t('Inicia el bot') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/comandos</code></td>
|
<td><code>/comandos</code></td>
|
||||||
<td>Lista de comandos disponibles</td>
|
<td><?= t('Lista de comandos disponibles') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/setlang [código]</code></td>
|
<td><code>/setlang [código]</code></td>
|
||||||
<td>Establece el idioma del usuario</td>
|
<td><?= t('Establece el idioma del usuario') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/bienvenida</code></td>
|
<td><code>/bienvenida</code></td>
|
||||||
<td>Envía mensaje de bienvenida</td>
|
<td><?= t('Envía mensaje de bienvenida') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>/agente</code></td>
|
<td><code>/agente</code></td>
|
||||||
<td>Cambia a modo IA</td>
|
<td><?= t('Cambia a modo IA') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Configuración del Agente IA';
|
$pageTitle = t('Configuración del Agente IA');
|
||||||
|
|
||||||
require_once __DIR__ . '/../templates/header.php';
|
require_once __DIR__ . '/../templates/header.php';
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ $config = $agent->getAllConfig();
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-robot"></i> Configuración del Agente IA</h2>
|
<h2><i class="bi bi-cpu"></i> <?= t('Configuración del Agente IA') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (!empty($message)): ?>
|
<?php if (!empty($message)): ?>
|
||||||
@@ -100,37 +101,37 @@ $config = $agent->getAllConfig();
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-database"></i> Conexión a Knowledge Base</h5>
|
<h5 class="mb-0"><i class="bi bi-database"></i> <?= t('Conexión a Knowledge Base') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Host</label>
|
<label class="form-label"><?= t('Host') ?></label>
|
||||||
<input type="text" name="kb_host" class="form-control" value="<?= htmlspecialchars($config['kb_db_host'] ?? $_ENV['KB_DB_HOST'] ?? '') ?>">
|
<input type="text" name="kb_host" class="form-control" value="<?= htmlspecialchars($config['kb_db_host'] ?? $_ENV['KB_DB_HOST'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Puerto</label>
|
<label class="form-label"><?= t('Puerto') ?></label>
|
||||||
<input type="text" name="kb_port" class="form-control" value="<?= htmlspecialchars($config['kb_db_port'] ?? $_ENV['KB_DB_PORT'] ?? '') ?>">
|
<input type="text" name="kb_port" class="form-control" value="<?= htmlspecialchars($config['kb_db_port'] ?? $_ENV['KB_DB_PORT'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Base de Datos</label>
|
<label class="form-label"><?= t('Base de Datos') ?></label>
|
||||||
<input type="text" name="kb_dbname" class="form-control" value="<?= htmlspecialchars($config['kb_db_name'] ?? $_ENV['KB_DB_NAME'] ?? '') ?>">
|
<input type="text" name="kb_dbname" class="form-control" value="<?= htmlspecialchars($config['kb_db_name'] ?? $_ENV['KB_DB_NAME'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Usuario</label>
|
<label class="form-label"><?= t('Usuario') ?></label>
|
||||||
<input type="text" name="kb_user" class="form-control" value="<?= htmlspecialchars($config['kb_db_user'] ?? $_ENV['KB_DB_USER'] ?? '') ?>">
|
<input type="text" name="kb_user" class="form-control" value="<?= htmlspecialchars($config['kb_db_user'] ?? $_ENV['KB_DB_USER'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Contraseña</label>
|
<label class="form-label"><?= t('Contraseña') ?></label>
|
||||||
<input type="password" name="kb_pass" class="form-control" value="<?= htmlspecialchars($config['kb_db_pass'] ?? '') ?>" placeholder="Dejar vacío para mantener actual">
|
<input type="password" name="kb_pass" class="form-control" value="<?= htmlspecialchars($config['kb_db_pass'] ?? '') ?>" placeholder="<?= t('Dejar vacío para mantener actual') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid gap-2 d-md-flex">
|
<div class="d-grid gap-2 d-md-flex">
|
||||||
<button type="submit" name="update_kb_config" class="btn btn-primary">
|
<button type="submit" name="update_kb_config" class="btn btn-primary">
|
||||||
<i class="bi bi-save"></i> Guardar
|
<i class="bi bi-save"></i> <?= t('Guardar') ?>
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="test_connection" class="btn btn-outline-primary">
|
<button type="submit" name="test_connection" class="btn btn-outline-primary">
|
||||||
<i class="bi bi-plug"></i> Probar Conexión
|
<i class="bi bi-plug"></i> <?= t('Probar Conexión') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -138,31 +139,31 @@ $config = $agent->getAllConfig();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-cpu"></i> Configuración de IA</h5>
|
<h5 class="mb-0"><i class="bi bi-cpu"></i> <?= t('Configuración de IA') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Habilitar Knowledge Base</label>
|
<label class="form-label"><?= t('Habilitar Knowledge Base') ?></label>
|
||||||
<select name="config_value" class="form-select">
|
<select name="config_value" class="form-select">
|
||||||
<option value="1" <?= ($config['kb_enabled'] ?? '1') === '1' ? 'selected' : '' ?>>Habilitado</option>
|
<option value="1" <?= ($config['kb_enabled'] ?? '1') === '1' ? 'selected' : '' ?>><?= t('Habilitado') ?></option>
|
||||||
<option value="0" <?= ($config['kb_enabled'] ?? '1') === '0' ? 'selected' : '' ?>>Deshabilitado</option>
|
<option value="0" <?= ($config['kb_enabled'] ?? '1') === '0' ? 'selected' : '' ?>><?= t('Deshabilitado') ?></option>
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="config_key" value="kb_enabled">
|
<input type="hidden" name="config_key" value="kb_enabled">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Modelo de IA (Groq)</label>
|
<label class="form-label"><?= t('Modelo de IA') ?> (Groq)</label>
|
||||||
<select name="config_value_model" class="form-select">
|
<select name="config_value_model" class="form-select">
|
||||||
<option value="llama-3.1-8b-instant" <?= ($config['ai_model'] ?? 'llama-3.1-8b-instant') === 'llama-3.1-8b-instant' ? 'selected' : '' ?>>Llama 3.1 8B (Rápido - Recomendado)</option>
|
<option value="llama-3.1-8b-instant" <?= ($config['ai_model'] ?? 'llama-3.1-8b-instant') === 'llama-3.1-8b-instant' ? 'selected' : '' ?>>Llama 3.1 8B (<?= t('Rápido - Recomendado') ?>)</option>
|
||||||
<option value="mixtral-8x7b-32768" <?= ($config['ai_model'] ?? '') === 'mixtral-8x7b-32768' ? 'selected' : '' ?>>Mixtral 8x7B</option>
|
<option value="mixtral-8x7b-32768" <?= ($config['ai_model'] ?? '') === 'mixtral-8x7b-32768' ? 'selected' : '' ?>>Mixtral 8x7B</option>
|
||||||
<option value="llama3-70b-8192" <?= ($config['ai_model'] ?? '') === 'llama3-70b-8192' ? 'selected' : '' ?>>Llama 3 70B</option>
|
<option value="llama3-70b-8192" <?= ($config['ai_model'] ?? '') === 'llama3-70b-8192' ? 'selected' : '' ?>>Llama 3 70B</option>
|
||||||
</select>
|
</select>
|
||||||
<small class="text-muted">Solo modelos gratuitos. Si se agotan los tokens, cambiará automáticamente al siguiente.</small>
|
<small class="text-muted"><?= t('Solo modelos gratuitos. Si se agotan los tokens, cambiará automáticamente al siguiente.') ?></small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<button type="submit" name="update_config" class="btn btn-primary">
|
<button type="submit" name="update_config" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Guardar Configuración
|
<i class="bi bi-check-circle"></i> <?= t('Guardar Configuración') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -172,20 +173,20 @@ $config = $agent->getAllConfig();
|
|||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-chat-left-text"></i> Prompt del Sistema</h5>
|
<h5 class="mb-0"><i class="bi bi-chat-left-text"></i> <?= t('Prompt del Sistema') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Instrucciones para el agente</label>
|
<label class="form-label"><?= t('Instrucciones para el agente') ?></label>
|
||||||
<textarea name="config_value" class="form-control" rows="6"><?= htmlspecialchars($config['system_prompt'] ?? '') ?></textarea>
|
<textarea name="config_value" class="form-control" rows="6"><?= htmlspecialchars($config['system_prompt'] ?? '') ?></textarea>
|
||||||
<small class="text-muted">Instrucciones que seguirá el agente al responder.</small>
|
<small class="text-muted"><?= t('Instrucciones que seguirá el agente al responder.') ?></small>
|
||||||
<input type="hidden" name="config_key" value="system_prompt">
|
<input type="hidden" name="config_key" value="system_prompt">
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" name="update_config" class="btn btn-primary">
|
<button type="submit" name="update_config" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Guardar Prompt
|
<i class="bi bi-check-circle"></i> <?= t('Guardar Prompt') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -193,25 +194,25 @@ $config = $agent->getAllConfig();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-sliders"></i> Parámetros Adicionales</h5>
|
<h5 class="mb-0"><i class="bi bi-sliders"></i> <?= t('Parámetros Adicionales') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Máximo de resultados de KB</label>
|
<label class="form-label"><?= t('Máximo de resultados de KB') ?></label>
|
||||||
<input type="number" name="config_value" class="form-control" value="<?= htmlspecialchars($config['kb_max_results'] ?? '5') ?>" min="1" max="20">
|
<input type="number" name="config_value" class="form-control" value="<?= htmlspecialchars($config['kb_max_results'] ?? '5') ?>" min="1" max="20">
|
||||||
<small class="text-muted">Cantidad de artículos a buscar en la base de conocimientos.</small>
|
<small class="text-muted"><?= t('Cantidad de artículos a buscar en la base de conocimientos.') ?></small>
|
||||||
<input type="hidden" name="config_key" value="kb_max_results">
|
<input type="hidden" name="config_key" value="kb_max_results">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Máximo de caracteres en respuesta</label>
|
<label class="form-label"><?= t('Máximo de caracteres en respuesta') ?></label>
|
||||||
<input type="number" name="config_value2" class="form-control" value="<?= htmlspecialchars($config['response_max_length'] ?? '1500') ?>" min="100" max="4000">
|
<input type="number" name="config_value2" class="form-control" value="<?= htmlspecialchars($config['response_max_length'] ?? '1500') ?>" min="100" max="4000">
|
||||||
<small class="text-muted">Límite de caracteres en las respuestas del agente.</small>
|
<small class="text-muted"><?= t('Límite de caracteres en las respuestas del agente.') ?></small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" name="update_config" class="btn btn-primary">
|
<button type="submit" name="update_config" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Guardar Parámetros
|
<i class="bi bi-check-circle"></i> <?= t('Guardar Parámetros') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -223,23 +224,23 @@ $config = $agent->getAllConfig();
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-chat-dots"></i> Prueba del Agente</h5>
|
<h5 class="mb-0"><i class="bi bi-chat-dots"></i> <?= t('Prueba del Agente') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" class="mb-3">
|
<form method="POST" class="mb-3">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text"><i class="bi bi-question-circle"></i></span>
|
<span class="input-group-text"><i class="bi bi-question-circle"></i></span>
|
||||||
<input type="text" name="test_question" class="form-control" placeholder="Escribe una pregunta para probar el agente..." value="<?= htmlspecialchars($_POST['test_question'] ?? '') ?>">
|
<input type="text" name="test_question" class="form-control" placeholder="<?= t('Escribe una pregunta para probar el agente...') ?>" value="<?= htmlspecialchars($_POST['test_question'] ?? '') ?>">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="bi bi-send"></i> Enviar
|
<i class="bi bi-send"></i> <?= t('Enviar') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php if (isset($testResponse)): ?>
|
<?php if (isset($testResponse)): ?>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label class="form-label text-muted">Respuesta del agente:</label>
|
<label class="form-label text-muted"><?= t('Respuesta del agente') ?>:</label>
|
||||||
<div class="p-3 bg-light rounded border">
|
<div class="p-3 bg-light rounded border">
|
||||||
<pre class="mb-0 white-space: pre-wrap;"><?= htmlspecialchars($testResponse) ?></pre>
|
<pre class="mb-0 white-space: pre-wrap;"><?= htmlspecialchars($testResponse) ?></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
require_once __DIR__ . '/../includes/activity_logger.php';
|
require_once __DIR__ . '/../includes/activity_logger.php';
|
||||||
require_once __DIR__ . '/../includes/env_loader.php';
|
require_once __DIR__ . '/../includes/env_loader.php';
|
||||||
require_once __DIR__ . '/../src/Translate.php';
|
require_once __DIR__ . '/../src/Translate.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Gestión de Idiomas';
|
$pageTitle = t('Gestión de Idiomas');
|
||||||
|
|
||||||
$languages = [];
|
$languages = [];
|
||||||
try {
|
try {
|
||||||
@@ -82,6 +83,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$syncError = "Error al conectar con LibreTranslate: " . $e->getMessage() . ". Verifica que el servicio esté configurado correctamente en el archivo .env";
|
$syncError = "Error al conectar con LibreTranslate: " . $e->getMessage() . ". Verifica que el servicio esté configurado correctamente en el archivo .env";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} elseif ($action === 'toggle_telegram') {
|
||||||
|
$id = (int) $_POST['id'];
|
||||||
|
$stmt = $pdo->prepare("UPDATE supported_languages SET telegram_enabled = NOT telegram_enabled WHERE id = ? AND is_active = TRUE");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
logActivity(getCurrentUserId(), 'toggle_telegram_language', "Idioma Telegram ID: $id");
|
||||||
|
header('Location: languages.php');
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} elseif ($action === 'toggle_discord') {
|
||||||
|
$id = (int) $_POST['id'];
|
||||||
|
$stmt = $pdo->prepare("UPDATE supported_languages SET discord_enabled = NOT discord_enabled WHERE id = ? AND is_active = TRUE");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
logActivity(getCurrentUserId(), 'toggle_discord_language', "Idioma Discord ID: $id");
|
||||||
|
header('Location: languages.php');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,16 +272,16 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-translate"></i> Gestión de Idiomas</h2>
|
<h2><i class="bi bi-translate"></i> <?= t('Gestión de Idiomas') ?></h2>
|
||||||
<div>
|
<div class="d-flex gap-2">
|
||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="action" value="sync_libretranslate">
|
<input type="hidden" name="action" value="sync_libretranslate">
|
||||||
<button type="submit" class="btn btn-outline-primary">
|
<button type="submit" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-cloud-download"></i> Sincronizar con LibreTranslate
|
<i class="bi bi-arrow-repeat"></i> <?= t('Sincronizar con LibreTranslate') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#languageModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#languageModal">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Idioma
|
<i class="bi bi-plus-circle"></i> <?= t('Nuevo Idioma') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,11 +306,13 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Bandera</th>
|
<th><?= t('Bandera') ?></th>
|
||||||
<th>Código</th>
|
<th><?= t('Código') ?></th>
|
||||||
<th>Nombre</th>
|
<th><?= t('Nombre') ?></th>
|
||||||
<th>Estado</th>
|
<th><?= t('Estado') ?></th>
|
||||||
<th>Acciones</th>
|
<th><?= t('Telegram') ?></th>
|
||||||
|
<th><?= t('Discord') ?></th>
|
||||||
|
<th><?= t('Acciones') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -304,9 +323,35 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<td><?= htmlspecialchars($lang['language_name']) ?></td>
|
<td><?= htmlspecialchars($lang['language_name']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($lang['is_active']): ?>
|
<?php if ($lang['is_active']): ?>
|
||||||
<span class="badge bg-success">Activo</span>
|
<span class="badge bg-success"><?= t('Activo') ?></span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="badge bg-secondary">Inactivo</span>
|
<span class="badge bg-secondary"><?= t('Inactivo') ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($lang['is_active']): ?>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="action" value="toggle_telegram">
|
||||||
|
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-<?= $lang['telegram_enabled'] ? 'info' : 'secondary' ?>">
|
||||||
|
<i class="bi bi-<?= $lang['telegram_enabled'] ? 'check-circle-fill' : 'circle' ?>"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted"><i class="bi bi-circle"></i></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($lang['is_active']): ?>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="action" value="toggle_discord">
|
||||||
|
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-<?= $lang['discord_enabled'] ? 'primary' : 'secondary' ?>">
|
||||||
|
<i class="bi bi-<?= $lang['discord_enabled'] ? 'check-circle-fill' : 'circle' ?>"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted"><i class="bi bi-circle"></i></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -319,7 +364,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#flagModal<?= $lang['id'] ?>">
|
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#flagModal<?= $lang['id'] ?>">
|
||||||
<i class="bi bi-flag"></i> Cambiar
|
<i class="bi bi-flag"></i> <?= t('Cambiar') ?>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -332,17 +377,17 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<input type="hidden" name="action" value="update_flag">
|
<input type="hidden" name="action" value="update_flag">
|
||||||
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
|
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Seleccionar Bandera - <?= htmlspecialchars($lang['language_name']) ?></h5>
|
<h5 class="modal-title"><?= t('Seleccionar Bandera') ?> - <?= htmlspecialchars($lang['language_name']) ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Bandera actual</label>
|
<label class="form-label"><?= t('Bandera actual') ?></label>
|
||||||
<div class="display-4"><?= htmlspecialchars($lang['flag_emoji']) ?></div>
|
<div class="display-4"><?= htmlspecialchars($lang['flag_emoji']) ?></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Seleccionar nueva bandera</label>
|
<label class="form-label"><?= t('Seleccionar nueva bandera') ?></label>
|
||||||
<div class="flag-selector" style="max-height: 400px; overflow-y: auto;">
|
<div class="flag-selector" style="max-height: 400px; overflow-y: auto;">
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<?php foreach ($availableFlags as $flag): ?>
|
<?php foreach ($availableFlags as $flag): ?>
|
||||||
@@ -359,14 +404,14 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label class="form-label">O escribir emoji manualmente</label>
|
<label class="form-label"><?= t('O escribir emoji manualmente') ?></label>
|
||||||
<input type="text" name="flag_emoji_custom" id="customFlag<?= $lang['id'] ?>" class="form-control" value="<?= htmlspecialchars($lang['flag_emoji']) ?>" maxlength="10" placeholder="🇲🇽">
|
<input type="text" name="flag_emoji_custom" id="customFlag<?= $lang['id'] ?>" class="form-control" value="<?= htmlspecialchars($lang['flag_emoji']) ?>" maxlength="10" placeholder="🇲🇽">
|
||||||
<small class="text-muted">Puedes copiar y pegar cualquier emoji de bandera aquí</small>
|
<small class="text-muted"><?= t('Puedes copiar y pegar cualquier emoji de bandera aquí') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
|
||||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -394,20 +439,20 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Nuevo Idioma</h5>
|
<h5 class="modal-title"><?= t('Nuevo Idioma') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Código de idioma (ej: ca, gl)</label>
|
<label class="form-label"><?= t('Código de idioma') ?> (ej: ca, gl)</label>
|
||||||
<input type="text" name="language_code" class="form-control" required maxlength="10">
|
<input type="text" name="language_code" class="form-control" required maxlength="10">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nombre del idioma</label>
|
<label class="form-label"><?= t('Nombre del idioma') ?></label>
|
||||||
<input type="text" name="language_name" class="form-control" required>
|
<input type="text" name="language_name" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Seleccionar bandera</label>
|
<label class="form-label"><?= t('Seleccionar bandera') ?></label>
|
||||||
<div class="flag-selector-new" style="max-height: 300px; overflow-y: auto;">
|
<div class="flag-selector-new" style="max-height: 300px; overflow-y: auto;">
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<?php foreach ($availableFlags as $flag): ?>
|
<?php foreach ($availableFlags as $flag): ?>
|
||||||
@@ -423,12 +468,12 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label class="form-label">O escribir emoji manualmente</label>
|
<label class="form-label"><?= t('O escribir emoji manualmente') ?></label>
|
||||||
<input type="text" id="newFlagInput" name="flag_emoji" class="form-control" maxlength="10" placeholder="🇲🇽">
|
<input type="text" id="newFlagInput" name="flag_emoji" class="form-control" maxlength="10" placeholder="🇲🇽">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Agregar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
require_once __DIR__ . '/../includes/activity_logger.php';
|
require_once __DIR__ . '/../includes/activity_logger.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Gestión de Destinatarios';
|
$pageTitle = t('Gestión de Destinatarios');
|
||||||
|
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
$languages = [];
|
$languages = [];
|
||||||
@@ -68,9 +69,9 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-person-check"></i> Gestión de Destinatarios</h2>
|
<h2><i class="bi bi-person-check"></i> <?= t('Gestión de Destinatarios') ?></h2>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRecipientModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRecipientModal">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Destinatario
|
<i class="bi bi-plus-circle"></i> <?= t('Nuevo Destinatario') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -93,19 +94,19 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php $discordRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'discord'); ?>
|
<?php $discordRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'discord'); ?>
|
||||||
<?php if (empty($discordRecipients)): ?>
|
<?php if (empty($discordRecipients)): ?>
|
||||||
<p class="text-muted">No hay destinatarios de Discord</p>
|
<p class="text-muted"><?= t('No hay destinatarios de Discord') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><?= t('ID') ?></th>
|
||||||
<th>Platform ID</th>
|
<th>Platform ID</th>
|
||||||
<th>Nombre</th>
|
<th><?= t('Nombre') ?></th>
|
||||||
<th>Tipo</th>
|
<th><?= t('Tipo') ?></th>
|
||||||
<th>Idioma</th>
|
<th><?= t('Idioma') ?></th>
|
||||||
<th>Creado</th>
|
<th><?= t('Creado') ?></th>
|
||||||
<th>Acciones</th>
|
<th><?= t('Acciones') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -114,7 +115,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<td><?= $recipient['id'] ?></td>
|
<td><?= $recipient['id'] ?></td>
|
||||||
<td><code><?= $recipient['platform_id'] ?></code></td>
|
<td><code><?= $recipient['platform_id'] ?></code></td>
|
||||||
<td><?= htmlspecialchars($recipient['name']) ?></td>
|
<td><?= htmlspecialchars($recipient['name']) ?></td>
|
||||||
<td><?= $recipient['type'] ?></td>
|
<td><?= t($recipient['type'] === 'channel' ? 'Canal' : 'Usuario') ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="action" value="update_language">
|
<input type="hidden" name="action" value="update_language">
|
||||||
@@ -130,7 +131,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</td>
|
</td>
|
||||||
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
|
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
|
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||||
@@ -153,19 +154,19 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php $telegramRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'telegram'); ?>
|
<?php $telegramRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'telegram'); ?>
|
||||||
<?php if (empty($telegramRecipients)): ?>
|
<?php if (empty($telegramRecipients)): ?>
|
||||||
<p class="text-muted">No hay destinatarios de Telegram</p>
|
<p class="text-muted"><?= t('No hay destinatarios de Telegram') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><?= t('ID') ?></th>
|
||||||
<th>Platform ID</th>
|
<th>Platform ID</th>
|
||||||
<th>Nombre</th>
|
<th><?= t('Nombre') ?></th>
|
||||||
<th>Tipo</th>
|
<th><?= t('Tipo') ?></th>
|
||||||
<th>Idioma</th>
|
<th><?= t('Idioma') ?></th>
|
||||||
<th>Creado</th>
|
<th><?= t('Creado') ?></th>
|
||||||
<th>Acciones</th>
|
<th><?= t('Acciones') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -174,7 +175,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<td><?= $recipient['id'] ?></td>
|
<td><?= $recipient['id'] ?></td>
|
||||||
<td><code><?= $recipient['platform_id'] ?></code></td>
|
<td><code><?= $recipient['platform_id'] ?></code></td>
|
||||||
<td><?= htmlspecialchars($recipient['name']) ?></td>
|
<td><?= htmlspecialchars($recipient['name']) ?></td>
|
||||||
<td><?= $recipient['type'] ?></td>
|
<td><?= t($recipient['type'] === 'channel' ? 'Canal' : 'Usuario') ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="action" value="update_language">
|
<input type="hidden" name="action" value="update_language">
|
||||||
@@ -190,7 +191,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</td>
|
</td>
|
||||||
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
|
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
|
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||||
@@ -217,13 +218,13 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Nuevo Destinatario</h5>
|
<h5 class="modal-title"><?= t('Nuevo Destinatario') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Plataforma</label>
|
<label class="form-label"><?= t('Plataforma') ?></label>
|
||||||
<select name="platform" class="form-select" required>
|
<select name="platform" class="form-select" required>
|
||||||
<option value="discord">Discord</option>
|
<option value="discord">Discord</option>
|
||||||
<option value="telegram">Telegram</option>
|
<option value="telegram">Telegram</option>
|
||||||
@@ -231,26 +232,26 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">ID en la plataforma</label>
|
<label class="form-label"><?= t('ID en la plataforma') ?></label>
|
||||||
<input type="text" name="platform_id" class="form-control" required placeholder="Ej: 123456789">
|
<input type="text" name="platform_id" class="form-control" required placeholder="<?= t('Ej') ?>: 123456789">
|
||||||
<small class="text-muted">ID del canal/usuario en Discord o Telegram</small>
|
<small class="text-muted"><?= t('ID del canal/usuario en Discord o Telegram') ?></small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nombre</label>
|
<label class="form-label"><?= t('Nombre') ?></label>
|
||||||
<input type="text" name="name" class="form-control" required>
|
<input type="text" name="name" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Tipo</label>
|
<label class="form-label"><?= t('Tipo') ?></label>
|
||||||
<select name="type" class="form-select" required>
|
<select name="type" class="form-select" required>
|
||||||
<option value="channel">Canal</option>
|
<option value="channel"><?= t('Canal') ?></option>
|
||||||
<option value="user">Usuario</option>
|
<option value="user"><?= t('Usuario') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Idioma</label>
|
<label class="form-label"><?= t('Idioma') ?></label>
|
||||||
<select name="language_code" class="form-select">
|
<select name="language_code" class="form-select">
|
||||||
<?php foreach ($languages as $lang): ?>
|
<?php foreach ($languages as $lang): ?>
|
||||||
<option value="<?= $lang['language_code'] ?>">
|
<option value="<?= $lang['language_code'] ?>">
|
||||||
@@ -262,8 +263,8 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
|
||||||
<button type="submit" class="btn btn-primary">Agregar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
109
admin/system.php
Normal file
109
admin/system.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
if (!isAdmin()) {
|
||||||
|
header('Location: /index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
|
|
||||||
|
$pageTitle = t('Sistema') . ' - Admin';
|
||||||
|
|
||||||
|
$dbHost = $_ENV['DB_HOST'] ?? getenv('DB_HOST') ?? 'localhost';
|
||||||
|
$dbPort = $_ENV['DB_PORT'] ?? getenv('DB_PORT') ?? '3306';
|
||||||
|
$dbName = $_ENV['DB_NAME'] ?? getenv('DB_NAME') ?? 'bot';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
$dbVersion = $pdo->query('SELECT VERSION()')->fetchColumn();
|
||||||
|
$dbSize = $pdo->query("SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema = '$dbName'")->fetchColumn();
|
||||||
|
$tableCount = $pdo->query("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '$dbName'")->fetchColumn();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$dbError = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2><i class="bi bi-hdd-stack"></i> <?= t('Sistema') ?></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header border-0">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-database"></i> <?= t('Base de Datos') ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (isset($dbError)): ?>
|
||||||
|
<div class="alert alert-danger"><?= htmlspecialchars($dbError) ?></div>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-borderless mb-0">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Host') ?> / IP</td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace; color: var(--hud-cyan);"><?= htmlspecialchars($dbHost) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Puerto') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace; color: var(--hud-cyan);"><?= htmlspecialchars($dbPort) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Base de Datos') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace; color: var(--accent-orange);"><?= htmlspecialchars($dbName) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Versión MySQL') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= htmlspecialchars($dbVersion ?? 'N/A') ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Tamaño') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= $dbSize ?? '0' ?> MB</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Tablas') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= $tableCount ?? '0' ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header border-0">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-server"></i> <?= t('Servidor') ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-borderless mb-0">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Versión PHP') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= phpversion() ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Software Servidor') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= $_SERVER['SERVER_SOFTWARE'] ?? 'N/A' ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Nombre Servidor') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= $_SERVER['SERVER_NAME'] ?? 'N/A' ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Raíz Documentos') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace; font-size: 0.85rem;"><?= $_SERVER['DOCUMENT_ROOT'] ?? 'N/A' ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted text-uppercase" style="font-size: 0.75rem; letter-spacing: 1px;"><?= t('Zona Horaria') ?></td>
|
||||||
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= date_default_timezone_get() ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once __DIR__ . '/../templates/footer.php'; ?>
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
require_once __DIR__ . '/../includes/env_loader.php';
|
require_once __DIR__ . '/../includes/env_loader.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Test de Conexión Discord';
|
$pageTitle = t('Test de Conexión Discord');
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
$error = '';
|
$error = '';
|
||||||
@@ -81,7 +82,7 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-discord"></i> Test de Conexión Discord</h2>
|
<h2><i class="bi bi-discord"></i> <?= t('Test de Conexión Discord') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
@@ -91,11 +92,11 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<?php if (!empty($results)): ?>
|
<?php if (!empty($results)): ?>
|
||||||
<?php if ($results['http_code'] === 200): ?>
|
<?php if ($results['http_code'] === 200): ?>
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<i class="bi bi-check-circle"></i> <strong>Conexión exitosa!</strong> El bot está conectado como <strong><?= htmlspecialchars($results['user']['username']) ?></strong>
|
<i class="bi bi-check-circle"></i> <strong><?= t('Conexión exitosa') ?>!</strong> <?= t('El bot está conectado como') ?> <strong><?= htmlspecialchars($results['user']['username']) ?></strong>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<i class="bi bi-x-circle"></i> <strong>Error de conexión:</strong> Código HTTP <?= $results['http_code'] ?>
|
<i class="bi bi-x-circle"></i> <strong><?= t('Error de conexión') ?>:</strong> <?= t('Código HTTP') ?> <?= $results['http_code'] ?>
|
||||||
<?php if (isset($results['user']['message'])): ?>
|
<?php if (isset($results['user']['message'])): ?>
|
||||||
<br><?= htmlspecialchars($results['user']['message']) ?>
|
<br><?= htmlspecialchars($results['user']['message']) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -104,16 +105,16 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
|
|
||||||
<?php if (isset($results['guild'])): ?>
|
<?php if (isset($results['guild'])): ?>
|
||||||
<div class="card border-0 shadow-sm mb-3">
|
<div class="card border-0 shadow-sm mb-3">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Servidor</h5>
|
<h5 class="mb-0"><?= t('Servidor') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if ($results['guild_code'] === 200): ?>
|
<?php if ($results['guild_code'] === 200): ?>
|
||||||
<p><strong>Nombre:</strong> <?= htmlspecialchars($results['guild']['name']) ?></p>
|
<p><strong><?= t('Nombre') ?>:</strong> <?= htmlspecialchars($results['guild']['name']) ?></p>
|
||||||
<p><strong>ID:</strong> <?= htmlspecialchars($results['guild']['id']) ?></p>
|
<p><strong>ID:</strong> <?= htmlspecialchars($results['guild']['id']) ?></p>
|
||||||
<p><strong>Miembros:</strong> <?= $results['guild']['approximate_member_count'] ?? 'N/A' ?></p>
|
<p><strong><?= t('Miembros') ?>:</strong> <?= $results['guild']['approximate_member_count'] ?? 'N/A' ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="alert alert-warning">Error al obtener servidor: Código <?= $results['guild_code'] ?></div>
|
<div class="alert alert-warning"><?= t('Error al obtener servidor') ?>: <?= t('Código') ?> <?= $results['guild_code'] ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,14 +122,14 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
|
|
||||||
<?php if (isset($results['test_message'])): ?>
|
<?php if (isset($results['test_message'])): ?>
|
||||||
<div class="card border-0 shadow-sm mb-3">
|
<div class="card border-0 shadow-sm mb-3">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Prueba de Envío</h5>
|
<h5 class="mb-0"><?= t('Prueba de Envío') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if ($results['test_message_code'] === 200): ?>
|
<?php if ($results['test_message_code'] === 200): ?>
|
||||||
<div class="alert alert-success"><i class="bi bi-check-circle"></i> Mensaje enviado correctamente</div>
|
<div class="alert alert-success"><i class="bi bi-check-circle"></i> <?= t('Mensaje enviado correctamente') ?></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="alert alert-danger">Error al enviar mensaje: Código <?= $results['test_message_code'] ?></div>
|
<div class="alert alert-danger"><?= t('Error al enviar mensaje') ?>: <?= t('Código') ?> <?= $results['test_message_code'] ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,22 +137,22 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Probar Conexión</h5>
|
<h5 class="mb-0"><?= t('Probar Conexión') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="test">
|
<input type="hidden" name="action" value="test">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-plug"></i> Verificar Conexión
|
<i class="bi bi-plug"></i> <?= t('Verificar Conexión') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Enviar Mensaje de Prueba</h5>
|
<h5 class="mb-0"><?= t('Enviar Mensaje de Prueba') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
@@ -159,17 +160,17 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<input type="hidden" name="test_message" value="1">
|
<input type="hidden" name="test_message" value="1">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">ID del Canal</label>
|
<label class="form-label"><?= t('ID del Canal') ?></label>
|
||||||
<input type="text" name="test_channel_id" class="form-control" placeholder="123456789012345678">
|
<input type="text" name="test_channel_id" class="form-control" placeholder="123456789012345678">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje</label>
|
<label class="form-label"><?= t('Mensaje') ?></label>
|
||||||
<textarea name="test_message_text" class="form-control" rows="3">✅ Prueba de conexión desde el sistema de mensajería</textarea>
|
<textarea name="test_message_text" class="form-control" rows="3">✅ <?= t('Prueba de conexión desde el sistema de mensajería') ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="bi bi-send"></i> Enviar Mensaje
|
<i class="bi bi-send"></i> <?= t('Enviar Mensaje') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
require_once __DIR__ . '/../includes/auth.php';
|
require_once __DIR__ . '/../includes/auth.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Gestión de Usuarios';
|
$pageTitle = t('Gestión de Usuarios');
|
||||||
|
|
||||||
$users = getAllUsers();
|
$users = getAllUsers();
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
header('Location: users.php');
|
header('Location: users.php');
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
$error = 'El usuario ya existe';
|
$error = t('El usuario ya existe');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
header('Location: users.php');
|
header('Location: users.php');
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
$error = 'No puedes eliminarte a ti mismo';
|
$error = t('No puedes eliminarte a ti mismo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,9 +46,9 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-people"></i> Gestión de Usuarios</h2>
|
<h2><i class="bi bi-people"></i> <?= t('Gestión de Usuarios') ?></h2>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#userModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#userModal">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Usuario
|
<i class="bi bi-plus-circle"></i> <?= t('Nuevo Usuario') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,12 +62,12 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><?= t('ID') ?></th>
|
||||||
<th>Usuario</th>
|
<th><?= t('Usuario') ?></th>
|
||||||
<th>Rol</th>
|
<th><?= t('Rol') ?></th>
|
||||||
<th>Telegram</th>
|
<th>Telegram</th>
|
||||||
<th>Creado</th>
|
<th><?= t('Creado') ?></th>
|
||||||
<th>Acciones</th>
|
<th><?= t('Acciones') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -76,14 +77,14 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<td><?= htmlspecialchars($user['username']) ?></td>
|
<td><?= htmlspecialchars($user['username']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
|
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
|
||||||
<?= $user['role'] ?>
|
<?= $user['role'] === 'admin' ? t('Administrador') : t('Usuario') ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td><?= $user['telegram_chat_id'] ? htmlspecialchars($user['telegram_chat_id']) : '-' ?></td>
|
<td><?= $user['telegram_chat_id'] ? htmlspecialchars($user['telegram_chat_id']) : '-' ?></td>
|
||||||
<td><?= date('d/m/Y', strtotime($user['created_at'])) ?></td>
|
<td><?= date('d/m/Y', strtotime($user['created_at'])) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($user['id'] !== getCurrentUserId()): ?>
|
<?php if ($user['id'] !== getCurrentUserId()): ?>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar este usuario?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar este usuario?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="user_id" value="<?= $user['id'] ?>">
|
<input type="hidden" name="user_id" value="<?= $user['id'] ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||||||
@@ -106,28 +107,28 @@ require_once __DIR__ . '/../templates/header.php';
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="create">
|
<input type="hidden" name="action" value="create">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Nuevo Usuario</h5>
|
<h5 class="modal-title"><?= t('Nuevo Usuario') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Usuario</label>
|
<label class="form-label"><?= t('Usuario') ?></label>
|
||||||
<input type="text" name="username" class="form-control" required>
|
<input type="text" name="username" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Contraseña</label>
|
<label class="form-label"><?= t('Contraseña') ?></label>
|
||||||
<input type="password" name="password" class="form-control" required minlength="6">
|
<input type="password" name="password" class="form-control" required minlength="6">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Rol</label>
|
<label class="form-label"><?= t('Rol') ?></label>
|
||||||
<select name="role" class="form-select">
|
<select name="role" class="form-select">
|
||||||
<option value="user">Usuario</option>
|
<option value="user"><?= t('Usuario') ?></option>
|
||||||
<option value="admin">Administrador</option>
|
<option value="admin"><?= t('Administrador') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Crear</button>
|
<button type="submit" class="btn btn-primary"><?= t('Crear') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
require_once __DIR__ . '/includes/message_handler.php';
|
require_once __DIR__ . '/includes/message_handler.php';
|
||||||
require_once __DIR__ . '/common/helpers/sender_factory.php';
|
require_once __DIR__ . '/common/helpers/sender_factory.php';
|
||||||
require_once __DIR__ . '/common/helpers/converter_factory.php';
|
require_once __DIR__ . '/common/helpers/converter_factory.php';
|
||||||
@@ -9,16 +10,19 @@ requireAdmin();
|
|||||||
|
|
||||||
function getTranslationButtons(PDO $pdo, string $text): array
|
function getTranslationButtons(PDO $pdo, string $text): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||||
$languages = $stmt->fetchAll();
|
$telegramLanguages = $stmtTelegram->fetchAll();
|
||||||
|
|
||||||
if (count($languages) <= 1) {
|
$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 [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
|
||||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ function buildDiscordTranslationButtons(array $languages, string $text): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageTitle = 'Enviar Mensaje Directo';
|
$pageTitle = t('Enviar Mensaje Directo');
|
||||||
|
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
$galleryImages = [];
|
$galleryImages = [];
|
||||||
@@ -139,32 +143,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$sender = \Common\Helpers\SenderFactory::create($schedule['platform']);
|
$sender = \Common\Helpers\SenderFactory::create($schedule['platform']);
|
||||||
|
|
||||||
// Obtener botones de traducción (convertir HTML a texto plano)
|
// Obtener botones de traducción (convertir HTML a texto plano)
|
||||||
$plainText = html_entity_decode(strip_tags($schedule['content']), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$plainText = $schedule['content'];
|
||||||
$plainText = preg_replace('/\s+/', ' ', $plainText); // Normalizar espacios
|
// 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);
|
$translationButtons = getTranslationButtons($pdo, $plainText);
|
||||||
|
|
||||||
// Parsear el contenido HTML en segmentos manteniendo el orden
|
// Parsear el contenido HTML en segmentos manteniendo el orden
|
||||||
$segments = $sender->parseContent($schedule['content']);
|
$segments = $sender->parseContent($schedule['content']);
|
||||||
|
|
||||||
$messageCount = 0;
|
$messageCount = 0;
|
||||||
|
$totalSegments = count($segments);
|
||||||
|
$currentSegment = 0;
|
||||||
|
|
||||||
// Enviar cada segmento en el orden correcto
|
// Enviar cada segmento en el orden correcto
|
||||||
foreach ($segments as $segment) {
|
foreach ($segments as $segment) {
|
||||||
|
$currentSegment++;
|
||||||
|
$isLastSegment = ($currentSegment === $totalSegments);
|
||||||
|
|
||||||
if ($segment['type'] === 'text') {
|
if ($segment['type'] === 'text') {
|
||||||
// Convertir el texto al formato de la plataforma
|
// Convertir el texto al formato de la plataforma
|
||||||
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||||
|
|
||||||
if (!empty(trim($textContent))) {
|
if (!empty(trim($textContent))) {
|
||||||
// Agregar botones de traducción al último segmento de texto
|
|
||||||
$buttons = null;
|
$buttons = null;
|
||||||
if ($segment === end($segments)) {
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$buttons = $schedule['platform'] === 'telegram'
|
$buttons = $translationButtons['discord'];
|
||||||
? $translationButtons['telegram']
|
|
||||||
: $translationButtons['discord'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($schedule['platform'] === 'telegram') {
|
if ($schedule['platform'] === 'telegram') {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||||
} else {
|
} else {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
||||||
}
|
}
|
||||||
@@ -173,22 +190,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
} elseif ($segment['type'] === 'image') {
|
} elseif ($segment['type'] === 'image') {
|
||||||
$imagePath = $segment['src'];
|
$imagePath = $segment['src'];
|
||||||
|
|
||||||
// Quitar parámetros de URL si los hay
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
|
|
||||||
if (file_exists($imgPath)) {
|
$buttons = null;
|
||||||
// Es un archivo local
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
$buttons = $translationButtons['discord'];
|
||||||
$messageCount++;
|
}
|
||||||
} elseif (strpos($imagePath, 'http') === 0) {
|
|
||||||
// Es una URL remota
|
if ($schedule['platform'] === 'telegram') {
|
||||||
$embed = ['image' => ['url' => $imagePath]];
|
if (strpos($imagePath, 'http') !== 0) {
|
||||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
$imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
|
||||||
|
} else {
|
||||||
|
$imageUrl = $imagePath;
|
||||||
|
}
|
||||||
|
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
|
||||||
$messageCount++;
|
$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("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||||
VALUES (?, ?, '', ?, NOW())
|
VALUES (?, ?, '', ?, NOW())
|
||||||
@@ -217,7 +255,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-send"></i> Enviar Mensaje Directo</h2>
|
<h2><i class="bi bi-send"></i> <?= t('Enviar Mensaje Directo') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($success): ?>
|
<?php if ($success): ?>
|
||||||
@@ -232,28 +270,28 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Plataforma</label>
|
<label class="form-label"><?= t('Plataforma') ?></label>
|
||||||
<select name="platform" id="platformSelect" class="form-select" required>
|
<select name="platform" id="platformSelect" class="form-select" required>
|
||||||
<option value="">-- Seleccionar --</option>
|
<option value="">-- <?= t('Seleccionar') ?> --</option>
|
||||||
<option value="discord">Discord</option>
|
<option value="discord">Discord</option>
|
||||||
<option value="telegram">Telegram</option>
|
<option value="telegram">Telegram</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Destinatario</label>
|
<label class="form-label"><?= t('Destinatario') ?></label>
|
||||||
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
|
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
|
||||||
<option value="">Selecciona una plataforma primero</option>
|
<option value=""><?= t('Selecciona una plataforma primero') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje</label>
|
<label class="form-label"><?= t('Mensaje') ?></label>
|
||||||
<textarea name="content" id="messageContent" class="form-control" rows="10" required></textarea>
|
<textarea name="content" id="messageContent" class="form-control" rows="10" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-send"></i> Enviar Ahora
|
<i class="bi bi-send"></i> <?= t('Enviar Ahora') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -264,7 +302,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><i class="bi bi-images"></i> Galería de Imágenes</h5>
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -272,7 +310,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<?php if (empty($galleryImages)): ?>
|
<?php if (empty($galleryImages)): ?>
|
||||||
<div class="col-12 text-center text-muted py-5">
|
<div class="col-12 text-center text-muted py-5">
|
||||||
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
||||||
<p class="mt-3">No hay imágenes en la galería</p>
|
<p class="mt-3"><?= t('No hay imágenes en la galería') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($galleryImages as $image): ?>
|
<?php foreach ($galleryImages as $image): ?>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Chat Telegram';
|
$pageTitle = t('Chat Telegram');
|
||||||
|
|
||||||
$interactions = [];
|
$interactions = [];
|
||||||
$selectedUser = $_GET['user_id'] ?? null;
|
$selectedUser = $_GET['user_id'] ?? null;
|
||||||
@@ -40,7 +41,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-telegram"></i> Chat Telegram</h2>
|
<h2><i class="bi bi-telegram"></i> <?= t('Chat Telegram') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($error)): ?>
|
<?php if (isset($error)): ?>
|
||||||
@@ -50,26 +51,26 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Usuarios</h5>
|
<h5 class="mb-0"><?= t('Usuarios') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<?php if (empty($interactions)): ?>
|
<?php if (empty($interactions)): ?>
|
||||||
<p class="text-muted p-3">No hay interacciones</p>
|
<p class="text-muted p-3"><?= t('No hay interacciones') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<?php if ($selectedUser): ?>
|
<?php if ($selectedUser): ?>
|
||||||
<a href="chat_telegram.php" class="list-group-item list-group-item-action">
|
<a href="chat_telegram.php" class="list-group-item list-group-item-action">
|
||||||
<i class="bi bi-arrow-left"></i> Volver a lista
|
<i class="bi bi-arrow-left"></i> <?= t('Volver a lista') ?>
|
||||||
</a>
|
</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($interactions as $user): ?>
|
<?php foreach ($interactions as $user): ?>
|
||||||
<a href="chat_telegram.php?user_id=<?= $user['user_id'] ?>" class="list-group-item list-group-item-action">
|
<a href="chat_telegram.php?user_id=<?= $user['user_id'] ?>" class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h6 class="mb-1"><?= htmlspecialchars($user['first_name'] ?? 'Usuario') ?></h6>
|
<h6 class="mb-1"><?= htmlspecialchars($user['first_name'] ?? t('Usuario')) ?></h6>
|
||||||
<small><?= $user['total_interactions'] ?></small>
|
<small><?= $user['total_interactions'] ?></small>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">@<?= htmlspecialchars($user['username'] ?? 'sin username') ?></small>
|
<small class="text-muted">@<?= htmlspecialchars($user['username'] ?? t('sin username')) ?></small>
|
||||||
</a>
|
</a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -81,8 +82,8 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Historial de Mensajes</h5>
|
<h5 class="mb-0"><?= t('Historial de Mensajes') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" style="max-height: 500px; overflow-y: auto;">
|
<div class="card-body" style="max-height: 500px; overflow-y: auto;">
|
||||||
<?php if ($selectedUser && !empty($interactions)): ?>
|
<?php if ($selectedUser && !empty($interactions)): ?>
|
||||||
@@ -90,13 +91,13 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="mb-3 p-2 <?= $msg['interaction_type'] === 'in' ? 'bg-light' : 'bg-white' ?> rounded">
|
<div class="mb-3 p-2 <?= $msg['interaction_type'] === 'in' ? 'bg-light' : 'bg-white' ?> rounded">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<?= date('d/m/Y H:i:s', strtotime($msg['interaction_date'])) ?>
|
<?= date('d/m/Y H:i:s', strtotime($msg['interaction_date'])) ?>
|
||||||
- <?= $msg['interaction_type'] === 'in' ? '📥 Usuario' : '📤 Bot' ?>
|
- <?= $msg['interaction_type'] === 'in' ? '📥 ' . t('Usuario') : '📤 Bot' ?>
|
||||||
</small>
|
</small>
|
||||||
<p class="mb-0 mt-1"><?= htmlspecialchars($msg['interaction_type'] ?? '') ?></p>
|
<p class="mb-0 mt-1"><?= htmlspecialchars($msg['interaction_type'] ?? '') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="text-muted text-center">Selecciona un usuario para ver el historial</p>
|
<p class="text-muted text-center"><?= t('Selecciona un usuario para ver el historial') ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,19 +6,23 @@ require_once __DIR__ . '/includes/message_handler.php';
|
|||||||
require_once __DIR__ . '/includes/schedule_helpers.php';
|
require_once __DIR__ . '/includes/schedule_helpers.php';
|
||||||
require_once __DIR__ . '/common/helpers/sender_factory.php';
|
require_once __DIR__ . '/common/helpers/sender_factory.php';
|
||||||
require_once __DIR__ . '/common/helpers/converter_factory.php';
|
require_once __DIR__ . '/common/helpers/converter_factory.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
|
|
||||||
function getTranslationButtons(PDO $pdo, string $text): array
|
function getTranslationButtons(PDO $pdo, string $text): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||||
$languages = $stmt->fetchAll();
|
$telegramLanguages = $stmtTelegram->fetchAll();
|
||||||
|
|
||||||
if (count($languages) <= 1) {
|
$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 [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
|
||||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ function buildDiscordTranslationButtons(array $languages, string $text): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageTitle = 'Crear Mensaje - Sistema de Mensajería';
|
$pageTitle = t('Crear Mensaje');
|
||||||
|
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
$templates = [];
|
$templates = [];
|
||||||
@@ -126,35 +130,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
|
|
||||||
// Obtener botones de traducción (convertir HTML a texto plano)
|
// Obtener botones de traducción (convertir HTML a texto plano)
|
||||||
$plainText = $schedule['content'];
|
$plainText = $schedule['content'];
|
||||||
// Convertir saltos de párrafo a saltos de línea
|
// Marcar donde hay imágenes
|
||||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
$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('/<p[^>]*>/i', '', $plainText);
|
||||||
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
||||||
// Eliminar HTML
|
// Eliminar HTML restante
|
||||||
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
// Limpiar espacios múltiples pero preservar saltos de línea
|
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
|
||||||
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
||||||
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
|
||||||
$plainText = trim($plainText);
|
$plainText = trim($plainText);
|
||||||
$translationButtons = getTranslationButtons($pdo, $plainText);
|
$translationButtons = getTranslationButtons($pdo, $plainText);
|
||||||
|
|
||||||
$segments = $sender->parseContent($schedule['content']);
|
$segments = $sender->parseContent($schedule['content']);
|
||||||
$messageCount = 0;
|
$messageCount = 0;
|
||||||
|
$totalSegments = count($segments);
|
||||||
|
$currentSegment = 0;
|
||||||
|
|
||||||
foreach ($segments as $segment) {
|
foreach ($segments as $segment) {
|
||||||
|
$currentSegment++;
|
||||||
|
$isLastSegment = ($currentSegment === $totalSegments);
|
||||||
|
|
||||||
if ($segment['type'] === 'text') {
|
if ($segment['type'] === 'text') {
|
||||||
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||||
if (!empty(trim($textContent))) {
|
if (!empty(trim($textContent))) {
|
||||||
// Agregar botones de traducción al último segmento de texto
|
|
||||||
$buttons = null;
|
$buttons = null;
|
||||||
if ($segment === end($segments)) {
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$buttons = $schedule['platform'] === 'telegram'
|
$buttons = $translationButtons['discord'];
|
||||||
? $translationButtons['telegram']
|
|
||||||
: $translationButtons['discord'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($schedule['platform'] === 'telegram') {
|
if ($schedule['platform'] === 'telegram') {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||||
} else {
|
} else {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
||||||
}
|
}
|
||||||
@@ -162,19 +170,44 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
}
|
}
|
||||||
} elseif ($segment['type'] === 'image') {
|
} elseif ($segment['type'] === 'image') {
|
||||||
$imagePath = $segment['src'];
|
$imagePath = $segment['src'];
|
||||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
|
||||||
|
|
||||||
if (file_exists($imgPath)) {
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
$messageCount++;
|
|
||||||
} elseif (strpos($imagePath, 'http') === 0) {
|
$buttons = null;
|
||||||
$embed = ['image' => ['url' => $imagePath]];
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
$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++;
|
$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("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||||
VALUES (?, ?, '', ?, NOW())
|
VALUES (?, ?, '', ?, NOW())
|
||||||
@@ -202,10 +235,10 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-plus-circle"></i> Crear Mensaje</h2>
|
<h2><i class="bi bi-pencil-square"></i> <?= t('Crear Mensaje') ?></h2>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="preview_message.php" class="btn btn-outline-secondary" target="_blank">
|
<a href="preview_message.php" class="btn btn-outline-secondary" target="_blank">
|
||||||
<i class="bi bi-eye"></i> Previsualizar
|
<i class="bi bi-eye"></i> <?= t('Previsualizar') ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -220,14 +253,14 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Contenido del Mensaje</h5>
|
<h5 class="mb-0"><?= t('Contenido del Mensaje') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Plantilla (opcional)</label>
|
<label class="form-label"><?= t('Plantilla (opcional)') ?></label>
|
||||||
<select class="form-select" id="templateSelect">
|
<select class="form-select" id="templateSelect">
|
||||||
<option value="">-- Seleccionar plantilla --</option>
|
<option value="">-- <?= t('Seleccionar plantilla') ?> --</option>
|
||||||
<?php foreach ($templates as $template): ?>
|
<?php foreach ($templates as $template): ?>
|
||||||
<option value="<?= htmlspecialchars($template['message_content']) ?>">
|
<option value="<?= htmlspecialchars($template['message_content']) ?>">
|
||||||
<?= htmlspecialchars($template['name']) ?>
|
<?= htmlspecialchars($template['name']) ?>
|
||||||
@@ -237,7 +270,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje</label>
|
<label class="form-label"><?= t('Mensaje') ?></label>
|
||||||
<textarea name="content" id="messageContent" class="form-control" rows="12"></textarea>
|
<textarea name="content" id="messageContent" class="form-control" rows="12"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,62 +279,62 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Destinatario</h5>
|
<h5 class="mb-0"><?= t('Destinatario') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Plataforma</label>
|
<label class="form-label"><?= t('Plataforma') ?></label>
|
||||||
<select name="platform" id="platformSelect" class="form-select" required>
|
<select name="platform" id="platformSelect" class="form-select" required>
|
||||||
<option value="">-- Seleccionar --</option>
|
<option value="">-- <?= t('Seleccionar') ?> --</option>
|
||||||
<option value="discord">Discord</option>
|
<option value="discord">Discord</option>
|
||||||
<option value="telegram">Telegram</option>
|
<option value="telegram">Telegram</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Destinatario</label>
|
<label class="form-label"><?= t('Destinatario') ?></label>
|
||||||
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
|
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
|
||||||
<option value="">Selecciona una plataforma primero</option>
|
<option value=""><?= t('Selecciona una plataforma primero') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Programación</h5>
|
<h5 class="mb-0"><?= t('Programación') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Tipo de envío</label>
|
<label class="form-label"><?= t('Tipo de envío') ?></label>
|
||||||
<select name="send_type" id="sendType" class="form-select" required>
|
<select name="send_type" id="sendType" class="form-select" required>
|
||||||
<option value="now">Enviar ahora</option>
|
<option value="now"><?= t('Enviar ahora') ?></option>
|
||||||
<option value="later">Programar para después</option>
|
<option value="later"><?= t('Programar para después') ?></option>
|
||||||
<option value="recurring">Recurrente</option>
|
<option value="recurring"><?= t('Recurrente') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="datetimeField" style="display: none;">
|
<div class="mb-3" id="datetimeField" style="display: none;">
|
||||||
<label class="form-label">Fecha y hora</label>
|
<label class="form-label"><?= t('Fecha y hora') ?></label>
|
||||||
<input type="datetime-local" name="send_datetime" class="form-control">
|
<input type="datetime-local" name="send_datetime" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="recurringFields" style="display: none;">
|
<div id="recurringFields" style="display: none;">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Días</label>
|
<label class="form-label"><?= t('Días') ?></label>
|
||||||
<select name="recurring_days" class="form-select">
|
<select name="recurring_days" class="form-select">
|
||||||
<option value="monday">Lunes</option>
|
<option value="monday"><?= t('Lunes') ?></option>
|
||||||
<option value="tuesday">Martes</option>
|
<option value="tuesday"><?= t('Martes') ?></option>
|
||||||
<option value="wednesday">Miércoles</option>
|
<option value="wednesday"><?= t('Miércoles') ?></option>
|
||||||
<option value="thursday">Jueves</option>
|
<option value="thursday"><?= t('Jueves') ?></option>
|
||||||
<option value="friday">Viernes</option>
|
<option value="friday"><?= t('Viernes') ?></option>
|
||||||
<option value="saturday">Sábado</option>
|
<option value="saturday"><?= t('Sábado') ?></option>
|
||||||
<option value="sunday">Domingo</option>
|
<option value="sunday"><?= t('Domingo') ?></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Hora</label>
|
<label class="form-label"><?= t('Hora') ?></label>
|
||||||
<input type="time" name="recurring_time" class="form-control" value="09:00">
|
<input type="time" name="recurring_time" class="form-control" value="09:00">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,9 +343,9 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<button type="submit" class="btn btn-primary btn-lg">
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
<i class="bi bi-send"></i> Enviar
|
<i class="bi bi-send"></i> <?= t('Enviar') ?>
|
||||||
</button>
|
</button>
|
||||||
<a href="index.php" class="btn btn-outline-secondary">Cancelar</a>
|
<a href="index.php" class="btn btn-outline-secondary"><?= t('Cancelar') ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,7 +356,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><i class="bi bi-images"></i> Galería de Imágenes</h5>
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -331,7 +364,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<?php if (empty($galleryImages)): ?>
|
<?php if (empty($galleryImages)): ?>
|
||||||
<div class="col-12 text-center text-muted py-5">
|
<div class="col-12 text-center text-muted py-5">
|
||||||
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
||||||
<p class="mt-3">No hay imágenes en la galería</p>
|
<p class="mt-3"><?= t('No hay imágenes en la galería') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($galleryImages as $image): ?>
|
<?php foreach ($galleryImages as $image): ?>
|
||||||
|
|||||||
0
database/.gitkeep
Normal file
0
database/.gitkeep
Normal file
@@ -95,34 +95,29 @@ class DiscordSender
|
|||||||
$result = null;
|
$result = null;
|
||||||
|
|
||||||
if (!empty($images)) {
|
if (!empty($images)) {
|
||||||
// Verificar si las imágenes son locales o URLs
|
|
||||||
$localImages = [];
|
|
||||||
$remoteImages = [];
|
$remoteImages = [];
|
||||||
|
|
||||||
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
|
|
||||||
foreach ($images as $imageUrl) {
|
foreach ($images as $imageUrl) {
|
||||||
if (strpos($imageUrl, 'http') === 0) {
|
if (strpos($imageUrl, 'http') === 0) {
|
||||||
// Es una URL remota
|
|
||||||
$remoteImages[] = $imageUrl;
|
$remoteImages[] = $imageUrl;
|
||||||
} elseif (file_exists($imageUrl)) {
|
} else {
|
||||||
// Es un archivo local
|
// Convertir a URL usando APP_URL
|
||||||
$localImages[] = $imageUrl;
|
$remoteImages[] = $baseUrl . '/' . ltrim($imageUrl, '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enviar imágenes locales como adjuntos
|
// Enviar todas las imágenes como embeds
|
||||||
if (!empty($localImages)) {
|
$result = $this->sendMessage($channelId, $content, null, $buttons);
|
||||||
$result = $this->sendMessageWithAttachments($channelId, $content, $localImages);
|
|
||||||
} else {
|
|
||||||
$result = $this->sendMessage($channelId, $content, null, $buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enviar imágenes remotas como embeds
|
|
||||||
foreach ($remoteImages as $imageUrl) {
|
foreach ($remoteImages as $imageUrl) {
|
||||||
$embed = [
|
$embed = [
|
||||||
'image' => ['url' => $imageUrl]
|
'image' => ['url' => $imageUrl]
|
||||||
];
|
];
|
||||||
$result = $this->sendMessage($channelId, '', $embed, $buttons);
|
$result = $this->sendMessage($channelId, '', $embed);
|
||||||
$buttons = null; // Solo enviar botones en el primer mensaje
|
$buttons = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$result = $this->sendMessage($channelId, $content, null, $buttons);
|
$result = $this->sendMessage($channelId, $content, null, $buttons);
|
||||||
@@ -136,26 +131,48 @@ class DiscordSender
|
|||||||
* Divide el contenido en segmentos y los envía manteniendo el orden
|
* Divide el contenido en segmentos y los envía manteniendo el orden
|
||||||
*/
|
*/
|
||||||
public function sendContentWithOrderedImages(string $channelId, array $segments): void
|
public function sendContentWithOrderedImages(string $channelId, array $segments): void
|
||||||
|
{
|
||||||
|
$this->sendContentWithOrderedImagesAndButtons($channelId, $segments, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendContentWithOrderedImagesAndButtons(string $channelId, array $segments, ?array $buttons = null): void
|
||||||
{
|
{
|
||||||
$channelId = $this->resolveUserToDmChannel($channelId);
|
$channelId = $this->resolveUserToDmChannel($channelId);
|
||||||
|
|
||||||
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
|
|
||||||
|
$totalSegments = count($segments);
|
||||||
|
$currentSegment = 0;
|
||||||
|
|
||||||
foreach ($segments as $segment) {
|
foreach ($segments as $segment) {
|
||||||
|
$currentSegment++;
|
||||||
|
$isLastSegment = ($currentSegment === $totalSegments);
|
||||||
|
|
||||||
if ($segment['type'] === 'text') {
|
if ($segment['type'] === 'text') {
|
||||||
// Enviar texto
|
|
||||||
if (!empty(trim($segment['content']))) {
|
if (!empty(trim($segment['content']))) {
|
||||||
$this->sendMessage($channelId, $segment['content']);
|
// Solo enviar botones en el último segmento de texto
|
||||||
|
if ($isLastSegment && $buttons) {
|
||||||
|
$this->sendMessage($channelId, $segment['content'], null, $buttons);
|
||||||
|
} else {
|
||||||
|
$this->sendMessage($channelId, $segment['content']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ($segment['type'] === 'image') {
|
} elseif ($segment['type'] === 'image') {
|
||||||
// Enviar imagen
|
|
||||||
$imagePath = $segment['src'];
|
$imagePath = $segment['src'];
|
||||||
|
|
||||||
if (strpos($imagePath, 'http') === 0) {
|
if (strpos($imagePath, 'http') === 0) {
|
||||||
// URL remota - enviar como embed
|
|
||||||
$embed = ['image' => ['url' => $imagePath]];
|
$embed = ['image' => ['url' => $imagePath]];
|
||||||
|
} else {
|
||||||
|
$imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
|
||||||
|
$embed = ['image' => ['url' => $imageUrl]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solo enviar botones en el último segmento
|
||||||
|
if ($isLastSegment && $buttons) {
|
||||||
|
$this->sendMessage($channelId, '', $embed, $buttons);
|
||||||
|
} else {
|
||||||
$this->sendMessage($channelId, '', $embed);
|
$this->sendMessage($channelId, '', $embed);
|
||||||
} elseif (file_exists($imagePath)) {
|
|
||||||
// Archivo local - enviar como adjunto
|
|
||||||
$this->sendMessageWithAttachments($channelId, '', [$imagePath]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
149
discord_bot.php
149
discord_bot.php
@@ -34,14 +34,19 @@ $discord->on(Event::GUILD_MEMBER_ADD, function (Member $member, Discord $discord
|
|||||||
try {
|
try {
|
||||||
$pdo = getDbConnection();
|
$pdo = getDbConnection();
|
||||||
|
|
||||||
|
// Obtener idioma por defecto dinámico
|
||||||
|
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
|
||||||
|
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
||||||
VALUES (?, ?, 'user', 'discord', 'es', 'agent')
|
VALUES (?, ?, 'user', 'discord', ?, 'agent')
|
||||||
ON DUPLICATE KEY UPDATE name = VALUES(name)
|
ON DUPLICATE KEY UPDATE name = VALUES(name)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$member->user->id,
|
$member->user->id,
|
||||||
$member->user->username
|
$member->user->username,
|
||||||
|
$defaultLang
|
||||||
]);
|
]);
|
||||||
|
|
||||||
echo "Usuario registrado en la base de datos" . PHP_EOL;
|
echo "Usuario registrado en la base de datos" . PHP_EOL;
|
||||||
@@ -76,6 +81,11 @@ $discord->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord
|
|||||||
sendDiscordWelcomeMessageOnMessage($pdo, $message, $username);
|
sendDiscordWelcomeMessageOnMessage($pdo, $message, $username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignorar si es solo contenido vacío sin texto (solo attachments/GIFs/imágenes)
|
||||||
|
if (empty(trim($content))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (str_starts_with($content, '#')) {
|
if (str_starts_with($content, '#')) {
|
||||||
$command = ltrim($content, '#');
|
$command = ltrim($content, '#');
|
||||||
handleTemplateCommand($pdo, $message, $command);
|
handleTemplateCommand($pdo, $message, $command);
|
||||||
@@ -108,40 +118,31 @@ function handleTemplateCommand(PDO $pdo, Message $message, string $command): voi
|
|||||||
$template = $stmt->fetch();
|
$template = $stmt->fetch();
|
||||||
|
|
||||||
if ($template) {
|
if ($template) {
|
||||||
require_once __DIR__ . '/discord/converters/HtmlToDiscordMarkdownConverter.php';
|
|
||||||
require_once __DIR__ . '/discord/DiscordSender.php';
|
require_once __DIR__ . '/discord/DiscordSender.php';
|
||||||
require_once __DIR__ . '/src/Translate.php';
|
|
||||||
|
|
||||||
$converter = new \Discord\Converters\HtmlToDiscordMarkdownConverter();
|
$sender = new \Discord\DiscordSender();
|
||||||
|
|
||||||
$images = $converter->extractImages($template['message_content']);
|
// Parsear el contenido manteniendo el orden texto-imagen
|
||||||
$contentWithoutImages = $converter->removeImages($template['message_content']);
|
$segments = $sender->parseContent($template['message_content']);
|
||||||
$content = $converter->convert($contentWithoutImages);
|
|
||||||
|
|
||||||
require_once __DIR__ . '/discord/DiscordSender.php';
|
if (!empty($segments)) {
|
||||||
require_once __DIR__ . '/src/Translate.php';
|
// Obtener texto completo para botones de traducción
|
||||||
|
$plainText = $template['message_content'];
|
||||||
|
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
||||||
|
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
||||||
|
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
|
||||||
|
$plainText = strip_tags($plainText);
|
||||||
|
$plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
$plainText = trim($plainText);
|
||||||
|
|
||||||
$sender = new \Discord\DiscordSender();
|
$translationButtons = getDiscordTranslationButtons($pdo, $plainText);
|
||||||
|
|
||||||
$plainText = $template['message_content'];
|
// Enviar contenido ordenado con botones
|
||||||
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
$sender->sendContentWithOrderedImagesAndButtons((string)$message->channel_id, $segments, $translationButtons);
|
||||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
}
|
||||||
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
|
|
||||||
$plainText = strip_tags($plainText);
|
|
||||||
$plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
||||||
$plainText = trim($plainText);
|
|
||||||
|
|
||||||
$translationButtons = getDiscordTranslationButtons($pdo, $plainText);
|
|
||||||
|
|
||||||
if (!empty($images)) {
|
|
||||||
$sender->sendMessageWithImages((string)$message->channel_id, $content, $images, $translationButtons);
|
|
||||||
} else {
|
} else {
|
||||||
$sender->sendMessage((string)$message->channel_id, $content, null, $translationButtons);
|
$message->channel->sendMessage("❌ Plantilla no encontrada: #{$command}");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
$message->channel->sendMessage("❌ Plantilla no encontrada: #{$command}");
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$message->channel->sendMessage("❌ Error: " . $e->getMessage());
|
$message->channel->sendMessage("❌ Error: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,7 @@ function handleTemplateCommand(PDO $pdo, Message $message, string $command): voi
|
|||||||
|
|
||||||
function getDiscordTranslationButtons(PDO $pdo, string $text): array
|
function getDiscordTranslationButtons(PDO $pdo, string $text): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
|
||||||
$languages = $stmt->fetchAll();
|
$languages = $stmt->fetchAll();
|
||||||
|
|
||||||
if (count($languages) <= 1) {
|
if (count($languages) <= 1) {
|
||||||
@@ -342,10 +343,15 @@ function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $te
|
|||||||
|
|
||||||
// Detectar idioma del mensaje (sin emojis para mejor precisión)
|
// Detectar idioma del mensaje (sin emojis para mejor precisión)
|
||||||
$textForDetection = stripEmojisForDetection($text);
|
$textForDetection = stripEmojisForDetection($text);
|
||||||
$detectedLang = $translator->detectLanguage($textForDetection) ?? 'es';
|
|
||||||
|
// Obtener idioma por defecto dinámico
|
||||||
|
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
|
||||||
|
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
|
||||||
|
|
||||||
|
$detectedLang = $translator->detectLanguage($textForDetection) ?? $defaultLang;
|
||||||
|
|
||||||
// Obtener idiomas activos de la base de datos
|
// Obtener idiomas activos de la base de datos
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
|
||||||
$activeLanguages = $stmt->fetchAll();
|
$activeLanguages = $stmt->fetchAll();
|
||||||
|
|
||||||
if (count($activeLanguages) <= 1) {
|
if (count($activeLanguages) <= 1) {
|
||||||
@@ -360,13 +366,11 @@ function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $te
|
|||||||
// Preparar botones
|
// Preparar botones
|
||||||
$buttons = [];
|
$buttons = [];
|
||||||
foreach ($activeLanguages as $lang) {
|
foreach ($activeLanguages as $lang) {
|
||||||
if ($lang['language_code'] !== $detectedLang) {
|
$buttons[] = [
|
||||||
$buttons[] = [
|
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
|
||||||
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
|
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
|
||||||
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
|
'style' => 1
|
||||||
'style' => 1
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enviar mensaje con botones usando MessageBuilder correctamente
|
// Enviar mensaje con botones usando MessageBuilder correctamente
|
||||||
@@ -476,14 +480,18 @@ function sendDiscordWelcomeMessage(PDO $pdo, Member $member, Discord $discord):
|
|||||||
|
|
||||||
function registerDiscordUser(PDO $pdo, $user): void
|
function registerDiscordUser(PDO $pdo, $user): void
|
||||||
{
|
{
|
||||||
|
// Obtener idioma por defecto de la base de datos (el primero activo)
|
||||||
|
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
|
||||||
|
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
||||||
VALUES (?, ?, 'user', 'discord', 'es', 'agent')
|
VALUES (?, ?, 'user', 'discord', ?, 'agent')
|
||||||
ON DUPLICATE KEY UPDATE name = VALUES(name)
|
ON DUPLICATE KEY UPDATE name = VALUES(name)
|
||||||
");
|
");
|
||||||
|
|
||||||
$name = $user->username ?? 'Usuario';
|
$name = $user->username ?? 'Usuario';
|
||||||
$stmt->execute([$user->id, $name]);
|
$stmt->execute([$user->id, $name, $defaultLang]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendDiscordWelcomeMessageOnMessage(PDO $pdo, Message $message, string $username): void
|
function sendDiscordWelcomeMessageOnMessage(PDO $pdo, Message $message, string $username): void
|
||||||
@@ -512,13 +520,11 @@ function handleTranslateInteraction($interaction, string $customId): void
|
|||||||
$targetLang = str_replace('translate_', '', $parts[0]);
|
$targetLang = str_replace('translate_', '', $parts[0]);
|
||||||
$textHash = $parts[1] ?? '';
|
$textHash = $parts[1] ?? '';
|
||||||
|
|
||||||
// Obtener texto de la base de datos primero
|
|
||||||
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
|
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
|
||||||
$stmt->execute([$textHash]);
|
$stmt->execute([$textHash]);
|
||||||
$row = $stmt->fetch();
|
$row = $stmt->fetch();
|
||||||
|
|
||||||
if (!$row || empty($row['original_text'])) {
|
if (!$row || empty($row['original_text'])) {
|
||||||
// Enviar error efímero
|
|
||||||
$builder = \Discord\Builders\MessageBuilder::new()
|
$builder = \Discord\Builders\MessageBuilder::new()
|
||||||
->setContent('❌ Error: Texto no encontrado');
|
->setContent('❌ Error: Texto no encontrado');
|
||||||
$interaction->respondWithMessage($builder, true);
|
$interaction->respondWithMessage($builder, true);
|
||||||
@@ -527,34 +533,24 @@ function handleTranslateInteraction($interaction, string $customId): void
|
|||||||
|
|
||||||
$originalText = $row['original_text'];
|
$originalText = $row['original_text'];
|
||||||
|
|
||||||
// Responder inmediatamente con "Traduciendo..."
|
try {
|
||||||
$loadingBuilder = \Discord\Builders\MessageBuilder::new()
|
$interaction->acknowledge();
|
||||||
->setContent("⏳ Traduciendo a " . strtoupper($targetLang) . "...");
|
} catch (\Exception $e) {
|
||||||
$interaction->respondWithMessage($loadingBuilder, true);
|
error_log("Acknowledge error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Ahora traducir (esto puede tardar)
|
$textForTranslation = stripEmojisForDetection($originalText);
|
||||||
// Usar texto sin emojis para detectar idioma y traducir con mayor precisión
|
$translated = $translator->translatePartial($textForTranslation, $targetLang);
|
||||||
$textForDetection = stripEmojisForDetection($originalText);
|
|
||||||
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es';
|
|
||||||
$translated = $translator->translate($textForDetection, $sourceLang, $targetLang);
|
|
||||||
|
|
||||||
if ($translated) {
|
if ($translated) {
|
||||||
// Limpiar todo el HTML
|
|
||||||
$translated = strip_tags($translated);
|
$translated = strip_tags($translated);
|
||||||
// Limpiar asteriscos duplicados
|
|
||||||
$translated = preg_replace('/\*+/', '', $translated);
|
$translated = preg_replace('/\*+/', '', $translated);
|
||||||
// Limpiar comandos con espacios
|
|
||||||
$translated = preg_replace('/\/(\s+)(\w+)/', '/$2', $translated);
|
$translated = preg_replace('/\/(\s+)(\w+)/', '/$2', $translated);
|
||||||
$translated = preg_replace('/#(\s+)(\w+)/', '#$2', $translated);
|
$translated = preg_replace('/#(\s+)(\w+)/', '#$2', $translated);
|
||||||
// Limpiar saltos de línea extras
|
|
||||||
$translated = preg_replace('/\n\s*\n/', "\n", $translated);
|
$translated = preg_replace('/\n\s*\n/', "\n", $translated);
|
||||||
|
|
||||||
// Actualizar la respuesta con la traducción real
|
|
||||||
// Preservar emojis del original si los hay
|
|
||||||
$displayText = trim($translated);
|
$displayText = trim($translated);
|
||||||
if (strpos($originalText, '👍') !== false || preg_match('/[\x{1F300}-\x{1F9FF}]/u', $originalText)) {
|
if (strpos($originalText, '👍') !== false || preg_match('/[\x{1F300}-\x{1F9FF}]/u', $originalText)) {
|
||||||
// Si el original tenía emojis, intentar identificarlos
|
|
||||||
// Para mensajes simples, los emojis generalmente están al inicio o final
|
|
||||||
$emojiPrefix = '';
|
$emojiPrefix = '';
|
||||||
$emojiSuffix = '';
|
$emojiSuffix = '';
|
||||||
if (preg_match('/^([\x{1F300}-\x{1F9FF}]+)\s*/u', $originalText, $match)) {
|
if (preg_match('/^([\x{1F300}-\x{1F9FF}]+)\s*/u', $originalText, $match)) {
|
||||||
@@ -566,25 +562,32 @@ function handleTranslateInteraction($interaction, string $customId): void
|
|||||||
$displayText = $emojiPrefix . $displayText . $emojiSuffix;
|
$displayText = $emojiPrefix . $displayText . $emojiSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalBuilder = \Discord\Builders\MessageBuilder::new()
|
$messageText = "🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText;
|
||||||
->setContent("🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText);
|
|
||||||
$interaction->updateOriginalResponse($finalBuilder);
|
$buttons = getDiscordTranslationButtons($pdo, $originalText);
|
||||||
|
|
||||||
|
$builder = \Discord\Builders\MessageBuilder::new()
|
||||||
|
->setContent($messageText);
|
||||||
|
|
||||||
|
if (!empty($buttons)) {
|
||||||
|
$actionRow = new \Discord\Builders\Components\ActionRow();
|
||||||
|
foreach ($buttons[0]['components'] as $btn) {
|
||||||
|
$button = \Discord\Builders\Components\Button::new(\Discord\Builders\Components\Button::STYLE_PRIMARY)
|
||||||
|
->setLabel($btn['label'])
|
||||||
|
->setCustomId($btn['custom_id']);
|
||||||
|
$actionRow->addComponent($button);
|
||||||
|
}
|
||||||
|
$builder->addComponent($actionRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
$interaction->message->edit($builder);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Actualizar con error
|
$interaction->message->edit(\Discord\Builders\MessageBuilder::new()->setContent("❌ Error al traducir"));
|
||||||
$errorBuilder = \Discord\Builders\MessageBuilder::new()
|
|
||||||
->setContent("❌ Error al traducir");
|
|
||||||
$interaction->updateOriginalResponse($errorBuilder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Discord translate error: " . $e->getMessage());
|
error_log("Discord translate error: " . $e->getMessage());
|
||||||
try {
|
|
||||||
$builder = \Discord\Builders\MessageBuilder::new()
|
|
||||||
->setContent('❌ Error en el proceso de traducción');
|
|
||||||
$interaction->respondWithMessage($builder, true);
|
|
||||||
} catch (Exception $inner) {
|
|
||||||
error_log("Error responding to interaction: " . $inner->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,52 @@
|
|||||||
FROM php:8.2-cli
|
FROM php:8.3-apache
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libzip-dev \
|
libzip-dev \
|
||||||
|
libpng-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libfreetype6-dev \
|
||||||
unzip \
|
unzip \
|
||||||
supervisor \
|
supervisor \
|
||||||
nano \
|
nano \
|
||||||
&& pecl install curl \
|
cron \
|
||||||
&& docker-php-ext-enable curl \
|
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||||
&& docker-php-ext-install pdo_mysql zip \
|
&& docker-php-ext-install pdo_mysql zip gd curl \
|
||||||
|
&& pecl install redis \
|
||||||
|
&& docker-php-ext-enable redis \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Habilitar módulos de Apache
|
||||||
|
RUN a2enmod rewrite headers ssl
|
||||||
|
|
||||||
|
# Copiar Composer
|
||||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
# Crear directorio de logs
|
||||||
|
RUN mkdir -p /var/www/html/logs && chown -R www-data:www-data /var/www/html/logs
|
||||||
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/var/www/html/docker/supervisord.conf"]
|
# Copiar configuración de Apache
|
||||||
|
COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf
|
||||||
|
|
||||||
|
# Copiar configuración de Supervisor
|
||||||
|
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# Copiar el proyecto completo
|
||||||
|
COPY . /var/www/html/
|
||||||
|
|
||||||
|
# Instalar dependencias de PHP
|
||||||
|
RUN composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
# Permisos correctos
|
||||||
|
RUN chown -R www-data:www-data /var/www/html \
|
||||||
|
&& chmod -R 755 /var/www/html \
|
||||||
|
&& chmod -R 775 /var/www/html/logs \
|
||||||
|
&& chmod -R 775 /var/www/html/galeria
|
||||||
|
|
||||||
|
# Puerto expuesto
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Iniciar Apache y Supervisor
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
|
|||||||
33
docker/apache.conf
Normal file
33
docker/apache.conf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
DocumentRoot /var/www/html
|
||||||
|
|
||||||
|
<Directory /var/www/html>
|
||||||
|
Options Indexes FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
# Permitir acceso a archivos estáticos
|
||||||
|
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$">
|
||||||
|
Require all granted
|
||||||
|
</FilesMatch>
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
|
||||||
|
# Aumentar límites para uploads
|
||||||
|
LimitRequestBody 10485760
|
||||||
|
|
||||||
|
# Timeout para scripts largos
|
||||||
|
TimeOut 300
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# Configuración de PHP
|
||||||
|
<IfModule mod_php.c>
|
||||||
|
php_value upload_max_filesize 10M
|
||||||
|
php_value post_max_size 10M
|
||||||
|
php_value max_execution_time 300
|
||||||
|
php_value max_input_time 300
|
||||||
|
</IfModule>
|
||||||
@@ -1,34 +1,52 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
bot:
|
app:
|
||||||
image: php:8.2-cli
|
build:
|
||||||
container_name: lastwar_bot
|
context: ..
|
||||||
|
dockerfile: docker/Dockerfile
|
||||||
|
container_name: lastwar_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
ports:
|
||||||
- ../lastwar:/var/www/html/lastwar
|
- "8080:80"
|
||||||
working_dir: /var/www/html/lastwar
|
env_file:
|
||||||
command: /usr/local/bin/supervisord -c /var/www/html/lastwar/docker/supervisord.conf
|
- ../.env
|
||||||
environment:
|
environment:
|
||||||
- PHP_DISPLAY_ERRORS=On
|
- DB_HOST=db
|
||||||
- PHP_ERROR_REPORTING=E_ALL
|
- DB_PORT=3306
|
||||||
|
- DB_NAME=${DB_NAME:-lastwar}
|
||||||
|
- DB_USER=${DB_USER:-lastwar}
|
||||||
|
- DB_PASS=${DB_PASS:-}
|
||||||
|
- LIBRETRANSLATE_URL=http://libretranslate:5000
|
||||||
|
volumes:
|
||||||
|
- app_logs:/var/www/html/logs
|
||||||
|
- app_galeria:/var/www/html/galeria
|
||||||
networks:
|
networks:
|
||||||
- bot_network
|
- lastwar_network
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
libretranslate:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
container_name: lastwar_db
|
container_name: lastwar_db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${DB_PASS:-}
|
MYSQL_ROOT_PASSWORD: ${DB_PASS:-rootpassword}
|
||||||
MYSQL_DATABASE: ${DB_NAME:-bot}
|
MYSQL_DATABASE: ${DB_NAME:-lastwar}
|
||||||
|
MYSQL_USER: ${DB_USER:-lastwar}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASS:-}
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
- ./db:/docker-entrypoint-initdb.d
|
- ../database:/docker-entrypoint-initdb.d:ro
|
||||||
networks:
|
networks:
|
||||||
- bot_network
|
- lastwar_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
libretranslate:
|
libretranslate:
|
||||||
image: libretranslate/libretranslate
|
image: libretranslate/libretranslate
|
||||||
@@ -37,11 +55,18 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
networks:
|
networks:
|
||||||
- bot_network
|
- lastwar_network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:5000/languages || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
bot_network:
|
lastwar_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data:
|
db_data:
|
||||||
|
app_logs:
|
||||||
|
app_galeria:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
[program:bot_process_queue]
|
|
||||||
process_name=%(program_name)s_%(process_num)02d
|
|
||||||
command=php /var/www/html/lastwar/process_queue.php
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
user=www-data
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/var/www/html/lastwar/logs/process_queue.log
|
|
||||||
stdout_logfile_maxbytes=10MB
|
|
||||||
stderr_logfile=/var/www/html/lastwar/logs/process_queue_error.log
|
|
||||||
redirect_stderr=true
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[program:bot_translation_queue]
|
|
||||||
process_name=%(program_name)s_%(process_num)02d
|
|
||||||
command=php /var/www/html/lastwar/process_translation_queue.php
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
user=www-data
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/var/www/html/lastwar/logs/translation_queue.log
|
|
||||||
stdout_logfile_maxbytes=10MB
|
|
||||||
stderr_logfile=/var/www/html/lastwar/logs/translation_queue_error.log
|
|
||||||
redirect_stderr=true
|
|
||||||
@@ -1,44 +1,61 @@
|
|||||||
|
[unix_http_server]
|
||||||
|
file=/var/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
[supervisord]
|
[supervisord]
|
||||||
nodaemon=true
|
nodaemon=true
|
||||||
logfile=/var/www/html/lastwar/logs/supervisor.log
|
logfile=/var/www/html/logs/supervisor.log
|
||||||
logfile_maxbytes=50MB
|
logfile_maxbytes=50MB
|
||||||
pidfile=/var/run/supervisord.pid
|
pidfile=/var/run/supervisord.pid
|
||||||
childlogdir=/var/www/html/lastwar/logs
|
childlogdir=/var/www/html/logs
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
|
[program:apache]
|
||||||
|
process_name=%(program_name)s
|
||||||
|
command=/usr/sbin/apache2ctl -D FOREGROUND
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/var/www/html/logs/apache.log
|
||||||
|
stdout_logfile_maxbytes=10MB
|
||||||
|
stderr_logfile=/var/www/html/logs/apache_error.log
|
||||||
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:bot_discord]
|
[program:bot_discord]
|
||||||
process_name=%(program_name)s
|
process_name=%(program_name)s
|
||||||
command=php /var/www/html/lastwar/discord_bot.php
|
command=php /var/www/html/discord_bot.php
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
user=www-data
|
|
||||||
numprocs=1
|
numprocs=1
|
||||||
stdout_logfile=/var/www/html/lastwar/logs/discord_bot.log
|
stdout_logfile=/var/www/html/logs/discord_bot.log
|
||||||
stdout_logfile_maxbytes=10MB
|
stdout_logfile_maxbytes=10MB
|
||||||
stderr_logfile=/var/www/html/lastwar/logs/discord_bot_error.log
|
stderr_logfile=/var/www/html/logs/discord_bot_error.log
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:bot_process_queue]
|
[program:bot_process_queue]
|
||||||
process_name=%(program_name)s
|
process_name=%(program_name)s
|
||||||
command=php /var/www/html/lastwar/process_queue.php
|
command=php /var/www/html/process_queue.php
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
user=www-data
|
|
||||||
numprocs=1
|
numprocs=1
|
||||||
stdout_logfile=/var/www/html/lastwar/logs/process_queue.log
|
stdout_logfile=/var/www/html/logs/process_queue.log
|
||||||
stdout_logfile_maxbytes=10MB
|
stdout_logfile_maxbytes=10MB
|
||||||
stderr_logfile=/var/www/html/lastwar/logs/process_queue_error.log
|
stderr_logfile=/var/www/html/logs/process_queue_error.log
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:bot_translation_queue]
|
[program:bot_translation_queue]
|
||||||
process_name=%(program_name)s
|
process_name=%(program_name)s
|
||||||
command=php /var/www/html/lastwar/process_translation_queue.php
|
command=php /var/www/html/process_translation_queue.php
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
user=www-data
|
|
||||||
numprocs=1
|
numprocs=1
|
||||||
stdout_logfile=/var/www/html/lastwar/logs/translation_queue.log
|
stdout_logfile=/var/www/html/logs/translation_queue.log
|
||||||
stdout_logfile_maxbytes=10MB
|
stdout_logfile_maxbytes=10MB
|
||||||
stderr_logfile=/var/www/html/lastwar/logs/translation_queue_error.log
|
stderr_logfile=/var/www/html/logs/translation_queue_error.log
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
[group:bot_workers]
|
[group:bot_workers]
|
||||||
|
|||||||
0
galeria/.gitkeep
Normal file
0
galeria/.gitkeep
Normal file
34
gallery.php
34
gallery.php
@@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
|
require_once __DIR__ . '/includes/activity_logger.php';
|
||||||
checkSession();
|
checkSession();
|
||||||
|
|
||||||
$pageTitle = 'Galería de Imágenes';
|
$pageTitle = t('Galería de Imágenes');
|
||||||
|
|
||||||
$galleryPath = __DIR__ . '/galeria';
|
$galleryPath = __DIR__ . '/galeria';
|
||||||
$images = [];
|
$images = [];
|
||||||
@@ -58,14 +60,14 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-images"></i> Galería de Imágenes</h2>
|
<h2><i class="bi bi-images"></i> <?= t('Galería de Imágenes') ?></h2>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
|
||||||
<i class="bi bi-upload"></i> Subir Imagen
|
<i class="bi bi-upload"></i> <?= t('Subir Imagen') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (empty($images)): ?>
|
<?php if (empty($images)): ?>
|
||||||
<div class="alert alert-info">No hay imágenes en la galería</div>
|
<div class="alert alert-info"><?= t('No hay imágenes en la galería') ?></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<?php foreach ($images as $image): ?>
|
<?php foreach ($images as $image): ?>
|
||||||
@@ -75,16 +77,16 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="card-body py-2">
|
<div class="card-body py-2">
|
||||||
<small class="d-block text-truncate"><?= htmlspecialchars($image) ?></small>
|
<small class="d-block text-truncate"><?= htmlspecialchars($image) ?></small>
|
||||||
<div class="btn-group btn-group-sm mt-2">
|
<div class="btn-group btn-group-sm mt-2">
|
||||||
<button class="btn btn-outline-primary" onclick="copyUrl('<?= urlencode($image) ?>')" title="Copiar URL">
|
<button class="btn btn-outline-primary" onclick="copyUrl('<?= urlencode($image) ?>')" title="<?= t('Copiar URL') ?>">
|
||||||
<i class="bi bi-clipboard"></i>
|
<i class="bi bi-clipboard"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary" onclick="renameImage('<?= htmlspecialchars($image) ?>')" title="Renombrar">
|
<button class="btn btn-outline-secondary" onclick="renameImage('<?= htmlspecialchars($image) ?>')" title="<?= t('Renombrar') ?>">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar esta imagen?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar esta imagen?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="filename" value="<?= htmlspecialchars($image) ?>">
|
<input type="hidden" name="filename" value="<?= htmlspecialchars($image) ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger" title="Eliminar">
|
<button type="submit" class="btn btn-outline-danger" title="<?= t('Eliminar') ?>">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -102,14 +104,14 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="action" value="upload">
|
<input type="hidden" name="action" value="upload">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Subir Imagen</h5>
|
<h5 class="modal-title"><?= t('Subir Imagen') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="file" name="image" class="form-control" accept="image/*" required>
|
<input type="file" name="image" class="form-control" accept="image/*" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Subir</button>
|
<button type="submit" class="btn btn-primary"><?= t('Subir') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,18 +124,18 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="rename">
|
<input type="hidden" name="action" value="rename">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Renombrar Imagen</h5>
|
<h5 class="modal-title"><?= t('Renombrar Imagen') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="hidden" name="oldname" id="renameOldname">
|
<input type="hidden" name="oldname" id="renameOldname">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nuevo nombre</label>
|
<label class="form-label"><?= t('Nuevo nombre') ?></label>
|
||||||
<input type="text" name="newname" class="form-control" required>
|
<input type="text" name="newname" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Renombrar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Renombrar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,10 +143,14 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const i18nGallery = {
|
||||||
|
urlCopied: '<?= t('URL copiada al portapapeles') ?>'
|
||||||
|
};
|
||||||
|
|
||||||
function copyUrl(filename) {
|
function copyUrl(filename) {
|
||||||
const url = window.location.origin + '/galeria/' + filename;
|
const url = window.location.origin + '/galeria/' + filename;
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
alert('URL copiada al portapapeles');
|
alert(i18nGallery.urlCopied);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifica si un texto contiene contenido real (no solo emojis, espacios, etc)
|
* Verifica si un texto contiene contenido real (no solo emojis, URLs de GIFs, espacios, etc)
|
||||||
* Los emojis se preservan en el texto, solo verificamos que hay más que eso
|
* Los emojis se preservan en el texto, solo verificamos que hay más que eso
|
||||||
*/
|
*/
|
||||||
function hasRealContent(string $text): bool
|
function hasRealContent(string $text): bool
|
||||||
@@ -10,23 +10,34 @@ function hasRealContent(string $text): bool
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hacer una copia para procesarla sin alterar el original
|
|
||||||
$clean = trim($text);
|
$clean = trim($text);
|
||||||
|
|
||||||
if (empty($clean)) {
|
if (empty($clean)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remover emojis y caracteres especiales, preservar solo letras, números y puntuación básica
|
// Remover URLs de GIFs y medias (tenor, giphy, media.tenor, cdn.discordapp, etc)
|
||||||
// Esta expresión regular mantiene letras, números y puntuación, elimina emojis
|
$clean = preg_replace('/https?:\/\/(www\.)?(tenor\.com|giphy\.com|media\.tenor\.com|cdn\.discordapp\.com|media\.discord|gifcdn|gfycat\.com|reddit\.com\/r\/[^\/]+\/comments\/)[^\s]*/i', '', $clean);
|
||||||
// Emojis Unicode: rangos múltiples de caracteres
|
|
||||||
$clean = preg_replace('/[\x{1F300}-\x{1F9FF}]/u', '', $clean); // Emojis de rango alto (0x1F300-0x1F9FF)
|
// Remover URLs de Discord gifts
|
||||||
$clean = preg_replace('/[\x{2600}-\x{26FF}]/u', '', $clean); // Símbolos de ajedrez, dados, etc
|
$clean = preg_replace('/https?:\/\/(www\.)?(discord\.gift|discord\.com\/gifts)[^\s]*/i', '', $clean);
|
||||||
$clean = preg_replace('/[\x{2B50}]/u', '', $clean); // Estrella
|
|
||||||
$clean = preg_replace('/[\x{00A0}\s]+/u', '', $clean); // Espacios en blanco (incluyendo no-breaking space)
|
// Remover otros URLs comunes (http://..., https://...)
|
||||||
$clean = preg_replace('/[\p{P}]/u', '', $clean); // Puntuación Unicode (incluyendo 👍)
|
$clean = preg_replace('/https?:\/\/[^\s]+/i', '', $clean);
|
||||||
|
|
||||||
|
$clean = trim($clean);
|
||||||
|
|
||||||
|
if (empty($clean)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remover emojis y caracteres especiales
|
||||||
|
$clean = preg_replace('/[\x{1F300}-\x{1F9FF}]/u', '', $clean);
|
||||||
|
$clean = preg_replace('/[\x{2600}-\x{26FF}]/u', '', $clean);
|
||||||
|
$clean = preg_replace('/[\x{2B50}]/u', '', $clean);
|
||||||
|
$clean = preg_replace('/[\x{00A0}\s]+/u', '', $clean);
|
||||||
|
$clean = preg_replace('/[\p{P}]/u', '', $clean);
|
||||||
|
|
||||||
// Si después de remover emojis y espacios no queda nada, es solo emojis
|
|
||||||
return !empty(trim($clean));
|
return !empty(trim($clean));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
134
includes/i18n.php
Normal file
134
includes/i18n.php
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/env_loader.php';
|
||||||
|
require_once __DIR__ . '/db.php';
|
||||||
|
require_once __DIR__ . '/../src/Translate.php';
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLanguage(): string
|
||||||
|
{
|
||||||
|
if (isset($_SESSION['user_language'])) {
|
||||||
|
return $_SESSION['user_language'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_COOKIE['user_language'])) {
|
||||||
|
$_SESSION['user_language'] = $_COOKIE['user_language'];
|
||||||
|
return $_COOKIE['user_language'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLang = 'es';
|
||||||
|
$_SESSION['user_language'] = $defaultLang;
|
||||||
|
return $defaultLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrentLanguage(string $langCode): void
|
||||||
|
{
|
||||||
|
$_SESSION['user_language'] = $langCode;
|
||||||
|
setcookie('user_language', $langCode, [
|
||||||
|
'expires' => time() + (365 * 24 * 60 * 60),
|
||||||
|
'path' => '/',
|
||||||
|
'secure' => true,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveLanguages(): array
|
||||||
|
{
|
||||||
|
static $languages = null;
|
||||||
|
|
||||||
|
if ($languages !== null) {
|
||||||
|
return $languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
$stmt = $pdo->query("SELECT * FROM supported_languages WHERE is_active = 1 ORDER BY language_name");
|
||||||
|
$languages = $stmt->fetchAll();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$languages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBaseLanguage(): string
|
||||||
|
{
|
||||||
|
return 'es';
|
||||||
|
}
|
||||||
|
|
||||||
|
function t(string $text, ?string $targetLang = null): string
|
||||||
|
{
|
||||||
|
if (empty(trim($text))) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetLang = $targetLang ?? getCurrentLanguage();
|
||||||
|
$baseLang = getBaseLanguage();
|
||||||
|
|
||||||
|
if ($targetLang === $baseLang) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static $translator = null;
|
||||||
|
static $cache = [];
|
||||||
|
|
||||||
|
$cacheKey = md5($text . $targetLang);
|
||||||
|
|
||||||
|
if (isset($cache[$cacheKey])) {
|
||||||
|
return $cache[$cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translator === null) {
|
||||||
|
$translator = new \src\Translate();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$translated = $translator->translate($text, $baseLang, $targetLang);
|
||||||
|
|
||||||
|
if ($translated !== null) {
|
||||||
|
$cache[$cacheKey] = $translated;
|
||||||
|
return $translated;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Translation error for '$text': " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ta(array $texts): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($texts as $key => $text) {
|
||||||
|
$result[$key] = t($text);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLanguageChange(): void
|
||||||
|
{
|
||||||
|
if (isset($_GET['lang'])) {
|
||||||
|
$lang = $_GET['lang'];
|
||||||
|
$activeLanguages = getActiveLanguages();
|
||||||
|
$validLangs = array_column($activeLanguages, 'language_code');
|
||||||
|
|
||||||
|
if (in_array($lang, $validLangs) || $lang === 'es') {
|
||||||
|
setCurrentLanguage($lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirectUrl = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
|
$queryParams = $_GET;
|
||||||
|
unset($queryParams['lang']);
|
||||||
|
|
||||||
|
if (!empty($queryParams)) {
|
||||||
|
$redirectUrl .= '?' . http_build_query($queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ' . $redirectUrl);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
index.php
78
index.php
@@ -14,21 +14,23 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-house-door"></i> Dashboard</h2>
|
<h2><i class="bi bi-crosshair"></i> <?= t('Dashboard') ?></h2>
|
||||||
<span class="text-muted">Bienvenido, <?= htmlspecialchars(getCurrentUsername()) ?></span>
|
<span class="text-muted" style="font-family: 'Share Tech Mono', monospace; font-size: 0.8rem;">
|
||||||
|
// <?= t('BIENVENIDO') ?>, <?= strtoupper(htmlspecialchars(getCurrentUsername())) ?>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="bg-primary bg-opacity-10 p-3 rounded me-3">
|
<div class="stat-card-icon me-3" style="border-color: var(--accent-orange); color: var(--accent-orange);">
|
||||||
<i class="bi bi-clock text-primary fs-4"></i>
|
<i class="bi bi-stopwatch fs-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-0"><?= $pendingCount ?></h3>
|
<div style="font-family: 'Share Tech Mono', monospace; font-size: 2rem; color: var(--accent-orange);"><?= $pendingCount ?></div>
|
||||||
<small class="text-muted">Mensajes Pendientes</small>
|
<small class="text-muted text-uppercase" style="letter-spacing: 1px; font-size: 0.7rem;"><?= t('Pendientes') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,15 +38,15 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="bg-success bg-opacity-10 p-3 rounded me-3">
|
<div class="stat-card-icon me-3" style="border-color: var(--hud-cyan); color: var(--hud-cyan);">
|
||||||
<i class="bi bi-check-circle text-success fs-4"></i>
|
<i class="bi bi-check2-all fs-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-0"><?= $sentToday ?></h3>
|
<div style="font-family: 'Share Tech Mono', monospace; font-size: 2rem; color: var(--hud-cyan);"><?= $sentToday ?></div>
|
||||||
<small class="text-muted">Enviados Hoy</small>
|
<small class="text-muted text-uppercase" style="letter-spacing: 1px; font-size: 0.7rem;"><?= t('Enviados Hoy') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,17 +54,17 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="bg-discord bg-opacity-10 p-3 rounded me-3" style="background-color: rgba(88,101,242,0.1)">
|
<div class="stat-card-icon me-3" style="border-color: var(--discord-color); color: var(--discord-color);">
|
||||||
<i class="bi bi-discord fs-4" style="color: #5865F2"></i>
|
<i class="bi bi-discord fs-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-0">
|
<div style="font-family: 'Share Tech Mono', monospace; font-size: 2rem; color: var(--discord-color);">
|
||||||
<?= count(array_filter($scheduledMessages, fn($m) => $m['platform'] === 'discord')) ?>
|
<?= count(array_filter($scheduledMessages, fn($m) => $m['platform'] === 'discord')) ?>
|
||||||
</h3>
|
</div>
|
||||||
<small class="text-muted">Discord</small>
|
<small class="text-muted text-uppercase" style="letter-spacing: 1px; font-size: 0.7rem;"><?= t('Discord') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,17 +72,17 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="bg-info bg-opacity-10 p-3 rounded me-3">
|
<div class="stat-card-icon me-3" style="border-color: var(--telegram-color); color: var(--telegram-color);">
|
||||||
<i class="bi bi-telegram text-info fs-4"></i>
|
<i class="bi bi-telegram fs-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-0">
|
<div style="font-family: 'Share Tech Mono', monospace; font-size: 2rem; color: var(--telegram-color);">
|
||||||
<?= count(array_filter($scheduledMessages, fn($m) => $m['platform'] === 'telegram')) ?>
|
<?= count(array_filter($scheduledMessages, fn($m) => $m['platform'] === 'telegram')) ?>
|
||||||
</h3>
|
</div>
|
||||||
<small class="text-muted">Telegram</small>
|
<small class="text-muted text-uppercase" style="letter-spacing: 1px; font-size: 0.7rem;"><?= t('Telegram') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,21 +93,21 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-clock"></i> Próximos Mensajes</h5>
|
<h5 class="mb-0"><i class="bi bi-list-check"></i> <?= t('Próximos Mensajes') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if (empty($scheduledMessages)): ?>
|
<?php if (empty($scheduledMessages)): ?>
|
||||||
<p class="text-muted text-center py-4">No hay mensajes programados</p>
|
<p class="text-muted text-center py-4">// <?= t('NO HAY MENSAJES PROGRAMADOS') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Destinatario</th>
|
<th><?= t('Destinatario') ?></th>
|
||||||
<th>Plataforma</th>
|
<th><?= t('Plataforma') ?></th>
|
||||||
<th>Fecha</th>
|
<th><?= t('Fecha') ?></th>
|
||||||
<th>Estado</th>
|
<th><?= t('Estado') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -119,7 +121,7 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
<i class="bi bi-telegram platform-telegram"></i> Telegram
|
<i class="bi bi-telegram platform-telegram"></i> Telegram
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td><?= date('d/m/Y H:i', strtotime($msg['send_time'])) ?></td>
|
<td style="font-family: 'Share Tech Mono', monospace;"><?= date('d/m/Y H:i', strtotime($msg['send_time'])) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-<?= match($msg['status']) {
|
<span class="badge bg-<?= match($msg['status']) {
|
||||||
'pending' => 'warning',
|
'pending' => 'warning',
|
||||||
@@ -129,7 +131,7 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
'disabled' => 'secondary',
|
'disabled' => 'secondary',
|
||||||
default => 'light'
|
default => 'light'
|
||||||
} ?>">
|
} ?>">
|
||||||
<?= $msg['status'] ?>
|
<?= t($msg['status']) ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -144,19 +146,19 @@ $sentToday = count(array_filter($sentMessages, fn($m) => date('Y-m-d', strtotime
|
|||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-lightning"></i> Acciones Rápidas</h5>
|
<h5 class="mb-0"><i class="bi bi-lightning"></i> <?= t('Acciones Rápidas') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<a href="create_message.php" class="btn btn-primary">
|
<a href="create_message.php" class="btn btn-primary">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Mensaje
|
<i class="bi bi-pencil-square"></i> <?= t('Nuevo Mensaje') ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="recurrentes.php" class="btn btn-outline-secondary">
|
<a href="recurrentes.php" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-repeat"></i> Ver Plantillas
|
<i class="bi bi-collection"></i> <?= t('Ver Plantillas') ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="scheduled_messages.php" class="btn btn-outline-secondary">
|
<a href="scheduled_messages.php" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-list"></i> Ver Todos los Programados
|
<i class="bi bi-list"></i> <?= t('Ver Todos los Programados') ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
315
login.php
Normal file → Executable file
315
login.php
Normal file → Executable file
@@ -4,7 +4,7 @@ require_once __DIR__ . '/includes/env_loader.php';
|
|||||||
require_once __DIR__ . '/includes/auth.php';
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
$domain = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
$domain = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
if ($domain) {
|
if ($domain && session_status() === PHP_SESSION_NONE) {
|
||||||
$parsed = parse_url($domain);
|
$parsed = parse_url($domain);
|
||||||
$host = $parsed['host'] ?? '';
|
$host = $parsed['host'] ?? '';
|
||||||
if ($host) {
|
if ($host) {
|
||||||
@@ -19,7 +19,9 @@ if ($domain) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session_start();
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
|
|
||||||
|
handleLanguageChange();
|
||||||
|
|
||||||
if (isset($_SESSION['user_id'])) {
|
if (isset($_SESSION['user_id'])) {
|
||||||
header('Location: index.php');
|
header('Location: index.php');
|
||||||
@@ -28,6 +30,8 @@ if (isset($_SESSION['user_id'])) {
|
|||||||
|
|
||||||
$error = '';
|
$error = '';
|
||||||
$theme = $_COOKIE['theme'] ?? 'light';
|
$theme = $_COOKIE['theme'] ?? 'light';
|
||||||
|
$currentLang = getCurrentLanguage();
|
||||||
|
$activeLanguages = getActiveLanguages();
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$username = $_POST['username'] ?? '';
|
$username = $_POST['username'] ?? '';
|
||||||
@@ -39,7 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
header('Location: index.php');
|
header('Location: index.php');
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
$error = 'Usuario o contraseña incorrectos';
|
$error = t('Usuario o contraseña incorrectos');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -51,31 +55,263 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<title>Login - Sistema de Mensajería</title>
|
<title>Login - Sistema de Mensajería</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--military-dark: #1a1f16;
|
||||||
|
--military-olive: #3d4a32;
|
||||||
|
--military-green: #5a6b4a;
|
||||||
|
--military-tan: #8b7355;
|
||||||
|
--military-sand: #c4b998;
|
||||||
|
--accent-orange: #d4652f;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
background: var(--military-dark);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
position: relative;
|
||||||
[data-bs-theme="dark"] body {
|
|
||||||
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
|
|
||||||
}
|
|
||||||
.login-card {
|
|
||||||
background: var(--bs-body-bg);
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 50px,
|
||||||
|
rgba(61, 74, 50, 0.1) 50px,
|
||||||
|
rgba(61, 74, 50, 0.1) 51px
|
||||||
|
),
|
||||||
|
repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
transparent 50px,
|
||||||
|
rgba(61, 74, 50, 0.1) 50px,
|
||||||
|
rgba(61, 74, 50, 0.1) 51px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] body {
|
||||||
|
background: linear-gradient(135deg, #3d4a32 0%, #2a3024 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .login-card {
|
||||||
|
background: rgba(245, 243, 237, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .login-body {
|
||||||
|
background: rgba(245, 243, 237, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .form-label {
|
||||||
|
color: #1a1f16;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .form-control {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #1a1f16;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .form-control:focus {
|
||||||
|
background: #fff;
|
||||||
|
color: #1a1f16;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .form-control::placeholder {
|
||||||
|
color: rgba(26, 31, 22, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .login-subtitle {
|
||||||
|
color: #1a1f16;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .alert-danger {
|
||||||
|
background: rgba(139, 58, 58, 0.15);
|
||||||
|
color: #8b3a3a;
|
||||||
|
border-color: #8b3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: rgba(26, 31, 22, 0.95);
|
||||||
|
border: 2px solid var(--military-green);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
var(--military-green) 0%,
|
||||||
|
var(--accent-orange) 50%,
|
||||||
|
var(--military-green) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
var(--military-green) 0%,
|
||||||
|
var(--accent-orange) 50%,
|
||||||
|
var(--military-green) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-header {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: transparent;
|
||||||
color: white;
|
color: var(--military-sand);
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
border-bottom: 1px solid var(--military-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--accent-orange);
|
||||||
|
letter-spacing: 4px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subtitle {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--military-sand);
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.login-body {
|
.login-body {
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
background: rgba(26, 31, 22, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
color: var(--military-sand);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
background: rgba(26, 31, 22, 0.8);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
color: var(--military-sand);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background: rgba(26, 31, 22, 0.9);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 101, 47, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: rgba(196, 185, 152, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
background: linear-gradient(180deg, var(--military-olive) 0%, var(--military-dark) 100%);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
color: var(--military-sand);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login:hover {
|
||||||
|
background: linear-gradient(180deg, var(--military-green) 0%, var(--military-olive) 100%);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login i {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background: rgba(139, 58, 58, 0.3);
|
||||||
|
border: 1px solid var(--accent-red, #8b3a3a);
|
||||||
|
border-radius: 0;
|
||||||
|
color: #ff8a8a;
|
||||||
|
border-left: 3px solid #8b3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-decoration {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-tl { top: -2px; left: -2px; border-right: none; border-bottom: none; }
|
||||||
|
.corner-tr { top: -2px; right: -2px; border-left: none; border-bottom: none; }
|
||||||
|
.corner-bl { bottom: -2px; left: -2px; border-right: none; border-top: none; }
|
||||||
|
.corner-br { bottom: -2px; right: -2px; border-left: none; border-top: none; }
|
||||||
|
|
||||||
|
.lang-selector-login {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-selector-login .btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
color: var(--military-sand);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-selector-login .btn:hover {
|
||||||
|
background: rgba(61, 74, 50, 0.5);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-selector-login .dropdown-menu {
|
||||||
|
background: var(--military-dark);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-selector-login .dropdown-item {
|
||||||
|
color: var(--military-sand);
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-selector-login .dropdown-item:hover,
|
||||||
|
.lang-selector-login .dropdown-item.active {
|
||||||
|
background: rgba(90, 107, 74, 0.4);
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -83,27 +319,54 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="login-card">
|
<div class="login-card position-relative">
|
||||||
|
<div class="corner-decoration corner-tl"></div>
|
||||||
|
<div class="corner-decoration corner-tr"></div>
|
||||||
|
<div class="corner-decoration corner-bl"></div>
|
||||||
|
<div class="corner-decoration corner-br"></div>
|
||||||
|
|
||||||
|
<div class="lang-selector-login dropdown">
|
||||||
|
<button class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
<?php
|
||||||
|
$currentLangData = array_filter($activeLanguages, fn($l) => $l['language_code'] === $currentLang);
|
||||||
|
$currentLangData = reset($currentLangData);
|
||||||
|
if ($currentLang === 'es' || !$currentLangData) {
|
||||||
|
echo '🇲🇽';
|
||||||
|
} else {
|
||||||
|
echo htmlspecialchars($currentLangData['flag_emoji']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a class="dropdown-item <?= $currentLang === 'es' ? 'active' : '' ?>" href="?lang=es">🇲🇽 <?= t('Español') ?></a></li>
|
||||||
|
<?php foreach ($activeLanguages as $lang): ?>
|
||||||
|
<?php if ($lang['language_code'] !== 'es'): ?>
|
||||||
|
<li><a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>"><?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?></a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="login-header">
|
<div class="login-header">
|
||||||
<h3><i class="bi bi-messenger"></i> Sistema de Mensajería</h3>
|
<div class="login-title">◈ LASTWAR</div>
|
||||||
<p class="mb-0">Discord & Telegram</p>
|
<div class="login-subtitle"><?= t('Centro de Comunicaciones') ?></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-body">
|
<div class="login-body">
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
<div class="alert alert-danger mb-3"><?= htmlspecialchars($error) ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label">Usuario</label>
|
<label class="form-label"><?= t('Usuario') ?></label>
|
||||||
<input type="text" name="username" class="form-control" required autofocus>
|
<input type="text" name="username" class="form-control" placeholder="<?= t('Ingrese su usuario') ?>" required autofocus>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label">Contraseña</label>
|
<label class="form-label"><?= t('Contraseña') ?></label>
|
||||||
<input type="password" name="password" class="form-control" required>
|
<input type="password" name="password" class="form-control" placeholder="••••••••" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
<button type="submit" class="btn btn-login">
|
||||||
<i class="bi bi-box-arrow-in-right"></i> Iniciar Sesión
|
<i class="bi bi-shield-lock"></i> <?= t('Acceder') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
0
logs/.gitkeep
Normal file
0
logs/.gitkeep
Normal file
@@ -13,16 +13,19 @@ define('SLEEP_INTERVAL', 5);
|
|||||||
|
|
||||||
function getTranslationButtons(PDO $pdo, string $text): array
|
function getTranslationButtons(PDO $pdo, string $text): array
|
||||||
{
|
{
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||||
$languages = $stmt->fetchAll();
|
$telegramLanguages = $stmtTelegram->fetchAll();
|
||||||
|
|
||||||
if (count($languages) <= 1) {
|
$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 [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
|
||||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,15 +107,17 @@ function processScheduledMessages(): array
|
|||||||
|
|
||||||
// Obtener botones de traducción (convertir HTML a texto plano)
|
// Obtener botones de traducción (convertir HTML a texto plano)
|
||||||
$plainText = $schedule['content'];
|
$plainText = $schedule['content'];
|
||||||
// Convertir saltos de párrafo a saltos de línea
|
// Marcar donde hay imágenes
|
||||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
$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('/<p[^>]*>/i', '', $plainText);
|
||||||
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
||||||
// Eliminar HTML
|
// Eliminar HTML restante
|
||||||
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
// Limpiar espacios múltiples pero preservar saltos de línea
|
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
|
||||||
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
||||||
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
|
||||||
$plainText = trim($plainText);
|
$plainText = trim($plainText);
|
||||||
$translationButtons = getTranslationButtons($pdo, $plainText);
|
$translationButtons = getTranslationButtons($pdo, $plainText);
|
||||||
|
|
||||||
@@ -120,24 +125,26 @@ function processScheduledMessages(): array
|
|||||||
$segments = $sender->parseContent($schedule['content']);
|
$segments = $sender->parseContent($schedule['content']);
|
||||||
|
|
||||||
$messageCount = 0;
|
$messageCount = 0;
|
||||||
|
$totalSegments = count($segments);
|
||||||
|
$currentSegment = 0;
|
||||||
|
|
||||||
// Enviar cada segmento en el orden correcto
|
// Enviar cada segmento en el orden correcto
|
||||||
foreach ($segments as $segment) {
|
foreach ($segments as $segment) {
|
||||||
|
$currentSegment++;
|
||||||
|
$isLastSegment = ($currentSegment === $totalSegments);
|
||||||
|
|
||||||
if ($segment['type'] === 'text') {
|
if ($segment['type'] === 'text') {
|
||||||
// Convertir el texto al formato de la plataforma
|
// Convertir el texto al formato de la plataforma
|
||||||
$textContent = ConverterFactory::convert($schedule['platform'], $segment['content']);
|
$textContent = ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||||
|
|
||||||
if (!empty(trim($textContent))) {
|
if (!empty(trim($textContent))) {
|
||||||
// Agregar botones de traducción al último segmento de texto
|
|
||||||
$buttons = null;
|
$buttons = null;
|
||||||
if ($segment === end($segments)) {
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$buttons = $schedule['platform'] === 'telegram'
|
$buttons = $translationButtons['discord'];
|
||||||
? $translationButtons['telegram']
|
|
||||||
: $translationButtons['discord'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($schedule['platform'] === 'telegram') {
|
if ($schedule['platform'] === 'telegram') {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||||
} else {
|
} else {
|
||||||
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
||||||
}
|
}
|
||||||
@@ -146,22 +153,43 @@ function processScheduledMessages(): array
|
|||||||
} elseif ($segment['type'] === 'image') {
|
} elseif ($segment['type'] === 'image') {
|
||||||
$imagePath = $segment['src'];
|
$imagePath = $segment['src'];
|
||||||
|
|
||||||
// Quitar parámetros de URL si los hay
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
|
|
||||||
if (file_exists($imgPath)) {
|
$buttons = null;
|
||||||
// Es un archivo local
|
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
$buttons = $translationButtons['discord'];
|
||||||
$messageCount++;
|
}
|
||||||
} elseif (strpos($imagePath, 'http') === 0) {
|
|
||||||
// Es una URL remota
|
if ($schedule['platform'] === 'telegram') {
|
||||||
$embed = ['image' => ['url' => $imagePath]];
|
if (strpos($imagePath, 'http') !== 0) {
|
||||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
$imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
|
||||||
|
} else {
|
||||||
|
$imageUrl = $imagePath;
|
||||||
|
}
|
||||||
|
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
|
||||||
$messageCount++;
|
$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("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||||
VALUES (?, ?, ?, ?, NOW())
|
VALUES (?, ?, ?, ?, NOW())
|
||||||
|
|||||||
21
profile.php
21
profile.php
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
|
||||||
checkSession();
|
checkSession();
|
||||||
|
|
||||||
$pageTitle = 'Mi Perfil';
|
$pageTitle = t('Mi Perfil');
|
||||||
|
|
||||||
$currentUserId = getCurrentUserId();
|
$currentUserId = getCurrentUserId();
|
||||||
if ($currentUserId === 0) {
|
if ($currentUserId === 0) {
|
||||||
@@ -56,7 +57,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-person"></i> Mi Perfil</h2>
|
<h2><i class="bi bi-person"></i> <?= t('Mi Perfil') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -68,11 +69,11 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<h4><?= htmlspecialchars($user['username']) ?></h4>
|
<h4><?= htmlspecialchars($user['username']) ?></h4>
|
||||||
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
|
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
|
||||||
<?= strtoupper($user['role']) ?>
|
<?= strtoupper($user['role'] === 'admin' ? t('Administrador') : t('Usuario')) ?>
|
||||||
</span>
|
</span>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-muted mb-1">ID: <?= $user['id'] ?></p>
|
<p class="text-muted mb-1">ID: <?= $user['id'] ?></p>
|
||||||
<p class="text-muted mb-0">Miembro desde: <?= date('d/m/Y', strtotime($user['created_at'])) ?></p>
|
<p class="text-muted mb-0"><?= t('Miembro desde') ?>: <?= date('d/m/Y', strtotime($user['created_at'])) ?></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,30 +88,30 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0"><i class="bi bi-key"></i> Cambiar Contraseña</h5>
|
<h5 class="mb-0"><i class="bi bi-key"></i> <?= t('Cambiar Contraseña') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="change_password">
|
<input type="hidden" name="action" value="change_password">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Contraseña Actual</label>
|
<label class="form-label"><?= t('Contraseña Actual') ?></label>
|
||||||
<input type="password" name="current_password" class="form-control" required>
|
<input type="password" name="current_password" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nueva Contraseña</label>
|
<label class="form-label"><?= t('Nueva Contraseña') ?></label>
|
||||||
<input type="password" name="new_password" class="form-control" required minlength="6">
|
<input type="password" name="new_password" class="form-control" required minlength="6">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Confirmar Nueva Contraseña</label>
|
<label class="form-label"><?= t('Confirmar Nueva Contraseña') ?></label>
|
||||||
<input type="password" name="confirm_password" class="form-control" required minlength="6">
|
<input type="password" name="confirm_password" class="form-control" required minlength="6">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-check-circle"></i> Actualizar Contraseña
|
<i class="bi bi-check-circle"></i> <?= t('Actualizar Contraseña') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
checkSession();
|
checkSession();
|
||||||
require_once __DIR__ . '/includes/activity_logger.php';
|
require_once __DIR__ . '/includes/activity_logger.php';
|
||||||
|
|
||||||
$pageTitle = 'Plantillas de Mensajes';
|
$pageTitle = t('Plantillas de Mensajes');
|
||||||
|
|
||||||
$templates = [];
|
$templates = [];
|
||||||
$galleryImages = [];
|
$galleryImages = [];
|
||||||
@@ -70,9 +71,9 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-repeat"></i> Plantillas de Mensajes</h2>
|
<h2><i class="bi bi-collection"></i> <?= t('Plantillas de Mensajes') ?></h2>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#templateModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#templateModal">
|
||||||
<i class="bi bi-plus-circle"></i> Nueva Plantilla
|
<i class="bi bi-plus-circle"></i> <?= t('Nueva Plantilla') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,19 +84,19 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<?php if (empty($templates)): ?>
|
<?php if (empty($templates)): ?>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p class="text-muted text-center py-4">No hay plantillas creadas</p>
|
<p class="text-muted text-center py-4"><?= t('No hay plantillas creadas') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($templates as $template): ?>
|
<?php foreach ($templates as $template): ?>
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-4">
|
||||||
<div class="card border-0 shadow-sm h-100">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center">
|
<div class="card-header border-0 d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0"><?= htmlspecialchars($template['name']) ?></h5>
|
<h5 class="mb-0"><?= htmlspecialchars($template['name']) ?></h5>
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
<button class="btn btn-outline-primary" onclick="editTemplate(<?= htmlspecialchars(json_encode($template)) ?>)">
|
<button class="btn btn-outline-primary" onclick="editTemplate(<?= htmlspecialchars(json_encode($template)) ?>)">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar esta plantilla?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar esta plantilla?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="id" value="<?= $template['id'] ?>">
|
<input type="hidden" name="id" value="<?= $template['id'] ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger">
|
<button type="submit" class="btn btn-outline-danger">
|
||||||
@@ -106,13 +107,13 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-muted small mb-2">
|
<p class="text-muted small mb-2">
|
||||||
<i class="bi bi-terminal"></i> Comando: <code>#<?= htmlspecialchars($template['telegram_command'] ?? 'sin comando') ?></code>
|
<i class="bi bi-terminal"></i> <?= t('Comando') ?>: <code>#<?= htmlspecialchars($template['telegram_command'] ?? t('sin comando')) ?></code>
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-light p-2 rounded mb-2" style="max-height: 100px; overflow-y: auto; font-size: 0.85rem;">
|
<div class="bg-light p-2 rounded mb-2" style="max-height: 100px; overflow-y: auto; font-size: 0.85rem;">
|
||||||
<?= strip_tags(substr($template['message_content'], 0, 200)) ?><?= strlen($template['message_content']) > 200 ? '...' : '' ?>
|
<?= strip_tags(substr($template['message_content'], 0, 200)) ?><?= strlen($template['message_content']) > 200 ? '...' : '' ?>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#previewModal<?= $template['id'] ?>">
|
<button class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#previewModal<?= $template['id'] ?>">
|
||||||
<i class="bi bi-eye"></i> Ver preview
|
<i class="bi bi-eye"></i> <?= t('Ver preview') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,30 +131,30 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<input type="hidden" name="id" value="" id="templateId">
|
<input type="hidden" name="id" value="" id="templateId">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="modalTitle">Nueva Plantilla</h5>
|
<h5 class="modal-title" id="modalTitle"><?= t('Nueva Plantilla') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nombre</label>
|
<label class="form-label"><?= t('Nombre') ?></label>
|
||||||
<input type="text" name="name" class="form-control" required id="templateName">
|
<input type="text" name="name" class="form-control" required id="templateName">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Comando de Telegram (sin #)</label>
|
<label class="form-label"><?= t('Comando de Telegram (sin #)') ?></label>
|
||||||
<input type="text" name="telegram_command" class="form-control" placeholder="ejemplo" id="templateCommand">
|
<input type="text" name="telegram_command" class="form-control" placeholder="<?= t('ejemplo') ?>" id="templateCommand">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Contenido</label>
|
<label class="form-label"><?= t('Contenido') ?></label>
|
||||||
<textarea name="message_content" class="form-control" rows="10" required id="templateContent"></textarea>
|
<textarea name="message_content" class="form-control" rows="10" required id="templateContent"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
|
||||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +166,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><i class="bi bi-images"></i> Galería de Imágenes</h5>
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -173,7 +174,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<?php if (empty($galleryImages)): ?>
|
<?php if (empty($galleryImages)): ?>
|
||||||
<div class="col-12 text-center text-muted py-5">
|
<div class="col-12 text-center text-muted py-5">
|
||||||
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
<i class="bi bi-images" style="font-size: 3rem;"></i>
|
||||||
<p class="mt-3">No hay imágenes en la galería</p>
|
<p class="mt-3"><?= t('No hay imágenes en la galería') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($galleryImages as $image): ?>
|
<?php foreach ($galleryImages as $image): ?>
|
||||||
@@ -200,7 +201,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
<i class="bi bi-eye"></i> Preview: <?= htmlspecialchars($template['name']) ?>
|
<i class="bi bi-eye"></i> <?= t('Preview') ?>: <?= htmlspecialchars($template['name']) ?>
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,18 +269,18 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="tab-pane fade" id="previewHtml<?= $template['id'] ?>">
|
<div class="tab-pane fade" id="previewHtml<?= $template['id'] ?>">
|
||||||
<div class="border rounded p-3 bg-white">
|
<div class="border rounded p-3 bg-white">
|
||||||
<h6>Renderizado:</h6>
|
<h6><?= t('Renderizado') ?>:</h6>
|
||||||
<div class="border rounded p-3 mb-3 bg-light">
|
<div class="border rounded p-3 mb-3 bg-light">
|
||||||
<?= $template['message_content'] ?>
|
<?= $template['message_content'] ?>
|
||||||
</div>
|
</div>
|
||||||
<h6>Código HTML:</h6>
|
<h6><?= t('Código HTML') ?>:</h6>
|
||||||
<pre class="bg-dark text-light p-3 rounded" style="max-height: 200px; overflow-y: auto;"><code><?= htmlspecialchars($template['message_content']) ?></code></pre>
|
<pre class="bg-dark text-light p-3 rounded" style="max-height: 200px; overflow-y: auto;"><code><?= htmlspecialchars($template['message_content']) ?></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cerrar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,10 +292,16 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const galleryImages = <?= json_encode($galleryImages) ?>;
|
const galleryImages = <?= json_encode($galleryImages) ?>;
|
||||||
|
const i18n = {
|
||||||
|
editTemplate: '<?= t('Editar Plantilla') ?>',
|
||||||
|
gallery: '<?= t('Galería') ?>',
|
||||||
|
insertImage: '<?= t('Insertar imagen desde galería') ?>',
|
||||||
|
newTemplate: '<?= t('Nueva Plantilla') ?>'
|
||||||
|
};
|
||||||
|
|
||||||
function editTemplate(template) {
|
function editTemplate(template) {
|
||||||
document.getElementById('modalAction').value = 'update';
|
document.getElementById('modalAction').value = 'update';
|
||||||
document.getElementById('modalTitle').textContent = 'Editar Plantilla';
|
document.getElementById('modalTitle').textContent = i18n.editTemplate;
|
||||||
document.getElementById('templateId').value = template.id;
|
document.getElementById('templateId').value = template.id;
|
||||||
document.getElementById('templateName').value = template.name;
|
document.getElementById('templateName').value = template.name;
|
||||||
document.getElementById('templateCommand').value = template.telegram_command || '';
|
document.getElementById('templateCommand').value = template.telegram_command || '';
|
||||||
@@ -315,8 +322,8 @@ function editTemplate(template) {
|
|||||||
buttons: {
|
buttons: {
|
||||||
gallery: function() {
|
gallery: function() {
|
||||||
return $.summernote.ui.button({
|
return $.summernote.ui.button({
|
||||||
contents: '<i class="bi bi-images"></i> Galería',
|
contents: '<i class="bi bi-images"></i> ' + i18n.gallery,
|
||||||
tooltip: 'Insertar imagen desde galería',
|
tooltip: i18n.insertImage,
|
||||||
click: function() {
|
click: function() {
|
||||||
$('#galleryModal').modal('show');
|
$('#galleryModal').modal('show');
|
||||||
}
|
}
|
||||||
@@ -356,8 +363,8 @@ $(document).ready(function() {
|
|||||||
buttons: {
|
buttons: {
|
||||||
gallery: function() {
|
gallery: function() {
|
||||||
return $.summernote.ui.button({
|
return $.summernote.ui.button({
|
||||||
contents: '<i class="bi bi-images"></i> Galería',
|
contents: '<i class="bi bi-images"></i> ' + i18n.gallery,
|
||||||
tooltip: 'Insertar imagen desde galería',
|
tooltip: i18n.insertImage,
|
||||||
click: function() {
|
click: function() {
|
||||||
$('#galleryModal').modal('show');
|
$('#galleryModal').modal('show');
|
||||||
}
|
}
|
||||||
@@ -370,7 +377,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
$('#templateModal').on('hidden.bs.modal', function() {
|
$('#templateModal').on('hidden.bs.modal', function() {
|
||||||
document.getElementById('modalAction').value = 'create';
|
document.getElementById('modalAction').value = 'create';
|
||||||
document.getElementById('modalTitle').textContent = 'Nueva Plantilla';
|
document.getElementById('modalTitle').textContent = i18n.newTemplate;
|
||||||
document.getElementById('templateId').value = '';
|
document.getElementById('templateId').value = '';
|
||||||
document.getElementById('templateName').value = '';
|
document.getElementById('templateName').value = '';
|
||||||
document.getElementById('templateCommand').value = '';
|
document.getElementById('templateCommand').value = '';
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
checkSession();
|
checkSession();
|
||||||
require_once __DIR__ . '/includes/message_handler.php';
|
require_once __DIR__ . '/includes/message_handler.php';
|
||||||
require_once __DIR__ . '/includes/schedule_actions.php';
|
require_once __DIR__ . '/includes/schedule_actions.php';
|
||||||
|
|
||||||
$pageTitle = 'Mensajes Programados';
|
$pageTitle = t('Mensajes Programados');
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
$scheduleId = (int) $_POST['schedule_id'];
|
$scheduleId = (int) $_POST['schedule_id'];
|
||||||
@@ -24,32 +25,32 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-clock"></i> Mensajes Programados</h2>
|
<h2><i class="bi bi-stopwatch"></i> <?= t('Mensajes Programados') ?></h2>
|
||||||
<a href="create_message.php" class="btn btn-primary">
|
<a href="create_message.php" class="btn btn-primary">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Mensaje
|
<i class="bi bi-plus-circle"></i> <?= t('Nuevo Mensaje') ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($_GET['success'])): ?>
|
<?php if (isset($_GET['success'])): ?>
|
||||||
<div class="alert alert-success">Mensaje creado exitosamente</div>
|
<div class="alert alert-success"><?= t('Mensaje creado exitosamente') ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if (empty($messages)): ?>
|
<?php if (empty($messages)): ?>
|
||||||
<p class="text-muted text-center py-4">No hay mensajes programados</p>
|
<p class="text-muted text-center py-4"><?= t('No hay mensajes programados') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><?= t('ID') ?></th>
|
||||||
<th>Destinatario</th>
|
<th><?= t('Destinatario') ?></th>
|
||||||
<th>Plataforma</th>
|
<th><?= t('Plataforma') ?></th>
|
||||||
<th>Fecha de Envío</th>
|
<th><?= t('Fecha de Envío') ?></th>
|
||||||
<th>Tipo</th>
|
<th><?= t('Tipo') ?></th>
|
||||||
<th>Estado</th>
|
<th><?= t('Estado') ?></th>
|
||||||
<th>Acciones</th>
|
<th><?= t('Acciones') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -67,9 +68,9 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<td><?= date('d/m/Y H:i', strtotime($msg['send_time'])) ?></td>
|
<td><?= date('d/m/Y H:i', strtotime($msg['send_time'])) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($msg['is_recurring']): ?>
|
<?php if ($msg['is_recurring']): ?>
|
||||||
<span class="badge bg-info">Recurrente</span>
|
<span class="badge bg-info"><?= t('Recurrente') ?></span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="badge bg-secondary">Único</span>
|
<span class="badge bg-secondary"><?= t('Único') ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -82,7 +83,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
'cancelled' => 'dark',
|
'cancelled' => 'dark',
|
||||||
default => 'light'
|
default => 'light'
|
||||||
} ?>">
|
} ?>">
|
||||||
<?= $msg['status'] ?>
|
<?= t($msg['status']) ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -91,7 +92,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
||||||
<input type="hidden" name="action" value="disable">
|
<input type="hidden" name="action" value="disable">
|
||||||
<button type="submit" class="btn btn-outline-warning" title="Deshabilitar">
|
<button type="submit" class="btn btn-outline-warning" title="<?= t('Deshabilitar') ?>">
|
||||||
<i class="bi bi-pause"></i>
|
<i class="bi bi-pause"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -99,7 +100,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
||||||
<input type="hidden" name="action" value="enable">
|
<input type="hidden" name="action" value="enable">
|
||||||
<button type="submit" class="btn btn-outline-success" title="Habilitar">
|
<button type="submit" class="btn btn-outline-success" title="<?= t('Habilitar') ?>">
|
||||||
<i class="bi bi-play"></i>
|
<i class="bi bi-play"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -107,26 +108,26 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<form method="POST" class="d-inline">
|
<form method="POST" class="d-inline">
|
||||||
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
||||||
<input type="hidden" name="action" value="retry">
|
<input type="hidden" name="action" value="retry">
|
||||||
<button type="submit" class="btn btn-outline-info" title="Reintentar">
|
<button type="submit" class="btn btn-outline-info" title="<?= t('Reintentar') ?>">
|
||||||
<i class="bi bi-arrow-clockwise"></i>
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($msg['status'] !== 'sent'): ?>
|
<?php if ($msg['status'] !== 'sent'): ?>
|
||||||
<form method="POST" class="d-inline" onsubmit="return confirm('¿Cancelar este mensaje?');">
|
<form method="POST" class="d-inline" onsubmit="return confirm('<?= t('¿Cancelar este mensaje?') ?>');">
|
||||||
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
||||||
<input type="hidden" name="action" value="cancel">
|
<input type="hidden" name="action" value="cancel">
|
||||||
<button type="submit" class="btn btn-outline-secondary" title="Cancelar">
|
<button type="submit" class="btn btn-outline-secondary" title="<?= t('Cancelar') ?>">
|
||||||
<i class="bi bi-x-circle"></i>
|
<i class="bi bi-x-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="POST" class="d-inline" onsubmit="return confirm('¿Eliminar este mensaje?');">
|
<form method="POST" class="d-inline" onsubmit="return confirm('<?= t('¿Eliminar este mensaje?') ?>');">
|
||||||
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="schedule_id" value="<?= $msg['id'] ?>">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<button type="submit" class="btn btn-outline-danger" title="Eliminar">
|
<button type="submit" class="btn btn-outline-danger" title="<?= t('Eliminar') ?>">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
checkSession();
|
checkSession();
|
||||||
require_once __DIR__ . '/includes/message_handler.php';
|
require_once __DIR__ . '/includes/message_handler.php';
|
||||||
|
|
||||||
$pageTitle = 'Mensajes Enviados';
|
$pageTitle = t('Mensajes Enviados');
|
||||||
|
|
||||||
$userId = getCurrentUserId();
|
$userId = getCurrentUserId();
|
||||||
$messages = getSentMessages(isAdmin() ? null : $userId, 100);
|
$messages = getSentMessages(isAdmin() ? null : $userId, 100);
|
||||||
@@ -13,23 +14,23 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-send"></i> Mensajes Enviados</h2>
|
<h2><i class="bi bi-check2-all"></i> <?= t('Mensajes Enviados') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if (empty($messages)): ?>
|
<?php if (empty($messages)): ?>
|
||||||
<p class="text-muted text-center py-4">No hay mensajes enviados</p>
|
<p class="text-muted text-center py-4"><?= t('No hay mensajes enviados') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><?= t('ID') ?></th>
|
||||||
<th>Destinatario</th>
|
<th><?= t('Destinatario') ?></th>
|
||||||
<th>Plataforma</th>
|
<th><?= t('Plataforma') ?></th>
|
||||||
<th>Fecha de Envío</th>
|
<th><?= t('Fecha de Envío') ?></th>
|
||||||
<th>Mensajes Enviados</th>
|
<th><?= t('Mensajes Enviados') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
require_once __DIR__ . '/includes/env_loader.php';
|
require_once __DIR__ . '/includes/env_loader.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Configurar Webhook de Telegram';
|
$pageTitle = t('Configurar Webhook de Telegram');
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
$error = '';
|
$error = '';
|
||||||
@@ -57,47 +58,47 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-telegram"></i> Configurar Webhook de Telegram</h2>
|
<h2><i class="bi bi-telegram"></i> <?= t('Configurar Webhook de Telegram') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<i class="bi bi-exclamation-triangle"></i> <strong>Importante:</strong> Telegram requiere obligatoriamente HTTPS con un certificado SSL válido.
|
<i class="bi bi-exclamation-triangle"></i> <strong><?= t('Importante') ?>:</strong> <?= t('Telegram requiere obligatoriamente HTTPS con un certificado SSL válido.') ?>
|
||||||
Asegúrate de que tu dominio tenga SSL activado. La URL propuesta usa: <code><?= htmlspecialchars($currentUrl) ?></code>
|
<?= t('Asegúrate de que tu dominio tenga SSL activado.') ?> <?= t('La URL propuesta usa') ?>: <code><?= htmlspecialchars($currentUrl) ?></code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (!empty($results)): ?>
|
<?php if (!empty($results)): ?>
|
||||||
<?php if (isset($results['set'])): ?>
|
<?php if (isset($results['set'])): ?>
|
||||||
<?php if ($results['set']['ok']): ?>
|
<?php if ($results['set']['ok']): ?>
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<i class="bi bi-check-circle"></i> <strong>Webhook configurado correctamente!</strong>
|
<i class="bi bi-check-circle"></i> <strong><?= t('Webhook configurado correctamente') ?>!</strong>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<i class="bi bi-x-circle"></i> <strong>Error:</strong> <?= htmlspecialchars($results['set']['description'] ?? 'Unknown error') ?>
|
<i class="bi bi-x-circle"></i> <strong><?= t('Error') ?>:</strong> <?= htmlspecialchars($results['set']['description'] ?? 'Unknown error') ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($results['delete'])): ?>
|
<?php if (isset($results['delete'])): ?>
|
||||||
<?php if ($results['delete']['ok']): ?>
|
<?php if ($results['delete']['ok']): ?>
|
||||||
<div class="alert alert-success">Webhook eliminado correctamente</div>
|
<div class="alert alert-success"><?= t('Webhook eliminado correctamente') ?></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="alert alert-danger">Error: <?= htmlspecialchars($results['delete']['description'] ?? 'Unknown error') ?></div>
|
<div class="alert alert-danger"><?= t('Error') ?>: <?= htmlspecialchars($results['delete']['description'] ?? 'Unknown error') ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($results['info'])): ?>
|
<?php if (isset($results['info'])): ?>
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Estado Actual del Webhook</h5>
|
<h5 class="mb-0"><?= t('Estado Actual del Webhook') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if ($results['info']['ok'] && !empty($results['info']['result']['url'])): ?>
|
<?php if ($results['info']['ok'] && !empty($results['info']['result']['url'])): ?>
|
||||||
<p><strong>URL:</strong> <?= htmlspecialchars($results['info']['result']['url']) ?></p>
|
<p><strong>URL:</strong> <?= htmlspecialchars($results['info']['result']['url']) ?></p>
|
||||||
<p><strong>Activo:</strong> <?= $results['info']['result']['url'] ? '✅ Sí' : '❌ No' ?></p>
|
<p><strong><?= t('Activo') ?>:</strong> <?= $results['info']['result']['url'] ? '✅ ' . t('Sí') : '❌ ' . t('No') ?></p>
|
||||||
<p><strong>Errores:</strong> <?= $results['info']['result']['last_error_message'] ?? 'Ninguno' ?></p>
|
<p><strong><?= t('Errores') ?>:</strong> <?= $results['info']['result']['last_error_message'] ?? t('Ninguno') ?></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="text-muted">No hay webhook configurado</p>
|
<p class="text-muted"><?= t('No hay webhook configurado') ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,11 +106,11 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">URL del Webhook</h5>
|
<h5 class="mb-0"><?= t('URL del Webhook') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-muted">URL base para el webhook (se añadirá automáticamente el token de autenticación):</p>
|
<p class="text-muted"><?= t('URL base para el webhook (se añadirá automáticamente el token de autenticación)') ?>:</p>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" readonly>
|
<input type="text" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" readonly>
|
||||||
<button class="btn btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars($currentUrl) ?>')">
|
<button class="btn btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars($currentUrl) ?>')">
|
||||||
@@ -122,18 +123,18 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Configurar Webhook</h5>
|
<h5 class="mb-0"><?= t('Configurar Webhook') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="set">
|
<input type="hidden" name="action" value="set">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">URL del webhook</label>
|
<label class="form-label"><?= t('URL del webhook') ?></label>
|
||||||
<input type="text" name="webhook_url" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" required>
|
<input type="text" name="webhook_url" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-plug"></i> Establecer Webhook
|
<i class="bi bi-plug"></i> <?= t('Establecer Webhook') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,21 +143,21 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Opciones</h5>
|
<h5 class="mb-0"><?= t('Opciones') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST" class="mb-2">
|
<form method="POST" class="mb-2">
|
||||||
<input type="hidden" name="action" value="check">
|
<input type="hidden" name="action" value="check">
|
||||||
<button type="submit" class="btn btn-secondary w-100 mb-2">
|
<button type="submit" class="btn btn-secondary w-100 mb-2">
|
||||||
<i class="bi bi-info-circle"></i> Verificar Estado
|
<i class="bi bi-info-circle"></i> <?= t('Verificar Estado') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar el webhook?');">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar el webhook?') ?>');">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<button type="submit" class="btn btn-danger w-100">
|
<button type="submit" class="btn btn-danger w-100">
|
||||||
<i class="bi bi-trash"></i> Eliminar Webhook
|
<i class="bi bi-trash"></i> <?= t('Eliminar Webhook') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,12 +34,10 @@ class Translate
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Primero intentar obtener del caché
|
|
||||||
$cacheKey = $this->generateCacheKey($text, $sourceLang, $targetLang);
|
$cacheKey = $this->generateCacheKey($text, $sourceLang, $targetLang);
|
||||||
$cached = $this->getFromCache($cacheKey);
|
$cached = $this->getFromCache($cacheKey);
|
||||||
|
|
||||||
if ($cached !== null) {
|
if ($cached !== null) {
|
||||||
error_log("Translation cache hit for: $sourceLang -> $targetLang");
|
|
||||||
return $cached;
|
return $cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,24 +45,29 @@ class Translate
|
|||||||
$translatedLines = [];
|
$translatedLines = [];
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
if (trim($line) === '') {
|
$trimmed = trim($line);
|
||||||
|
if ($trimmed === '') {
|
||||||
$translatedLines[] = '';
|
$translatedLines[] = '';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->request('/translate', [
|
try {
|
||||||
'q' => trim($line),
|
$response = $this->request('/translate', [
|
||||||
'source' => $sourceLang,
|
'q' => $trimmed,
|
||||||
'target' => $targetLang,
|
'source' => $sourceLang,
|
||||||
'format' => 'text'
|
'target' => $targetLang,
|
||||||
]);
|
'format' => 'text'
|
||||||
|
]);
|
||||||
|
|
||||||
$translatedLines[] = $response['translatedText'] ?? trim($line);
|
$translated = $response['translatedText'] ?? $trimmed;
|
||||||
|
$translatedLines[] = $translated;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log("Line translation error: " . $e->getMessage());
|
||||||
|
$translatedLines[] = $line; // Mantener original en caso de error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = implode("\n", $translatedLines);
|
$result = implode("\n", $translatedLines);
|
||||||
|
|
||||||
// Guardar en caché
|
|
||||||
$this->saveToCache($cacheKey, $result);
|
$this->saveToCache($cacheKey, $result);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
@@ -74,6 +77,225 @@ class Translate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function translatePartial(string $text, string $targetLang): ?string
|
||||||
|
{
|
||||||
|
if (empty(trim($text))) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cacheKey = $this->generateCacheKey($text . '_partial_v8', 'partial', $targetLang);
|
||||||
|
$cached = $this->getFromCache($cacheKey);
|
||||||
|
|
||||||
|
if ($cached !== null) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Intentar procesar texto con marcadores [:es]...[:es]
|
||||||
|
$processed = $this->processMarkedText($text, $targetLang);
|
||||||
|
if ($processed !== $text) {
|
||||||
|
$this->saveToCache($cacheKey, $processed);
|
||||||
|
return $processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Detección inicial dinámica
|
||||||
|
if (!function_exists('stripEmojisForDetection')) {
|
||||||
|
require_once __DIR__ . '/../includes/emoji_helper.php';
|
||||||
|
}
|
||||||
|
$textForDetection = stripEmojisForDetection($text);
|
||||||
|
$mainLang = $this->detectLanguage($textForDetection);
|
||||||
|
|
||||||
|
error_log("translatePartial: hash=" . md5($text) . " mainLang=" . ($mainLang ?? 'null') . " target=$targetLang");
|
||||||
|
|
||||||
|
// 3. Procesamiento por líneas
|
||||||
|
$lines = explode("\n", $text);
|
||||||
|
|
||||||
|
// Si es una sola línea, intentar traducción global directa
|
||||||
|
if (count($lines) <= 1) {
|
||||||
|
// Si el idioma detectado coincide con el destino, forzamos 'auto' para procesar posibles mezclas
|
||||||
|
$srcLang = ($mainLang === $targetLang) ? 'auto' : ($mainLang ?? 'auto');
|
||||||
|
$result = $this->translate($text, $srcLang, $targetLang);
|
||||||
|
if ($result) {
|
||||||
|
$this->saveToCache($cacheKey, $result);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translatedLines = [];
|
||||||
|
$seenNorm = [];
|
||||||
|
$translatedAny = false;
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmed = trim($line);
|
||||||
|
if (empty($trimmed)) {
|
||||||
|
$translatedLines[] = "";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la línea tiene separadores como " / ", " | ", intentar separarla
|
||||||
|
if (preg_match('/\s+[\/|]\s+/', $trimmed)) {
|
||||||
|
$parts = preg_split('/\s+[\/|]\s+/', $trimmed);
|
||||||
|
$translatedParts = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$partTrimmed = trim($part);
|
||||||
|
if (empty($partTrimmed)) continue;
|
||||||
|
|
||||||
|
$partLang = $this->detectLanguage($partTrimmed);
|
||||||
|
// Si el idioma detectado de la parte coincide con el destino, usamos 'auto' para ver si LibreTranslate traduce algo interno
|
||||||
|
$srcLang = ($partLang === $targetLang) ? 'auto' : ($partLang ?? 'auto');
|
||||||
|
|
||||||
|
$translatedPart = $this->translate($partTrimmed, $srcLang, $targetLang);
|
||||||
|
if ($translatedPart !== $partTrimmed) $translatedAny = true;
|
||||||
|
|
||||||
|
if ($translatedPart) {
|
||||||
|
$normPart = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $translatedPart));
|
||||||
|
if (!isset($seenNorm[$normPart])) {
|
||||||
|
$translatedParts[] = $translatedPart;
|
||||||
|
$seenNorm[$normPart] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($translatedParts)) {
|
||||||
|
$translatedLines[] = implode(" / ", $translatedParts);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$lineLang = $this->detectLanguage($trimmed);
|
||||||
|
// Mismo principio: si coincide el idioma, usamos 'auto' para permitir traducción de mezclas
|
||||||
|
$srcLang = ($lineLang === $targetLang) ? 'auto' : ($lineLang ?? 'auto');
|
||||||
|
|
||||||
|
$translatedLine = $this->translate($trimmed, $srcLang, $targetLang);
|
||||||
|
if ($translatedLine !== $trimmed) $translatedAny = true;
|
||||||
|
|
||||||
|
if ($translatedLine) {
|
||||||
|
$normLine = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $translatedLine));
|
||||||
|
if (!isset($seenNorm[$normLine])) {
|
||||||
|
$translatedLines[] = $translatedLine;
|
||||||
|
$seenNorm[$normLine] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruir el mensaje
|
||||||
|
$result = trim(implode("\n", $translatedLines));
|
||||||
|
$result = preg_replace('/\n{3,}/', "\n\n", $result);
|
||||||
|
|
||||||
|
// Si el resultado es igual al original y no se detectó ninguna traducción efectiva, forzar global con auto
|
||||||
|
if ($result === trim($text) && !$translatedAny) {
|
||||||
|
$globalResult = $this->translate($text, 'auto', $targetLang);
|
||||||
|
if ($globalResult) $result = $globalResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$this->saveToCache($cacheKey, $result);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log("Partial translation error: " . $e->getMessage());
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processMarkedText(string $text, string $targetLang): string
|
||||||
|
{
|
||||||
|
error_log("processMarkedText called with text: $text, target: $targetLang");
|
||||||
|
|
||||||
|
$pattern = '/\[:([a-z]{2})\](.*?)\[:\1\]/su';
|
||||||
|
|
||||||
|
$result = preg_replace_callback($pattern, function($matches) use ($targetLang) {
|
||||||
|
$wordLang = $matches[1];
|
||||||
|
$word = trim($matches[2]);
|
||||||
|
|
||||||
|
if ($wordLang === $targetLang) {
|
||||||
|
return $word;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translated = $this->translate($word, $wordLang, $targetLang);
|
||||||
|
return $translated ?? $word;
|
||||||
|
}, $text);
|
||||||
|
|
||||||
|
if ($result !== $text) {
|
||||||
|
$lines = explode("\n", $result);
|
||||||
|
$uniqueLines = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmed = trim($line);
|
||||||
|
if (empty($trimmed)) {
|
||||||
|
$uniqueLines[] = "";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$norm = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $trimmed));
|
||||||
|
if (mb_strlen($norm) < 10 || !isset($seen[$norm])) {
|
||||||
|
$uniqueLines[] = $trimmed;
|
||||||
|
$seen[$norm] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result = implode("\n", $uniqueLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function splitIntoSegments(string $text): array
|
||||||
|
{
|
||||||
|
if (empty(trim($text))) {
|
||||||
|
return [['text' => $text]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$words = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
foreach ($words as $word) {
|
||||||
|
$trimmed = trim($word);
|
||||||
|
|
||||||
|
if ($trimmed === '') {
|
||||||
|
$result .= $word;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($trimmed) < 3) {
|
||||||
|
$result .= $word;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wordLang = $this->detectLanguage($trimmed);
|
||||||
|
|
||||||
|
if ($wordLang) {
|
||||||
|
$result .= '[:' . $wordLang . ']' . $trimmed . '[:' . $wordLang . ']';
|
||||||
|
} else {
|
||||||
|
$result .= $word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [['text' => $result, 'processed' => true]];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reconstructText(array $segments): string
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
foreach ($segments as $i => $segment) {
|
||||||
|
$text = $segment['translated'];
|
||||||
|
|
||||||
|
if ($i > 0) {
|
||||||
|
$lastChar = substr($result, -1);
|
||||||
|
$firstChar = substr($text, 0, 1);
|
||||||
|
|
||||||
|
if ($lastChar !== ' ' && $lastChar !== "\n" && $firstChar !== ' ' && $firstChar !== "\n") {
|
||||||
|
$result .= ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result .= $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
public function translateToMultiple(string $text, string $sourceLang, array $targetLangs): array
|
public function translateToMultiple(string $text, string $sourceLang, array $targetLangs): array
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TelegramSender
|
|||||||
return $this->request('sendMessage', $data);
|
return $this->request('sendMessage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function editMessageText(int $chatId, int $messageId, string $text, ?array $keyboard = null, ?string $parseMode = 'HTML'): array
|
public function editMessageText(int $chatId, int $messageId, string $text, ?array $keyboard = null, ?string $parseMode = 'HTML', ?string $inlineMessageId = null): array
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'chat_id' => $chatId,
|
'chat_id' => $chatId,
|
||||||
@@ -35,6 +35,12 @@ class TelegramSender
|
|||||||
'text' => $text,
|
'text' => $text,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($inlineMessageId) {
|
||||||
|
unset($data['chat_id']);
|
||||||
|
unset($data['message_id']);
|
||||||
|
$data['inline_message_id'] = $inlineMessageId;
|
||||||
|
}
|
||||||
|
|
||||||
if ($parseMode) {
|
if ($parseMode) {
|
||||||
$data['parse_mode'] = $parseMode;
|
$data['parse_mode'] = $parseMode;
|
||||||
}
|
}
|
||||||
@@ -247,20 +253,43 @@ class TelegramSender
|
|||||||
*/
|
*/
|
||||||
public function sendContentWithOrderedImages(int $chatId, array $segments): void
|
public function sendContentWithOrderedImages(int $chatId, array $segments): void
|
||||||
{
|
{
|
||||||
|
$this->sendContentWithOrderedImagesAndButtons($chatId, $segments, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enviar contenido con texto e imágenes en el orden correcto con botones
|
||||||
|
*/
|
||||||
|
public function sendContentWithOrderedImagesAndButtons(int $chatId, array $segments, ?array $buttons = null): void
|
||||||
|
{
|
||||||
|
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||||
|
$baseUrl = rtrim($appUrl, '/');
|
||||||
|
|
||||||
|
$totalSegments = count($segments);
|
||||||
|
$currentSegment = 0;
|
||||||
|
|
||||||
foreach ($segments as $segment) {
|
foreach ($segments as $segment) {
|
||||||
|
$currentSegment++;
|
||||||
|
$isLastSegment = ($currentSegment === $totalSegments);
|
||||||
|
|
||||||
if ($segment['type'] === 'text') {
|
if ($segment['type'] === 'text') {
|
||||||
// Enviar texto
|
|
||||||
if (!empty(trim($segment['content']))) {
|
if (!empty(trim($segment['content']))) {
|
||||||
$this->sendMessage($chatId, $segment['content']);
|
if ($isLastSegment && $buttons) {
|
||||||
|
$this->sendMessage($chatId, $segment['content'], $buttons);
|
||||||
|
} else {
|
||||||
|
$this->sendMessage($chatId, $segment['content']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ($segment['type'] === 'image') {
|
} elseif ($segment['type'] === 'image') {
|
||||||
$imagePath = $segment['src'];
|
$imagePath = $segment['src'];
|
||||||
|
|
||||||
if (file_exists($imagePath)) {
|
// Convertir ruta local a URL usando APP_URL
|
||||||
// Es un archivo local
|
if (strpos($imagePath, 'http') !== 0) {
|
||||||
$this->sendPhoto($chatId, $imagePath);
|
$imagePath = $baseUrl . '/' . ltrim($imagePath, '/');
|
||||||
} elseif (strpos($imagePath, 'http') === 0) {
|
}
|
||||||
// Es una URL remota
|
|
||||||
|
if ($isLastSegment && $buttons) {
|
||||||
|
$this->sendPhoto($chatId, $imagePath, null, $buttons);
|
||||||
|
} else {
|
||||||
$this->sendPhoto($chatId, $imagePath);
|
$this->sendPhoto($chatId, $imagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../../includes/db.php';
|
require_once __DIR__ . '/../../includes/db.php';
|
||||||
require_once __DIR__ . '/../../includes/session_check.php';
|
require_once __DIR__ . '/../../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../../includes/i18n.php';
|
||||||
require_once __DIR__ . '/../../includes/activity_logger.php';
|
require_once __DIR__ . '/../../includes/activity_logger.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Configuración del Bot de Telegram';
|
$pageTitle = t('Configuración del Bot de Telegram');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo = getDbConnection();
|
$pdo = getDbConnection();
|
||||||
@@ -32,7 +33,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$stmt->execute([$messageText, $buttonText, $groupInviteLink, $isActive, $registerUsers]);
|
$stmt->execute([$messageText, $buttonText, $groupInviteLink, $isActive, $registerUsers]);
|
||||||
|
|
||||||
logActivity(getCurrentUserId(), 'update_telegram_config', 'Configuración del bot de Telegram actualizada');
|
logActivity(getCurrentUserId(), 'update_telegram_config', 'Configuración del bot de Telegram actualizada');
|
||||||
$success = 'Configuración guardada correctamente';
|
$success = t('Configuración guardada correctamente');
|
||||||
|
|
||||||
$stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
|
$stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
|
||||||
$config = $stmt->fetch();
|
$config = $stmt->fetch();
|
||||||
@@ -42,7 +43,7 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-telegram"></i> Configuración del Bot de Telegram</h2>
|
<h2><i class="bi bi-telegram"></i> <?= t('Configuración del Bot de Telegram') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($success): ?>
|
<?php if ($success): ?>
|
||||||
@@ -55,47 +56,47 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Mensaje de Bienvenida</h5>
|
<h5 class="mb-0"><?= t('Mensaje de Bienvenida') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje de bienvenida</label>
|
<label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
|
||||||
<textarea name="message_text" class="form-control" rows="5"><?= htmlspecialchars($config['message_text'] ?? '') ?></textarea>
|
<textarea name="message_text" class="form-control" rows="5"><?= htmlspecialchars($config['message_text'] ?? '') ?></textarea>
|
||||||
<small class="text-muted">Usa <code>{user_name}</code> para el nombre del usuario</small>
|
<small class="text-muted"><?= t('Usa') ?> <code>{user_name}</code> <?= t('para el nombre del usuario') ?></small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Texto del botón</label>
|
<label class="form-label"><?= t('Texto del botón') ?></label>
|
||||||
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($config['button_text'] ?? '') ?>" placeholder="Unirse al grupo">
|
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($config['button_text'] ?? '') ?>" placeholder="<?= t('Unirse al grupo') ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Enlace de invitación al grupo</label>
|
<label class="form-label"><?= t('Enlace de invitación al grupo') ?></label>
|
||||||
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($config['group_invite_link'] ?? '') ?>" placeholder="https://t.me/...">
|
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($config['group_invite_link'] ?? '') ?>" placeholder="https://t.me/...">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Opciones</h5>
|
<h5 class="mb-0"><?= t('Opciones') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-check form-switch mb-3">
|
<div class="form-check form-switch mb-3">
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" id="isActive" <?= ($config['is_active'] ?? true) ? 'checked' : '' ?>>
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActive" <?= ($config['is_active'] ?? true) ? 'checked' : '' ?>>
|
||||||
<label class="form-check-label" for="isActive">Mensaje de bienvenida activo</label>
|
<label class="form-check-label" for="isActive"><?= t('Mensaje de bienvenida activo') ?></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" name="register_users" id="registerUsers" <?= ($config['register_users'] ?? true) ? 'checked' : '' ?>>
|
<input class="form-check-input" type="checkbox" name="register_users" id="registerUsers" <?= ($config['register_users'] ?? true) ? 'checked' : '' ?>>
|
||||||
<label class="form-check-label" for="registerUsers">Registrar usuarios automáticamente</label>
|
<label class="form-check-label" for="registerUsers"><?= t('Registrar usuarios automáticamente') ?></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-save"></i> Guardar Configuración
|
<i class="bi bi-save"></i> <?= t('Guardar Configuración') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../../includes/db.php';
|
require_once __DIR__ . '/../../includes/db.php';
|
||||||
require_once __DIR__ . '/../../includes/session_check.php';
|
require_once __DIR__ . '/../../includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/../../includes/i18n.php';
|
||||||
require_once __DIR__ . '/../../includes/activity_logger.php';
|
require_once __DIR__ . '/../../includes/activity_logger.php';
|
||||||
|
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
|
|
||||||
$pageTitle = 'Mensajes de Bienvenida por Grupo';
|
$pageTitle = t('Mensajes de Bienvenida por Grupo');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo = getDbConnection();
|
$pdo = getDbConnection();
|
||||||
@@ -78,9 +79,9 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-telegram"></i> Mensajes de Bienvenida por Grupo</h2>
|
<h2><i class="bi bi-telegram"></i> <?= t('Mensajes de Bienvenida por Grupo') ?></h2>
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#welcomeModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#welcomeModal">
|
||||||
<i class="bi bi-plus-circle"></i> Nuevo Mensaje
|
<i class="bi bi-plus-circle"></i> <?= t('Nuevo Mensaje') ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -91,19 +92,19 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<?php if (empty($welcomeMessages)): ?>
|
<?php if (empty($welcomeMessages)): ?>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p class="text-muted text-center py-4">No hay mensajes de bienvenida configurados</p>
|
<p class="text-muted text-center py-4"><?= t('No hay mensajes de bienvenida configurados') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($welcomeMessages as $msg): ?>
|
<?php foreach ($welcomeMessages as $msg): ?>
|
||||||
<div class="col-md-6 mb-4">
|
<div class="col-md-6 mb-4">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center">
|
<div class="card-header border-0 d-flex justify-content-between align-items-center">
|
||||||
<h6 class="mb-0"><?= htmlspecialchars($msg['flag_emoji'] ?? '') ?> <?= htmlspecialchars($msg['language_name'] ?? 'Grupo') ?></h6>
|
<h6 class="mb-0"><?= htmlspecialchars($msg['flag_emoji'] ?? '') ?> <?= htmlspecialchars($msg['language_name'] ?? t('Grupo')) ?></h6>
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editModal<?= $msg['id'] ?>">
|
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editModal<?= $msg['id'] ?>">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline">
|
<form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
|
||||||
<input type="hidden" name="action" value="delete">
|
<input type="hidden" name="action" value="delete">
|
||||||
<input type="hidden" name="id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="id" value="<?= $msg['id'] ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger">
|
<button type="submit" class="btn btn-outline-danger">
|
||||||
@@ -115,7 +116,7 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-muted small mb-1">Chat ID: <?= $msg['chat_id'] ?></p>
|
<p class="text-muted small mb-1">Chat ID: <?= $msg['chat_id'] ?></p>
|
||||||
<p class="mb-1"><?= nl2br(htmlspecialchars($msg['welcome_message'] ?? '')) ?></p>
|
<p class="mb-1"><?= nl2br(htmlspecialchars($msg['welcome_message'] ?? '')) ?></p>
|
||||||
<small class="text-muted"><?= $msg['is_active'] ? '✅ Activo' : '❌ Inactivo' ?></small>
|
<small class="text-muted"><?= $msg['is_active'] ? '✅ ' . t('Activo') : '❌ ' . t('Inactivo') ?></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,29 +128,29 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
<input type="hidden" name="action" value="update">
|
<input type="hidden" name="action" value="update">
|
||||||
<input type="hidden" name="id" value="<?= $msg['id'] ?>">
|
<input type="hidden" name="id" value="<?= $msg['id'] ?>">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Editar Mensaje</h5>
|
<h5 class="modal-title"><?= t('Editar Mensaje') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje de bienvenida</label>
|
<label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
|
||||||
<textarea name="welcome_message" class="form-control" rows="3"><?= htmlspecialchars($msg['welcome_message'] ?? '') ?></textarea>
|
<textarea name="welcome_message" class="form-control" rows="3"><?= htmlspecialchars($msg['welcome_message'] ?? '') ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Texto del botón</label>
|
<label class="form-label"><?= t('Texto del botón') ?></label>
|
||||||
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($msg['button_text'] ?? '') ?>">
|
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($msg['button_text'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Enlace de invitación</label>
|
<label class="form-label"><?= t('Enlace de invitación') ?></label>
|
||||||
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($msg['group_invite_link'] ?? '') ?>">
|
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($msg['group_invite_link'] ?? '') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">Código idioma</label>
|
<label class="form-label"><?= t('Código idioma') ?></label>
|
||||||
<input type="text" name="language_code" class="form-control" value="<?= htmlspecialchars($msg['language_code'] ?? 'es') ?>">
|
<input type="text" name="language_code" class="form-control" value="<?= htmlspecialchars($msg['language_code'] ?? 'es') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">Nombre idioma</label>
|
<label class="form-label"><?= t('Nombre idioma') ?></label>
|
||||||
<input type="text" name="language_name" class="form-control" value="<?= htmlspecialchars($msg['language_name'] ?? 'Español') ?>">
|
<input type="text" name="language_name" class="form-control" value="<?= htmlspecialchars($msg['language_name'] ?? 'Español') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -159,11 +160,11 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" id="editActive<?= $msg['id'] ?>" <?= $msg['is_active'] ? 'checked' : '' ?>>
|
<input class="form-check-input" type="checkbox" name="is_active" id="editActive<?= $msg['id'] ?>" <?= $msg['is_active'] ? 'checked' : '' ?>>
|
||||||
<label class="form-check-label" for="editActive<?= $msg['id'] ?>">Activo</label>
|
<label class="form-check-label" for="editActive<?= $msg['id'] ?>"><?= t('Activo') ?></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,33 +180,33 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Nuevo Mensaje de Bienvenida</h5>
|
<h5 class="modal-title"><?= t('Nuevo Mensaje de Bienvenida') ?></h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Chat ID del grupo</label>
|
<label class="form-label"><?= t('Chat ID del grupo') ?></label>
|
||||||
<input type="text" name="chat_id" class="form-control" required>
|
<input type="text" name="chat_id" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Mensaje de bienvenida</label>
|
<label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
|
||||||
<textarea name="welcome_message" class="form-control" rows="3" required></textarea>
|
<textarea name="welcome_message" class="form-control" rows="3" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Texto del botón</label>
|
<label class="form-label"><?= t('Texto del botón') ?></label>
|
||||||
<input type="text" name="button_text" class="form-control">
|
<input type="text" name="button_text" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Enlace de invitación</label>
|
<label class="form-label"><?= t('Enlace de invitación') ?></label>
|
||||||
<input type="text" name="group_invite_link" class="form-control">
|
<input type="text" name="group_invite_link" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">Código idioma</label>
|
<label class="form-label"><?= t('Código idioma') ?></label>
|
||||||
<input type="text" name="language_code" class="form-control" value="es">
|
<input type="text" name="language_code" class="form-control" value="es">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label class="form-label">Nombre idioma</label>
|
<label class="form-label"><?= t('Nombre idioma') ?></label>
|
||||||
<input type="text" name="language_name" class="form-control" value="Español">
|
<input type="text" name="language_name" class="form-control" value="Español">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -215,11 +216,11 @@ require_once __DIR__ . '/../../templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" id="newActive" checked>
|
<input class="form-check-input" type="checkbox" name="is_active" id="newActive" checked>
|
||||||
<label class="form-check-label" for="newActive">Activo</label>
|
<label class="form-check-label" for="newActive"><?= t('Activo') ?></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary">Agregar</button>
|
<button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ if (!$update) {
|
|||||||
exit('No update');
|
exit('No update');
|
||||||
}
|
}
|
||||||
|
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " -收到更新\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Received update\n", FILE_APPEND);
|
||||||
|
|
||||||
if (isset($update['callback_query'])) {
|
if (isset($update['callback_query'])) {
|
||||||
$callbackData = $update['callback_query']['data'] ?? 'no data';
|
$callbackData = $update['callback_query']['data'] ?? 'no data';
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Callback: $callbackData\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Callback: $callbackData\n", FILE_APPEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -82,11 +82,12 @@ try {
|
|||||||
$callbackData = $callbackQuery['data'];
|
$callbackData = $callbackQuery['data'];
|
||||||
$chatId = $callbackQuery['message']['chat']['id'];
|
$chatId = $callbackQuery['message']['chat']['id'];
|
||||||
$messageId = $callbackQuery['message']['message_id'];
|
$messageId = $callbackQuery['message']['message_id'];
|
||||||
|
$inlineMessageId = $callbackQuery['inline_message_id'] ?? null;
|
||||||
$userId = $callbackQuery['from']['id'] ?? '';
|
$userId = $callbackQuery['from']['id'] ?? '';
|
||||||
|
|
||||||
error_log("Telegram callback - chatId: $chatId, messageId: $messageId, userId: $userId, callbackData: $callbackData");
|
error_log("Telegram callback - chatId: $chatId, messageId: $messageId, userId: $userId, callbackData: $callbackData");
|
||||||
|
|
||||||
handleTelegramCallback($pdo, $sender, $translator, $chatId, $messageId, $callbackQuery['id'], $callbackData);
|
handleTelegramCallback($pdo, $sender, $translator, $chatId, $messageId, $inlineMessageId, $callbackQuery['id'], $callbackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -95,6 +96,10 @@ try {
|
|||||||
|
|
||||||
function registerTelegramUser(PDO $pdo, array $user): void
|
function registerTelegramUser(PDO $pdo, array $user): void
|
||||||
{
|
{
|
||||||
|
// Obtener idioma por defecto de la base de datos (el primero activo)
|
||||||
|
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
|
||||||
|
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
||||||
VALUES (?, ?, 'user', 'telegram', ?, 'bot')
|
VALUES (?, ?, 'user', 'telegram', ?, 'bot')
|
||||||
@@ -102,16 +107,15 @@ function registerTelegramUser(PDO $pdo, array $user): void
|
|||||||
");
|
");
|
||||||
|
|
||||||
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||||
$languageCode = $user['language_code'] ?? 'es';
|
$languageCode = $user['language_code'] ?? $defaultLang;
|
||||||
|
|
||||||
$stmt->execute([$user['id'], $name, $languageCode]);
|
$stmt->execute([$user['id'], $name, $languageCode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, string $text): void
|
function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, string $text): void
|
||||||
{
|
{
|
||||||
// Usar texto sin emojis para detección de idioma, pero guardar el original para mostrar
|
// Usar el texto original completo para generar los botones y guardar en caché
|
||||||
$textForDetection = stripEmojisForDetection($text);
|
$keyboard = getTelegramTranslationButtons($pdo, $text);
|
||||||
$keyboard = getTelegramTranslationButtons($pdo, $textForDetection ?: $text);
|
|
||||||
|
|
||||||
if (!empty($keyboard)) {
|
if (!empty($keyboard)) {
|
||||||
$message = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
|
$message = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
|
||||||
@@ -119,10 +123,10 @@ function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Tr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTelegramTranslationButtons(PDO $pdo, string $text, ?string $excludeLang = null): ?array
|
function getTelegramTranslationButtons(PDO $pdo, string $text): ?array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||||
$activeLanguages = $stmt->fetchAll();
|
$activeLanguages = $stmt->fetchAll();
|
||||||
|
|
||||||
if (count($activeLanguages) <= 1) {
|
if (count($activeLanguages) <= 1) {
|
||||||
@@ -135,10 +139,6 @@ function getTelegramTranslationButtons(PDO $pdo, string $text, ?string $excludeL
|
|||||||
|
|
||||||
$buttons = [];
|
$buttons = [];
|
||||||
foreach ($activeLanguages as $lang) {
|
foreach ($activeLanguages as $lang) {
|
||||||
if ($excludeLang && $lang['language_code'] === $excludeLang) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$callbackData = "translate:" . $lang['language_code'] . ":" . $textHash;
|
$callbackData = "translate:" . $lang['language_code'] . ":" . $textHash;
|
||||||
|
|
||||||
$buttons[] = [
|
$buttons[] = [
|
||||||
@@ -335,33 +335,32 @@ function sendTemplateByCommand(PDO $pdo, Telegram\TelegramSender $sender, int $c
|
|||||||
$template = $stmt->fetch();
|
$template = $stmt->fetch();
|
||||||
|
|
||||||
if ($template) {
|
if ($template) {
|
||||||
$converter = new Telegram\Converters\HtmlToTelegramHtmlConverter();
|
// Parsear el contenido manteniendo el orden texto-imagen
|
||||||
$content = $converter->convert($template['message_content']);
|
$segments = $sender->parseContent($template['message_content']);
|
||||||
|
|
||||||
$plainText = $template['message_content'];
|
if (!empty($segments)) {
|
||||||
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
// Obtener texto completo para botones de traducción
|
||||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
$plainText = $template['message_content'];
|
||||||
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
|
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
|
||||||
$plainText = strip_tags($plainText);
|
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
||||||
$plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
|
||||||
// Limpiar espacios múltiples pero preservar saltos de línea
|
$plainText = strip_tags($plainText);
|
||||||
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
$plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
||||||
$plainText = trim($plainText);
|
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
||||||
|
$plainText = trim($plainText);
|
||||||
|
|
||||||
$translationButtons = getTelegramTranslationButtons($pdo, $plainText);
|
$translationButtons = getTelegramTranslationButtons($pdo, $plainText);
|
||||||
|
|
||||||
if ($translationButtons) {
|
// Enviar contenido ordenado con botones
|
||||||
$sender->sendMessage($chatId, $content, $translationButtons);
|
$sender->sendContentWithOrderedImagesAndButtons($chatId, $segments, $translationButtons);
|
||||||
} else {
|
|
||||||
$sender->sendMessage($chatId, $content);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$sender->sendMessage($chatId, "❌ Plantilla no encontrada: #{$command}");
|
$sender->sendMessage($chatId, "❌ Plantilla no encontrada: #{$command}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, int $messageId, string $callbackQueryId, string $callbackData): void
|
function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, int $messageId, ?string $inlineMessageId, string $callbackQueryId, string $callbackData): void
|
||||||
{
|
{
|
||||||
$parts = explode(':', $callbackData, 3);
|
$parts = explode(':', $callbackData, 3);
|
||||||
$action = $parts[0] ?? '';
|
$action = $parts[0] ?? '';
|
||||||
@@ -370,7 +369,7 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
|||||||
$targetLang = $parts[1] ?? 'es';
|
$targetLang = $parts[1] ?? 'es';
|
||||||
$textHash = $parts[2] ?? '';
|
$textHash = $parts[2] ?? '';
|
||||||
|
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, textHash: $textHash\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, textHash: $textHash\n", FILE_APPEND);
|
||||||
|
|
||||||
// Recuperar texto de la base de datos
|
// Recuperar texto de la base de datos
|
||||||
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
|
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
|
||||||
@@ -378,7 +377,7 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
|||||||
$row = $stmt->fetch();
|
$row = $stmt->fetch();
|
||||||
|
|
||||||
if (!$row) {
|
if (!$row) {
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: No text found\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: No text found\n", FILE_APPEND);
|
||||||
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: Texto no encontrado");
|
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: Texto no encontrado");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -386,20 +385,17 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
|||||||
$originalText = $row['original_text'];
|
$originalText = $row['original_text'];
|
||||||
|
|
||||||
if (empty($originalText)) {
|
if (empty($originalText)) {
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Empty text\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Empty text\n", FILE_APPEND);
|
||||||
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: No se pudo recuperar el texto");
|
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: No se pudo recuperar el texto");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Obtener el idioma original (usar texto sin emojis para mayor precisión)
|
// Traducción parcial - detecta el idioma de cada segmento y traduce solo lo necesario
|
||||||
$textForDetection = stripEmojisForDetection($originalText);
|
$textForTranslation = stripEmojisForDetection($originalText);
|
||||||
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es';
|
$translated = $translator->translatePartial($textForTranslation ?: $originalText, $targetLang);
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - sourceLang: $sourceLang, targetLang: $targetLang, originalText: " . substr($originalText, 0, 50) . "\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, originalText: " . substr($originalText, 0, 50) . "\n", FILE_APPEND);
|
||||||
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - translated: $translated\n", FILE_APPEND);
|
||||||
// Traducir (usar texto sin emojis para evitar interferencias)
|
|
||||||
$translated = $translator->translate($textForDetection ?: $originalText, $sourceLang, $targetLang);
|
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - translated: $translated\n", FILE_APPEND);
|
|
||||||
|
|
||||||
if ($translated) {
|
if ($translated) {
|
||||||
// Limpiar TODAS las etiquetas HTML problemáticas (con espacios como < b > o </ b >)
|
// Limpiar TODAS las etiquetas HTML problemáticas (con espacios como < b > o </ b >)
|
||||||
@@ -423,16 +419,16 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
|||||||
|
|
||||||
$sender->answerCallbackQuery($callbackQueryId, "", false);
|
$sender->answerCallbackQuery($callbackQueryId, "", false);
|
||||||
|
|
||||||
$keyboard = getTelegramTranslationButtons($pdo, $originalText, $targetLang);
|
$keyboard = getTelegramTranslationButtons($pdo, $originalText);
|
||||||
|
|
||||||
$result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard);
|
$result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard, 'HTML', $inlineMessageId);
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - editMessageText result: " . json_encode($result) . "\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - editMessageText result: " . json_encode($result) . "\n", FILE_APPEND);
|
||||||
} else {
|
} else {
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Translation failed\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Translation failed\n", FILE_APPEND);
|
||||||
$sender->answerCallbackQuery($callbackQueryId, "❌ Error al traducir el mensaje", false);
|
$sender->answerCallbackQuery($callbackQueryId, "❌ Error al traducir el mensaje", false);
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - EXCEPTION: " . $e->getMessage() . "\n", FILE_APPEND);
|
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - EXCEPTION: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||||
$sender->answerCallbackQuery($callbackQueryId, "❌ Error en la traducción", false);
|
$sender->answerCallbackQuery($callbackQueryId, "❌ Error en la traducción", false);
|
||||||
}
|
}
|
||||||
} elseif ($action === 'chat_mode') {
|
} elseif ($action === 'chat_mode') {
|
||||||
|
|||||||
10
templates/footer.php
Normal file → Executable file
10
templates/footer.php
Normal file → Executable file
@@ -1,6 +1,12 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
</div>
|
<footer class="text-center py-3 mt-auto" style="background: var(--military-dark); border-top: 1px solid var(--military-green);">
|
||||||
|
<small class="text-muted">
|
||||||
|
© <?= date('Y') ?> <strong>LASTWAR</strong> - <?= t('Sistema de Mensajería') ?> |
|
||||||
|
<?= t('Desarrollado por') ?> <a href="https://github.com/nickpons666" target="_blank" style="color: var(--accent-orange);">nickpons666</a> |
|
||||||
|
<a href="https://opensource.org/licenses/MIT" target="_blank" style="color: var(--military-sand);">MIT License</a>
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
|||||||
801
templates/header.php
Normal file → Executable file
801
templates/header.php
Normal file → Executable file
@@ -1,10 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
require_once __DIR__ . '/../includes/session_check.php';
|
require_once __DIR__ . '/../includes/session_check.php';
|
||||||
require_once __DIR__ . '/../includes/url_helper.php';
|
require_once __DIR__ . '/../includes/url_helper.php';
|
||||||
|
require_once __DIR__ . '/../includes/i18n.php';
|
||||||
|
|
||||||
|
handleLanguageChange();
|
||||||
|
|
||||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||||
$theme = $_COOKIE['theme'] ?? 'light';
|
$theme = $_COOKIE['theme'] ?? 'light';
|
||||||
|
$currentLang = getCurrentLanguage();
|
||||||
|
$activeLanguages = getActiveLanguages();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="es" data-bs-theme="<?= $theme ?>">
|
<html lang="es" data-bs-theme="<?= $theme ?>">
|
||||||
@@ -15,190 +22,712 @@ $theme = $_COOKIE['theme'] ?? 'light';
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
--military-dark: #1a1f16;
|
||||||
|
--military-olive: #3d4a32;
|
||||||
|
--military-green: #5a6b4a;
|
||||||
|
--military-tan: #8b7355;
|
||||||
|
--military-sand: #c4b998;
|
||||||
|
--accent-orange: #d4652f;
|
||||||
|
--accent-red: #8b3a3a;
|
||||||
|
--hud-cyan: #4a9c8c;
|
||||||
--discord-color: #5865F2;
|
--discord-color: #5865F2;
|
||||||
--telegram-color: #0088cc;
|
--telegram-color: #0088cc;
|
||||||
|
--navbar-height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
[data-bs-theme="dark"] {
|
[data-bs-theme="dark"] {
|
||||||
--sidebar-bg: #1a1a2e;
|
--bs-body-bg: #12150f;
|
||||||
|
--bs-body-color: #e8e4d9;
|
||||||
|
--bs-card-bg: #1a1f16;
|
||||||
|
--bs-border-color: #3d4a32;
|
||||||
|
--navbar-bg: linear-gradient(180deg, #1a1f16 0%, #0f120c 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-bs-theme="light"] {
|
[data-bs-theme="light"] {
|
||||||
--sidebar-bg: #1a1a2e;
|
--bs-body-bg: #e8e4d9;
|
||||||
|
--bs-body-color: #1a1f16;
|
||||||
|
--bs-card-bg: #f5f3ed;
|
||||||
|
--bs-border-color: #8b7355;
|
||||||
|
--navbar-bg: linear-gradient(180deg, #3d4a32 0%, #2a3024 100%);
|
||||||
|
--military-dark: #2a3024;
|
||||||
|
--military-sand: #e8e4d9;
|
||||||
}
|
}
|
||||||
.sidebar {
|
|
||||||
|
[data-bs-theme="light"] .nav-link-main,
|
||||||
|
[data-bs-theme="light"] .dropdown-toggle-military {
|
||||||
|
color: var(--military-sand) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .nav-link-main:hover,
|
||||||
|
[data-bs-theme="light"] .nav-link-main.active,
|
||||||
|
[data-bs-theme="light"] .dropdown-toggle-military:hover,
|
||||||
|
[data-bs-theme="light"] .dropdown-toggle-military.show {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .navbar-brand small {
|
||||||
|
color: var(--military-sand);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .dropdown-menu-military {
|
||||||
|
background: var(--bs-card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .dropdown-menu-military .dropdown-item {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .dropdown-menu-military .dropdown-item:hover {
|
||||||
|
background: rgba(61, 74, 50, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] #theme-toggle {
|
||||||
|
color: var(--military-sand);
|
||||||
|
border-color: rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .text-muted {
|
||||||
|
color: #5a6b4a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .table thead th {
|
||||||
|
background-color: rgba(61, 74, 50, 0.2);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .card-header {
|
||||||
|
background-color: rgba(61, 74, 50, 0.15);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .alert-danger {
|
||||||
|
background-color: rgba(139, 58, 58, 0.15);
|
||||||
|
color: #8b3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .alert-success {
|
||||||
|
background-color: rgba(74, 156, 140, 0.15);
|
||||||
|
color: #2d6b61;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] ::-webkit-scrollbar-track {
|
||||||
|
background: #d4d0c5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] ::-webkit-scrollbar-thumb {
|
||||||
|
background: #8b7355;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
padding-top: var(--navbar-height);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--sidebar-bg);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-military {
|
||||||
|
background: var(--navbar-bg);
|
||||||
|
border-bottom: 2px solid var(--military-green);
|
||||||
|
min-height: var(--navbar-height);
|
||||||
|
padding: 0 1rem;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-military::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
transparent 0%,
|
||||||
|
var(--accent-orange) 20%,
|
||||||
|
var(--accent-orange) 80%,
|
||||||
|
transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: var(--accent-orange) !important;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand small {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: var(--military-sand);
|
||||||
|
letter-spacing: 3px;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link-main {
|
||||||
|
color: var(--military-sand) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link-main:hover,
|
||||||
|
.nav-link-main.active {
|
||||||
|
color: #fff !important;
|
||||||
|
border-bottom-color: var(--accent-orange);
|
||||||
|
background: rgba(90, 107, 74, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link-main i {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
|
background: var(--military-green);
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section-label {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-military {
|
||||||
|
background: var(--military-dark);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-military .dropdown-item {
|
||||||
|
color: var(--military-sand);
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-military .dropdown-item:hover {
|
||||||
|
background: rgba(90, 107, 74, 0.4);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-military .dropdown-item i {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-military .dropdown-divider {
|
||||||
|
border-color: var(--military-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-military {
|
||||||
|
color: var(--military-sand) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-military:hover,
|
||||||
|
.dropdown-toggle-military.show {
|
||||||
|
color: #fff !important;
|
||||||
|
border-bottom-color: var(--accent-orange);
|
||||||
|
background: rgba(90, 107, 74, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-toggle {
|
||||||
|
color: var(--military-sand);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-toggle:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--military-olive);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler-military {
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler-military .navbar-toggler-icon {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(196, 185, 152, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--bs-card-bg);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
border-radius: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
var(--military-green) 0%,
|
||||||
|
var(--accent-orange) 50%,
|
||||||
|
var(--military-green) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: rgba(61, 74, 50, 0.3);
|
||||||
|
border-bottom: 1px solid var(--military-green);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
[data-bs-theme="dark"] .card-header.bg-white,
|
[data-bs-theme="dark"] .card-header.bg-white,
|
||||||
[data-bs-theme="dark"] .bg-white:not(.btn):not(.nav-link):not(.dropdown-menu) {
|
[data-bs-theme="dark"] .bg-white:not(.btn):not(.nav-link):not(.dropdown-menu) {
|
||||||
background-color: var(--bs-body-bg) !important;
|
background-color: var(--bs-card-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-bs-theme="dark"] .bg-light {
|
[data-bs-theme="dark"] .bg-light {
|
||||||
background-color: var(--bs-tertiary-bg) !important;
|
background-color: rgba(61, 74, 50, 0.2) !important;
|
||||||
}
|
}
|
||||||
.sidebar .nav-link {
|
|
||||||
color: rgba(255,255,255,0.7);
|
.btn-primary {
|
||||||
padding: 12px 20px;
|
background: linear-gradient(180deg, var(--military-olive) 0%, var(--military-dark) 100%);
|
||||||
border-radius: 8px;
|
border: 1px solid var(--military-green);
|
||||||
margin: 4px 8px;
|
color: var(--military-sand);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
#theme-toggle {
|
|
||||||
color: rgba(255,255,255,0.7);
|
.btn-primary:hover {
|
||||||
border: none;
|
background: linear-gradient(180deg, var(--military-green) 0%, var(--military-olive) 100%);
|
||||||
background: transparent;
|
border-color: var(--accent-orange);
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
#theme-toggle:hover {
|
|
||||||
color: white;
|
.btn-outline-secondary {
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
color: var(--military-sand);
|
||||||
|
border-radius: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.sidebar .nav-link:hover, .sidebar .nav-link.active {
|
|
||||||
background: rgba(255,255,255,0.1);
|
.btn-outline-secondary:hover {
|
||||||
color: white;
|
background: var(--military-olive);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-outline-danger {
|
||||||
|
border-color: var(--accent-red);
|
||||||
|
color: var(--accent-red);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-danger:hover {
|
||||||
|
background: var(--accent-red);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-warning {
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: var(--accent-orange);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-warning:hover {
|
||||||
|
background: var(--accent-orange);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-success {
|
||||||
|
border-color: var(--hud-cyan);
|
||||||
|
color: var(--hud-cyan);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-success:hover {
|
||||||
|
background: var(--hud-cyan);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-info {
|
||||||
|
border-color: var(--telegram-color);
|
||||||
|
color: var(--telegram-color);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-info:hover {
|
||||||
|
background: var(--telegram-color);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control, .form-select {
|
||||||
|
background-color: rgba(26, 31, 22, 0.8);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
background-color: rgba(26, 31, 22, 0.9);
|
||||||
|
border-color: var(--accent-orange);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
box-shadow: 0 0 0 2px rgba(212, 101, 47, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .form-control,
|
||||||
|
[data-bs-theme="light"] .form-select {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
border-radius: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-warning {
|
||||||
|
background-color: var(--accent-orange) !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-success {
|
||||||
|
background-color: var(--hud-cyan) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-danger {
|
||||||
|
background-color: var(--accent-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-info {
|
||||||
|
background-color: var(--telegram-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: rgba(61, 74, 50, 0.4);
|
||||||
|
border-bottom: 2px solid var(--military-green);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-hover tbody tr:hover {
|
||||||
|
background-color: rgba(90, 107, 74, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 0;
|
||||||
|
border-left: 3px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background-color: rgba(139, 58, 58, 0.2);
|
||||||
|
border-color: var(--accent-red);
|
||||||
|
color: #ff8a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background-color: rgba(74, 156, 140, 0.2);
|
||||||
|
border-color: var(--hud-cyan);
|
||||||
|
color: #7ce0d0;
|
||||||
|
}
|
||||||
|
|
||||||
.platform-discord { color: var(--discord-color); }
|
.platform-discord { color: var(--discord-color); }
|
||||||
.platform-telegram { color: var(--telegram-color); }
|
.platform-telegram { color: var(--telegram-color); }
|
||||||
.status-pending { color: #ffc107; }
|
.status-pending { color: var(--accent-orange); }
|
||||||
.status-sent { color: #28a745; }
|
.status-sent { color: var(--hud-cyan); }
|
||||||
.status-failed { color: #dc3545; }
|
.status-failed { color: var(--accent-red); }
|
||||||
.status-processing { color: #17a2b8; }
|
.status-processing { color: var(--telegram-color); }
|
||||||
|
|
||||||
|
.stat-card-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px solid;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-icon::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: currentColor;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: currentColor;
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3, h4, h5 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 i, h3 i, h4 i, h5 i {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-color: var(--military-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
background-color: var(--bs-card-bg);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background-color: rgba(90, 107, 74, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--military-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--military-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.navbar-collapse {
|
||||||
|
background: var(--military-dark);
|
||||||
|
border: 1px solid var(--military-green);
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse .nav-link-main,
|
||||||
|
.navbar-collapse .dropdown-toggle-military {
|
||||||
|
padding: 10px 12px !important;
|
||||||
|
border-bottom: 1px solid var(--military-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse .dropdown-menu-military {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse .dropdown-menu-military .dropdown-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-divider {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-toggle {
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<nav class="navbar navbar-expand-lg navbar-military">
|
||||||
<div class="row">
|
<div class="container-fluid">
|
||||||
<nav class="col-md-2 d-none d-md-block sidebar py-3">
|
<a class="navbar-brand" href="/index.php">
|
||||||
<div class="text-center mb-4 position-relative">
|
◈ LASTWAR <small>COMMS</small>
|
||||||
<button class="btn btn-sm position-absolute end-0 me-2" id="theme-toggle" title="Cambiar tema">
|
</a>
|
||||||
<i class="bi bi-moon-fill"></i>
|
|
||||||
</button>
|
|
||||||
<h5 class="text-white"><i class="bi bi-messenger"></i> Mensajería</h5>
|
|
||||||
<small class="text-muted">Discord & Telegram</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="nav flex-column">
|
<button class="navbar-toggler navbar-toggler-military" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMain">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarMain">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'index.php' ? 'active' : '' ?>" href="/index.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'index.php' ? 'active' : '' ?>" href="/index.php">
|
||||||
<i class="bi bi-house-door"></i> Inicio
|
<i class="bi bi-crosshair"></i> <?= t('Dashboard') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'create_message.php' ? 'active' : '' ?>" href="/create_message.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'create_message.php' ? 'active' : '' ?>" href="/create_message.php">
|
||||||
<i class="bi bi-plus-circle"></i> Crear Mensaje
|
<i class="bi bi-pencil-square"></i> <?= t('Crear') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'scheduled_messages.php' ? 'active' : '' ?>" href="/scheduled_messages.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'scheduled_messages.php' ? 'active' : '' ?>" href="/scheduled_messages.php">
|
||||||
<i class="bi bi-clock"></i> Programados
|
<i class="bi bi-stopwatch"></i> <?= t('Programados') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'recurrentes.php' ? 'active' : '' ?>" href="/recurrentes.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'recurrentes.php' ? 'active' : '' ?>" href="/recurrentes.php">
|
||||||
<i class="bi bi-repeat"></i> Plantillas
|
<i class="bi bi-collection"></i> <?= t('Plantillas') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'sent_messages.php' ? 'active' : '' ?>" href="/sent_messages.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'sent_messages.php' ? 'active' : '' ?>" href="/sent_messages.php">
|
||||||
<i class="bi bi-send"></i> Enviados
|
<i class="bi bi-check2-all"></i> <?= t('Enviados') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $currentPage === 'gallery.php' ? 'active' : '' ?>" href="/gallery.php">
|
<a class="nav-link nav-link-main <?= $currentPage === 'gallery.php' ? 'active' : '' ?>" href="/gallery.php">
|
||||||
<i class="bi bi-images"></i> Galería
|
<i class="bi bi-images"></i> <?= t('Galería') ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<?php if (isAdmin()): ?>
|
<?php if (isAdmin()): ?>
|
||||||
<li class="nav-item mt-3">
|
<div class="nav-divider d-none d-lg-block"></div>
|
||||||
<small class="text-muted px-3">ADMINISTRACIÓN</small>
|
<li class="nav-item dropdown">
|
||||||
</li>
|
<a class="nav-link dropdown-toggle-military dropdown-toggle" href="#" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-shield-lock"></i> <?= t('COMANDO') ?>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'users.php' ? 'active' : '' ?>" href="/admin/users.php">
|
|
||||||
<i class="bi bi-people"></i> Usuarios
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'recipients.php' ? 'active' : '' ?>" href="/admin/recipients.php">
|
|
||||||
<i class="bi bi-person-check"></i> Destinatarios
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'languages.php' ? 'active' : '' ?>" href="/admin/languages.php">
|
|
||||||
<i class="bi bi-translate"></i> Idiomas
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'comandos.php' ? 'active' : '' ?>" href="/admin/comandos.php">
|
|
||||||
<i class="bi bi-terminal"></i> Comandos
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'test_discord_connection.php' ? 'active' : '' ?>" href="/admin/test_discord_connection.php">
|
|
||||||
<i class="bi bi-discord"></i> Test Discord
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'ia_agent.php' ? 'active' : '' ?>" href="/admin/ia_agent.php">
|
|
||||||
<i class="bi bi-robot"></i> Config IA
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'set_webhook.php' ? 'active' : '' ?>" href="/set_webhook.php">
|
|
||||||
<i class="bi bi-telegram"></i> Webhook Telegram
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'chat_telegram.php' ? 'active' : '' ?>" href="/chat_telegram.php">
|
|
||||||
<i class="bi bi-chat-dots"></i> Chat Telegram
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'telegram_bot_interactions.php' ? 'active' : '' ?>" href="/telegram/admin/telegram_bot_interactions.php">
|
|
||||||
<i class="bi bi-robot"></i> Bot Telegram
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'telegram_welcome.php' ? 'active' : '' ?>" href="/telegram/admin/telegram_welcome.php">
|
|
||||||
<i class="bi bi-person-plus"></i> Bienvenida Grupos
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'translate_message.php' ? 'active' : '' ?>" href="/translate_message.php">
|
|
||||||
<i class="bi bi-translate"></i> Traductor
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link <?= $currentPage === 'admin_send_message.php' ? 'active' : '' ?>" href="/admin_send_message.php">
|
|
||||||
<i class="bi bi-send"></i> Enviar Directo
|
|
||||||
</a>
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-military">
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'users.php' ? 'active' : '' ?>" href="/admin/users.php"><i class="bi bi-people"></i> <?= t('Usuarios') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'recipients.php' ? 'active' : '' ?>" href="/admin/recipients.php"><i class="bi bi-person-check"></i> <?= t('Destinatarios') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'languages.php' ? 'active' : '' ?>" href="/admin/languages.php"><i class="bi bi-translate"></i> <?= t('Idiomas') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'comandos.php' ? 'active' : '' ?>" href="/admin/comandos.php"><i class="bi bi-terminal"></i> <?= t('Comandos') ?></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'test_discord_connection.php' ? 'active' : '' ?>" href="/admin/test_discord_connection.php"><i class="bi bi-discord"></i> <?= t('Test Discord') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'ia_agent.php' ? 'active' : '' ?>" href="/admin/ia_agent.php"><i class="bi bi-cpu"></i> <?= t('Config IA') ?></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'set_webhook.php' ? 'active' : '' ?>" href="/set_webhook.php"><i class="bi bi-telegram"></i> <?= t('Webhook TG') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'chat_telegram.php' ? 'active' : '' ?>" href="/chat_telegram.php"><i class="bi bi-chat-dots"></i> <?= t('Chat TG') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'telegram_bot_interactions.php' ? 'active' : '' ?>" href="/telegram/admin/telegram_bot_interactions.php"><i class="bi bi-robot"></i> <?= t('Bot Telegram') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'telegram_welcome.php' ? 'active' : '' ?>" href="/telegram/admin/telegram_welcome.php"><i class="bi bi-person-plus"></i> <?= t('Bienvenida') ?></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'translate_message.php' ? 'active' : '' ?>" href="/translate_message.php"><i class="bi bi-translate"></i> <?= t('Traductor') ?></a></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'admin_send_message.php' ? 'active' : '' ?>" href="/admin_send_message.php"><i class="bi bi-lightning"></i> <?= t('Enviar Directo') ?></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item <?= $currentPage === 'system.php' ? 'active' : '' ?>" href="/admin/system.php"><i class="bi bi-hdd-stack"></i> <?= t('Sistema') ?></a></li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="mt-auto pt-3 border-top">
|
<ul class="navbar-nav">
|
||||||
<a class="nav-link <?= $currentPage === 'profile.php' ? 'active' : '' ?>" href="/profile.php">
|
<li class="nav-item dropdown">
|
||||||
<i class="bi bi-person"></i> Perfil
|
<a class="nav-link dropdown-toggle-military dropdown-toggle lang-selector" href="#" data-bs-toggle="dropdown" title="<?= t('Idioma') ?>">
|
||||||
</a>
|
<?php
|
||||||
<a class="nav-link" href="/logout.php">
|
$currentLangData = array_filter($activeLanguages, fn($l) => $l['language_code'] === $currentLang);
|
||||||
<i class="bi bi-box-arrow-left"></i> Cerrar Sesión
|
$currentLangData = reset($currentLangData);
|
||||||
</a>
|
if ($currentLang === 'es' || !$currentLangData) {
|
||||||
</div>
|
echo '<span>🇲🇽</span>';
|
||||||
</nav>
|
} else {
|
||||||
|
echo '<span>' . htmlspecialchars($currentLangData['flag_emoji']) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-military dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item <?= $currentLang === 'es' ? 'active' : '' ?>" href="?lang=es">
|
||||||
|
🇲🇽 <?= t('Español') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php foreach ($activeLanguages as $lang): ?>
|
||||||
|
<?php if ($lang['language_code'] !== 'es'): ?>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>">
|
||||||
|
<?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="btn" id="theme-toggle" title="<?= t('Cambiar tema') ?>">
|
||||||
|
<i class="bi bi-moon-fill"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link nav-link-main <?= $currentPage === 'profile.php' ? 'active' : '' ?>" href="/profile.php">
|
||||||
|
<i class="bi bi-person"></i> <?= t('Perfil') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link nav-link-main" href="/logout.php">
|
||||||
|
<i class="bi bi-box-arrow-left"></i> <?= t('Salir') ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<main class="col-md-10 ms-sm-auto px-4 py-4">
|
<main class="main-content">
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/includes/db.php';
|
require_once __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/session_check.php';
|
require_once __DIR__ . '/includes/session_check.php';
|
||||||
|
require_once __DIR__ . '/includes/i18n.php';
|
||||||
checkSession();
|
checkSession();
|
||||||
require_once __DIR__ . '/includes/env_loader.php';
|
require_once __DIR__ . '/includes/env_loader.php';
|
||||||
require_once __DIR__ . '/src/Translate.php';
|
require_once __DIR__ . '/src/Translate.php';
|
||||||
|
|
||||||
$pageTitle = 'Traducir Mensaje';
|
$pageTitle = t('Traducir Mensaje');
|
||||||
|
|
||||||
$sourceText = $_GET['text'] ?? '';
|
$sourceText = $_GET['text'] ?? '';
|
||||||
$sourceLang = $_GET['source_lang'] ?? 'es';
|
$sourceLang = $_GET['source_lang'] ?? 'es';
|
||||||
@@ -24,7 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$translatedText = $translator->translate($sourceText, $sourceLang, $targetLang);
|
$translatedText = $translator->translate($sourceText, $sourceLang, $targetLang);
|
||||||
|
|
||||||
if (!$translatedText) {
|
if (!$translatedText) {
|
||||||
$error = 'Error al traducir. Verifica la conexión con LibreTranslate.';
|
$error = t('Error al traducir. Verifica la conexión con LibreTranslate.');
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$error = $e->getMessage();
|
$error = $e->getMessage();
|
||||||
@@ -44,7 +45,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2><i class="bi bi-translate"></i> Traducir Mensaje</h2>
|
<h2><i class="bi bi-translate"></i> <?= t('Traducir Mensaje') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
@@ -54,13 +55,13 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Texto Original</h5>
|
<h5 class="mb-0"><?= t('Texto Original') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Idioma de origen</label>
|
<label class="form-label"><?= t('Idioma de origen') ?></label>
|
||||||
<select name="source_lang" class="form-select">
|
<select name="source_lang" class="form-select">
|
||||||
<?php foreach ($languages as $lang): ?>
|
<?php foreach ($languages as $lang): ?>
|
||||||
<option value="<?= $lang['language_code'] ?>" <?= $sourceLang === $lang['language_code'] ? 'selected' : '' ?>>
|
<option value="<?= $lang['language_code'] ?>" <?= $sourceLang === $lang['language_code'] ? 'selected' : '' ?>>
|
||||||
@@ -71,12 +72,12 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Texto a traducir</label>
|
<label class="form-label"><?= t('Texto a traducir') ?></label>
|
||||||
<textarea name="source_text" class="form-control" rows="8" required><?= htmlspecialchars($sourceText) ?></textarea>
|
<textarea name="source_text" class="form-control" rows="8" required><?= htmlspecialchars($sourceText) ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Idioma de destino</label>
|
<label class="form-label"><?= t('Idioma de destino') ?></label>
|
||||||
<select name="target_lang" class="form-select">
|
<select name="target_lang" class="form-select">
|
||||||
<?php foreach ($languages as $lang): ?>
|
<?php foreach ($languages as $lang): ?>
|
||||||
<option value="<?= $lang['language_code'] ?>" <?= $targetLang === $lang['language_code'] ? 'selected' : '' ?>>
|
<option value="<?= $lang['language_code'] ?>" <?= $targetLang === $lang['language_code'] ? 'selected' : '' ?>>
|
||||||
@@ -87,7 +88,7 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-translate"></i> Traducir
|
<i class="bi bi-translate"></i> <?= t('Traducir') ?>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,17 +97,17 @@ require_once __DIR__ . '/templates/header.php';
|
|||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header bg-white border-0">
|
<div class="card-header border-0">
|
||||||
<h5 class="mb-0">Traducción</h5>
|
<h5 class="mb-0"><?= t('Traducción') ?></h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if ($translatedText): ?>
|
<?php if ($translatedText): ?>
|
||||||
<textarea class="form-control" rows="12" readonly><?= htmlspecialchars($translatedText) ?></textarea>
|
<textarea class="form-control" rows="12" readonly><?= htmlspecialchars($translatedText) ?></textarea>
|
||||||
<button class="btn btn-outline-secondary mt-2" onclick="navigator.clipboard.writeText('<?= htmlspecialchars(addslashes($translatedText)) ?>')">
|
<button class="btn btn-outline-secondary mt-2" onclick="navigator.clipboard.writeText('<?= htmlspecialchars(addslashes($translatedText)) ?>')">
|
||||||
<i class="bi bi-clipboard"></i> Copiar
|
<i class="bi bi-clipboard"></i> <?= t('Copiar') ?>
|
||||||
</button>
|
</button>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="text-muted text-center py-5">Traducción aparecerá aquí</p>
|
<p class="text-muted text-center py-5"><?= t('Traducción aparecerá aquí') ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user