Primer commit del sistema separado falta mejorar mucho
This commit is contained in:
40
shared/api/php_errors.log
Executable file
40
shared/api/php_errors.log
Executable file
@@ -0,0 +1,40 @@
|
||||
[30-Nov-2025 06:47:27 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:47:40 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:47:45 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:47:48 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:47:52 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:47:58 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:48:18 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:48:26 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 06:51:10 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
[30-Nov-2025 21:00:05 UTC] PHP Fatal error: Uncaught Error: Call to undefined function hasPermission() in /var/www/html/bot/shared/api/stats.php:29
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in /var/www/html/bot/shared/api/stats.php on line 29
|
||||
78
shared/api/stats.php
Executable file
78
shared/api/stats.php
Executable file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* API de estadísticas para el panel principal
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Cargar variables de entorno
|
||||
if (file_exists(__DIR__ . '/../../.env')) {
|
||||
$lines = file(__DIR__ . '/../../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
if (strpos($line, '=') === false) continue;
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$_ENV[trim($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../database/connection.php';
|
||||
require_once __DIR__. '/../auth/jwt.php';
|
||||
|
||||
// Verificar autenticación
|
||||
$userData = JWTAuth::authenticate();
|
||||
if (!$userData) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'No autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = getDB();
|
||||
|
||||
// Estadísticas de Discord
|
||||
$discordStats = [
|
||||
'users' => 0,
|
||||
'messages' => 0,
|
||||
'templates' => 0
|
||||
];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM destinatarios_discord WHERE activo = 1 AND tipo = 'usuario'");
|
||||
$discordStats['users'] = $stmt->fetch()['total'];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM mensajes_discord WHERE estado = 'enviado'");
|
||||
$discordStats['messages'] = $stmt->fetch()['total'];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM plantillas_discord");
|
||||
$discordStats['templates'] = $stmt->fetch()['total'];
|
||||
|
||||
// Estadísticas de Telegram
|
||||
$telegramStats = [
|
||||
'users' => 0,
|
||||
'messages' => 0,
|
||||
'templates' => 0
|
||||
];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM destinatarios_telegram WHERE activo = 1 AND tipo = 'usuario'");
|
||||
$telegramStats['users'] = $stmt->fetch()['total'];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM mensajes_telegram WHERE estado = 'enviado'");
|
||||
$telegramStats['messages'] = $stmt->fetch()['total'];
|
||||
|
||||
$stmt = $db->query("SELECT COUNT(*) as total FROM plantillas_telegram");
|
||||
$telegramStats['templates'] = $stmt->fetch()['total'];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'discord' => $discordStats,
|
||||
'telegram' => $telegramStats
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error al obtener estadísticas'
|
||||
]);
|
||||
error_log('Error en stats.php: ' . $e->getMessage());
|
||||
}
|
||||
200
shared/auth/jwt.php
Executable file
200
shared/auth/jwt.php
Executable file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/**
|
||||
* Utilidades JWT para autenticación
|
||||
* Basado en Firebase PHP-JWT
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../database/connection.php';
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
class JWTAuth {
|
||||
private static $secret;
|
||||
private static $algorithm = 'HS256';
|
||||
private static $expiration = 3600; // 1 hora por defecto
|
||||
private static $userData = null; // Para almacenar los datos del usuario autenticado
|
||||
|
||||
private static function init() {
|
||||
if (self::$secret === null) {
|
||||
self::$secret = $_ENV['JWT_SECRET'] ?? getenv('JWT_SECRET');
|
||||
$algo = $_ENV['JWT_ALGORITHM'] ?? getenv('JWT_ALGORITHM');
|
||||
$exp = $_ENV['JWT_EXPIRATION'] ?? getenv('JWT_EXPIRATION');
|
||||
|
||||
if ($algo) self::$algorithm = $algo;
|
||||
if ($exp) self::$expiration = (int)$exp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar un token JWT
|
||||
*/
|
||||
public static function generateToken($userId, $username, $rol, $idioma = 'es', $permisos = []) {
|
||||
self::init();
|
||||
|
||||
$issuedAt = time();
|
||||
$expire = $issuedAt + self::$expiration;
|
||||
|
||||
$payload = [
|
||||
'iat' => $issuedAt,
|
||||
'exp' => $expire,
|
||||
'iss' => $_ENV['APP_URL'] ?? getenv('APP_URL'),
|
||||
'data' => [
|
||||
'userId' => $userId,
|
||||
'username' => $username,
|
||||
'rol' => $rol,
|
||||
'idioma' => $idioma,
|
||||
'permissions' => $permisos // Cambiado a 'permissions' para consistencia
|
||||
]
|
||||
];
|
||||
|
||||
return JWT::encode($payload, self::$secret, self::$algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar y decodificar un token JWT
|
||||
*/
|
||||
public static function validateToken($token) {
|
||||
self::init();
|
||||
|
||||
try {
|
||||
$decoded = JWT::decode($token, new Key(self::$secret, self::$algorithm));
|
||||
return [
|
||||
'valid' => true,
|
||||
'data' => $decoded->data
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refrescar un token JWT
|
||||
*/
|
||||
public static function refreshToken($token) {
|
||||
$result = self::validateToken($token);
|
||||
|
||||
if (!$result['valid']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $result['data'];
|
||||
return self::generateToken(
|
||||
$data->userId,
|
||||
$data->username,
|
||||
$data->rol,
|
||||
$data->idioma,
|
||||
(array)$data->permissions // Cambiado a 'permissions'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extraer el token del header Authorization
|
||||
*/
|
||||
public static function getTokenFromHeader() {
|
||||
// Compatibilidad con todos los entornos PHP
|
||||
if (function_exists('getallheaders')) {
|
||||
$headers = getallheaders();
|
||||
} else {
|
||||
$headers = [];
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($headers['Authorization'])) {
|
||||
$matches = [];
|
||||
if (preg_match('/Bearer\s+(.*)$/i', $headers['Authorization'], $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware de autenticación
|
||||
* Retorna los datos del usuario si el token es válido, o false si no
|
||||
*/
|
||||
public static function authenticate() {
|
||||
if (self::$userData !== null) {
|
||||
return self::$userData; // Ya autenticado
|
||||
}
|
||||
|
||||
// Intentar obtener el token del header
|
||||
$token = self::getTokenFromHeader();
|
||||
|
||||
// Si no está en el header, buscar en cookie
|
||||
if (!$token && isset($_COOKIE['auth_token'])) {
|
||||
$token = $_COOKIE['auth_token'];
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = self::validateToken($token);
|
||||
|
||||
if (!$result['valid']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$userData = $result['data'];
|
||||
return self::$userData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener los datos del usuario autenticado.
|
||||
* Asume que authenticate() o requireAuth() ya han sido llamados.
|
||||
*/
|
||||
public static function getUserData() {
|
||||
return self::$userData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware que requiere autenticación
|
||||
* Redirige al login si no está autenticado
|
||||
*/
|
||||
public static function requireAuth($redirectTo = '/login.php') {
|
||||
$userData = self::authenticate();
|
||||
|
||||
if (!$userData) {
|
||||
header('Location: ' . $redirectTo);
|
||||
exit;
|
||||
}
|
||||
|
||||
return $userData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargar los permisos de un usuario desde la base de datos
|
||||
*/
|
||||
public static function loadUserPermissions($userId) {
|
||||
try {
|
||||
$db = getDB();
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT p.nombre
|
||||
FROM permisos p
|
||||
INNER JOIN usuarios_permisos up ON p.id = up.permiso_id
|
||||
WHERE up.usuario_id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([$userId]);
|
||||
$permisos = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
return $permisos;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error cargando permisos: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
shared/bootstrap.php
Executable file
50
shared/bootstrap.php
Executable file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Bootstrap File
|
||||
*
|
||||
* Carga y configura todos los componentes esenciales de la aplicación.
|
||||
*/
|
||||
|
||||
// Iniciar sesión si no está iniciada
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Habilitar logging de errores
|
||||
ini_set('display_errors', 0); // No mostrar errores al usuario
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/../logs/php_errors.log'); // Ruta centralizada
|
||||
|
||||
// Cargar variables de entorno
|
||||
if (file_exists(__DIR__ . '/../.env')) {
|
||||
$lines = file(__DIR__ . '/../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
if (strpos($line, '=') === false) continue;
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$_ENV[trim($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Cargar la conexión a la base de datos
|
||||
require_once __DIR__ . '/database/connection.php';
|
||||
|
||||
// 2. Cargar el helper de autenticación JWT
|
||||
require_once __DIR__ . '/auth/jwt.php';
|
||||
|
||||
// 3. Cargar helpers generales (que ahora pueden asumir que la DB y JWT existen)
|
||||
require_once __DIR__ . '/utils/helpers.php';
|
||||
|
||||
// 4. Realizar la autenticación y obtener los datos del usuario
|
||||
// Esto se hace una sola vez y los datos se guardan en la clase JWTAuth
|
||||
try {
|
||||
$userData = JWTAuth::requireAuth();
|
||||
} catch (Exception $e) {
|
||||
// Si la autenticación falla, redirigir al login
|
||||
// Esto es más seguro que mostrar un 'die()', ya que no expone la estructura de archivos.
|
||||
header('Location: /login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. Cargar el gestor de traducciones (que depende de los datos del usuario para el idioma)
|
||||
require_once __DIR__ . '/translations/manager.php';
|
||||
76
shared/database/connection.php
Executable file
76
shared/database/connection.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Conexión a Base de Datos Compartida
|
||||
* Utilizada por Discord, Telegram y todos los módulos del sistema
|
||||
*/
|
||||
|
||||
class Database {
|
||||
private static $instance = null;
|
||||
private $connection;
|
||||
|
||||
private function __construct() {
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
private function connect() {
|
||||
try {
|
||||
$host = $_ENV['DB_HOST'] ?? getenv('DB_HOST');
|
||||
$port = $_ENV['DB_PORT'] ?? getenv('DB_PORT');
|
||||
$dbname = $_ENV['DB_NAME'] ?? getenv('DB_NAME');
|
||||
$user = $_ENV['DB_USER'] ?? getenv('DB_USER');
|
||||
$pass = $_ENV['DB_PASS'] ?? getenv('DB_PASS');
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
|
||||
];
|
||||
|
||||
$this->connection = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$logFile = __DIR__ . '/../../logs/database_errors.log';
|
||||
$logMessage = date('Y-m-d H:i:s') . " - ERROR DE CONEXIÓN: " . $e->getMessage() . "\n";
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
|
||||
throw new Exception("Error de conexión a la base de datos. Consulte los logs para más detalles.");
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
// Verificar si la conexión está activa
|
||||
try {
|
||||
$this->connection->query('SELECT 1');
|
||||
} catch (PDOException $e) {
|
||||
// Reconectar si la conexión se perdió
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
// Prevenir clonación
|
||||
private function __clone() {}
|
||||
|
||||
// Prevenir deserialización
|
||||
public function __wakeup() {
|
||||
throw new Exception("No se puede deserializar un singleton.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Función helper para obtener la conexión
|
||||
*/
|
||||
function getDB() {
|
||||
return Database::getInstance()->getConnection();
|
||||
}
|
||||
363
shared/database/schema.sql
Executable file
363
shared/database/schema.sql
Executable file
@@ -0,0 +1,363 @@
|
||||
-- =====================================================
|
||||
-- SCHEMA DE BASE DE DATOS COMPLETO
|
||||
-- Sistema de Administración de Bots Discord/Telegram
|
||||
-- =====================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- =====================================================
|
||||
-- TABLAS COMPARTIDAS
|
||||
-- =====================================================
|
||||
|
||||
-- Tabla de usuarios del panel
|
||||
CREATE TABLE IF NOT EXISTS `usuarios` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`email` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`nombre_completo` VARCHAR(100),
|
||||
`rol_id` INT UNSIGNED,
|
||||
`idioma_id` INT UNSIGNED DEFAULT 1,
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`ultimo_acceso` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_username` (`username`),
|
||||
INDEX `idx_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Tabla de roles
|
||||
CREATE TABLE IF NOT EXISTS `roles` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`nombre` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`descripcion` VARCHAR(255),
|
||||
`nivel` INT DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Insertar roles por defecto
|
||||
INSERT INTO `roles` (`nombre`, `descripcion`, `nivel`) VALUES
|
||||
('Admin', 'Administrador con acceso completo', 100),
|
||||
('Editor', 'Editor con permisos limitados', 50)
|
||||
ON DUPLICATE KEY UPDATE `descripcion`=VALUES(`descripcion`);
|
||||
|
||||
-- Tabla de permisos
|
||||
CREATE TABLE IF NOT EXISTS `permisos` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`nombre` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`descripcion` VARCHAR(255),
|
||||
`modulo` ENUM('discord', 'telegram', 'global') DEFAULT 'global',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Insertar permisos por defecto
|
||||
INSERT INTO `permisos` (`nombre`, `descripcion`, `modulo`) VALUES
|
||||
('crear_mensajes', 'Crear y enviar mensajes', 'global'),
|
||||
('editar_plantillas', 'Editar plantillas de mensajes', 'global'),
|
||||
('gestionar_usuarios', 'Gestionar destinatarios', 'global'),
|
||||
('ver_logs', 'Ver logs del sistema', 'global'),
|
||||
('gestionar_roles', 'Gestionar roles y permisos', 'global')
|
||||
ON DUPLICATE KEY UPDATE `descripcion`=VALUES(`descripcion`);
|
||||
|
||||
-- Tabla relacional usuarios-permisos
|
||||
CREATE TABLE IF NOT EXISTS `usuarios_permisos` (
|
||||
`usuario_id` INT UNSIGNED NOT NULL,
|
||||
`permiso_id` INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`usuario_id`, `permiso_id`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`permiso_id`) REFERENCES `permisos`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Tabla de idiomas
|
||||
CREATE TABLE IF NOT EXISTS `idiomas` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`codigo` VARCHAR(10) NOT NULL UNIQUE,
|
||||
`nombre` VARCHAR(50) NOT NULL,
|
||||
`nombre_nativo` VARCHAR(50),
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Insertar idiomas por defecto
|
||||
INSERT INTO `idiomas` (`codigo`, `nombre`, `nombre_nativo`, `activo`) VALUES
|
||||
('es', 'Español', 'Español', 1),
|
||||
('en', 'Inglés', 'English', 1),
|
||||
('fr', 'Francés', 'Français', 0),
|
||||
('de', 'Alemán', 'Deutsch', 0),
|
||||
('pt', 'Portugués', 'Português', 0)
|
||||
ON DUPLICATE KEY UPDATE `nombre`=VALUES(`nombre`);
|
||||
|
||||
-- Tabla de galería (compartida)
|
||||
CREATE TABLE IF NOT EXISTS `gallery` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`nombre` VARCHAR(255) NOT NULL,
|
||||
`nombre_original` VARCHAR(255) NOT NULL,
|
||||
`ruta` VARCHAR(500) NOT NULL,
|
||||
`ruta_thumbnail` VARCHAR(500),
|
||||
`hash_md5` VARCHAR(32),
|
||||
`tipo_mime` VARCHAR(100),
|
||||
`tamano` INT UNSIGNED,
|
||||
`ancho` INT UNSIGNED,
|
||||
`alto` INT UNSIGNED,
|
||||
`usuario_id` INT UNSIGNED,
|
||||
`fecha_subida` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_hash` (`hash_md5`),
|
||||
INDEX `idx_usuario` (`usuario_id`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- =====================================================
|
||||
-- TABLAS DE DISCORD
|
||||
-- =====================================================
|
||||
|
||||
-- Destinatarios de Discord
|
||||
CREATE TABLE IF NOT EXISTS `destinatarios_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`discord_id` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`tipo` ENUM('usuario', 'canal', 'grupo', 'rol') DEFAULT 'usuario',
|
||||
`nombre` VARCHAR(255),
|
||||
`username` VARCHAR(100),
|
||||
`discriminador` VARCHAR(10),
|
||||
`avatar_url` VARCHAR(500),
|
||||
`idioma_detectado` VARCHAR(10),
|
||||
`chat_mode` VARCHAR(10) DEFAULT 'bot',
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`fecha_registro` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`ultima_interaccion` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_discord_id` (`discord_id`),
|
||||
INDEX `idx_tipo` (`tipo`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Plantillas de Discord
|
||||
CREATE TABLE IF NOT EXISTS `plantillas_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`nombre` VARCHAR(100) NOT NULL,
|
||||
`contenido` TEXT NOT NULL,
|
||||
`usuario_id` INT UNSIGNED,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`fecha_modificacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Mensajes de Discord
|
||||
CREATE TABLE IF NOT EXISTS `mensajes_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`contenido` TEXT NOT NULL,
|
||||
`tipo_envio` ENUM('inmediato', 'programado', 'recurrente') DEFAULT 'inmediato',
|
||||
`fecha_envio` TIMESTAMP NULL,
|
||||
`canal_id` VARCHAR(50),
|
||||
`mensaje_discord_id` VARCHAR(50),
|
||||
`usuario_id` INT UNSIGNED,
|
||||
`plantilla_id` INT UNSIGNED NULL,
|
||||
`estado` ENUM('pendiente', 'enviado', 'fallido', 'deshabilitado') DEFAULT 'pendiente',
|
||||
`error` TEXT NULL,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_tipo_envio` (`tipo_envio`),
|
||||
INDEX `idx_estado` (`estado`),
|
||||
INDEX `idx_fecha_envio` (`fecha_envio`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`plantilla_id`) REFERENCES `plantillas_discord`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Destinatarios de mensajes Discord (relación muchos a muchos)
|
||||
CREATE TABLE IF NOT EXISTS `mensajes_discord_destinatarios` (
|
||||
`mensaje_id` INT UNSIGNED NOT NULL,
|
||||
`destinatario_id` INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`mensaje_id`, `destinatario_id`),
|
||||
FOREIGN KEY (`mensaje_id`) REFERENCES `mensajes_discord`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`destinatario_id`) REFERENCES `destinatarios_discord`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Mensajes recurrentes de Discord
|
||||
CREATE TABLE IF NOT EXISTS `recurrentes_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`mensaje_id` INT UNSIGNED NOT NULL,
|
||||
`frecuencia` ENUM('diario', 'semanal', 'mensual') DEFAULT 'diario',
|
||||
`hora_envio` TIME DEFAULT '09:00:00',
|
||||
`dia_semana` TINYINT NULL COMMENT '1=Lunes, 7=Domingo',
|
||||
`dia_mes` TINYINT NULL COMMENT '1-31',
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`ultimo_envio` TIMESTAMP NULL,
|
||||
`proximo_envio` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`mensaje_id`) REFERENCES `mensajes_discord`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Logs de Discord
|
||||
CREATE TABLE IF NOT EXISTS `logs_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tipo` ENUM('mensaje', 'comando', 'error', 'interaccion', 'usuario') DEFAULT 'mensaje',
|
||||
`nivel` ENUM('info', 'warning', 'error') DEFAULT 'info',
|
||||
`descripcion` TEXT,
|
||||
`datos_json` JSON NULL,
|
||||
`usuario_id` INT UNSIGNED NULL,
|
||||
`destinatario_id` INT UNSIGNED NULL,
|
||||
`fecha` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_tipo` (`tipo`),
|
||||
INDEX `idx_nivel` (`nivel`),
|
||||
INDEX `idx_fecha` (`fecha`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Comandos de Discord
|
||||
CREATE TABLE IF NOT EXISTS `comandos_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`comando` VARCHAR(50) NOT NULL,
|
||||
`descripcion` VARCHAR(255),
|
||||
`destinatario_id` INT UNSIGNED,
|
||||
`plantilla_id` INT UNSIGNED NULL,
|
||||
`veces_usado` INT UNSIGNED DEFAULT 0,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`ultimo_uso` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_comando` (`comando`),
|
||||
FOREIGN KEY (`destinatario_id`) REFERENCES `destinatarios_discord`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`plantilla_id`) REFERENCES `plantillas_discord`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Configuración de bienvenida Discord
|
||||
CREATE TABLE IF NOT EXISTS `bienvenida_discord` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`canal_id` VARCHAR(50) NOT NULL,
|
||||
`texto` TEXT,
|
||||
`imagen_id` INT UNSIGNED NULL,
|
||||
`idiomas_habilitados` JSON NULL,
|
||||
`registrar_usuario` TINYINT(1) DEFAULT 1,
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`imagen_id`) REFERENCES `gallery`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- =====================================================
|
||||
-- TABLAS DE TELEGRAM
|
||||
-- =====================================================
|
||||
|
||||
-- Destinatarios de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `destinatarios_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`telegram_id` VARCHAR(50) NOT NULL UNIQUE,
|
||||
`tipo` ENUM('usuario', 'canal', 'grupo') DEFAULT 'usuario',
|
||||
`nombre` VARCHAR(255),
|
||||
`username` VARCHAR(100),
|
||||
`avatar_url` VARCHAR(500),
|
||||
`idioma_detectado` VARCHAR(10),
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`fecha_registro` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`ultima_interaccion` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_telegram_id` (`telegram_id`),
|
||||
INDEX `idx_tipo` (`tipo`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Plantillas de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `plantillas_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`nombre` VARCHAR(100) NOT NULL,
|
||||
`comando` VARCHAR(50) UNIQUE,
|
||||
`contenido` TEXT NOT NULL,
|
||||
`usuario_id` INT UNSIGNED,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`fecha_modificacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_comando` (`comando`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Mensajes de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `mensajes_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`contenido` TEXT NOT NULL,
|
||||
`tipo_envio` ENUM('inmediato', 'programado', 'recurrente') DEFAULT 'inmediato',
|
||||
`fecha_envio` TIMESTAMP NULL,
|
||||
`chat_id` VARCHAR(50),
|
||||
`mensaje_telegram_id` VARCHAR(50),
|
||||
`usuario_id` INT UNSIGNED,
|
||||
`plantilla_id` INT UNSIGNED NULL,
|
||||
`estado` ENUM('pendiente', 'enviado', 'fallido', 'deshabilitado') DEFAULT 'pendiente',
|
||||
`error` TEXT NULL,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_tipo_envio` (`tipo_envio`),
|
||||
INDEX `idx_estado` (`estado`),
|
||||
INDEX `idx_fecha_envio` (`fecha_envio`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`plantilla_id`) REFERENCES `plantillas_telegram`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Destinatarios de mensajes Telegram (relación muchos a muchos)
|
||||
CREATE TABLE IF NOT EXISTS `mensajes_telegram_destinatarios` (
|
||||
`mensaje_id` INT UNSIGNED NOT NULL,
|
||||
`destinatario_id` INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`mensaje_id`, `destinatario_id`),
|
||||
FOREIGN KEY (`mensaje_id`) REFERENCES `mensajes_telegram`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`destinatario_id`) REFERENCES `destinatarios_telegram`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Mensajes recurrentes de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `recurrentes_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`mensaje_id` INT UNSIGNED NOT NULL,
|
||||
`frecuencia` ENUM('diario', 'semanal', 'mensual') DEFAULT 'diario',
|
||||
`hora_envio` TIME DEFAULT '09:00:00',
|
||||
`dia_semana` TINYINT NULL,
|
||||
`dia_mes` TINYINT NULL,
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`ultimo_envio` TIMESTAMP NULL,
|
||||
`proximo_envio` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`mensaje_id`) REFERENCES `mensajes_telegram`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Logs de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `logs_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tipo` ENUM('mensaje', 'comando', 'error', 'interaccion', 'usuario') DEFAULT 'mensaje',
|
||||
`nivel` ENUM('info', 'warning', 'error') DEFAULT 'info',
|
||||
`descripcion` TEXT,
|
||||
`datos_json` JSON NULL,
|
||||
`usuario_id` INT UNSIGNED NULL,
|
||||
`destinatario_id` INT UNSIGNED NULL,
|
||||
`fecha` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_tipo` (`tipo`),
|
||||
INDEX `idx_nivel` (`nivel`),
|
||||
INDEX `idx_fecha` (`fecha`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Comandos de Telegram
|
||||
CREATE TABLE IF NOT EXISTS `comandos_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`comando` VARCHAR(50) NOT NULL,
|
||||
`descripcion` VARCHAR(255),
|
||||
`destinatario_id` INT UNSIGNED,
|
||||
`plantilla_id` INT UNSIGNED NULL,
|
||||
`veces_usado` INT UNSIGNED DEFAULT 0,
|
||||
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`ultimo_uso` TIMESTAMP NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_comando` (`comando`),
|
||||
FOREIGN KEY (`destinatario_id`) REFERENCES `destinatarios_telegram`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`plantilla_id`) REFERENCES `plantillas_telegram`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Configuración de bienvenida Telegram
|
||||
CREATE TABLE IF NOT EXISTS `bienvenida_telegram` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`chat_id` VARCHAR(50) NOT NULL,
|
||||
`texto` TEXT,
|
||||
`imagen_id` INT UNSIGNED NULL,
|
||||
`idiomas_habilitados` JSON NULL,
|
||||
`registrar_usuario` TINYINT(1) DEFAULT 1,
|
||||
`activo` TINYINT(1) DEFAULT 1,
|
||||
`boton_unirse_texto` VARCHAR(100) DEFAULT 'Únete al grupo',
|
||||
`boton_unirse_url` VARCHAR(500),
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`imagen_id`) REFERENCES `gallery`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
41
shared/languages/get_active.php
Executable file
41
shared/languages/get_active.php
Executable file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Habilitar logging para depuración
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/../../shared/utils/helpers.php';
|
||||
require_once __DIR__ . '/../../shared/auth/jwt.php';
|
||||
require_once __DIR__ . '/../../shared/database/connection.php';
|
||||
|
||||
// Verificar autenticación (opcional, dependiendo de tus requisitos de seguridad)
|
||||
try {
|
||||
$userData = JWTAuth::authenticate();
|
||||
if (!$userData) {
|
||||
jsonResponse(['success' => false, 'error' => 'No autenticado'], 401);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Si la autenticación falla, igualmente devolvemos los idiomas (o puedes cambiar esto según tus necesidades)
|
||||
}
|
||||
|
||||
try {
|
||||
$db = getDB();
|
||||
|
||||
// Obtener solo los idiomas activos
|
||||
$stmt = $db->query("SELECT id, nombre, codigo, bandera FROM idiomas WHERE activo = 1 ORDER BY nombre ASC");
|
||||
$languages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'languages' => $languages
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error al obtener idiomas activos: " . $e->getMessage());
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Error al cargar los idiomas',
|
||||
'debug' => DEBUG_MODE ? $e->getMessage() : null
|
||||
], 500);
|
||||
}
|
||||
604
shared/languages/manager.php
Executable file
604
shared/languages/manager.php
Executable file
@@ -0,0 +1,604 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Habilitar logging para depuración
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/../../shared/utils/helpers.php';
|
||||
require_once __DIR__ . '/../../shared/auth/jwt.php';
|
||||
require_once __DIR__ . '/../../shared/database/connection.php';
|
||||
require_once __DIR__ . '/../../shared/translations/manager.php'; // Incluir helper de traducciones
|
||||
|
||||
$userData = JWTAuth::requireAuth();
|
||||
|
||||
// Verificar permiso para ver la página de gestión de idiomas
|
||||
if (!hasPermission('view_languages')) {
|
||||
die(__('you_do_not_have_permission_to_manage_languages'));
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Manejar acciones POST (Activar/Desactivar/Sincronizar/Actualizar Bandera)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Verificar permiso para realizar acciones de gestión de idiomas
|
||||
if (!hasPermission('manage_languages')) {
|
||||
jsonResponse(['success' => false, 'error' => __('you_do_not_have_permission_to_manage_languages')], 403);
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (isset($input['action'])) {
|
||||
try {
|
||||
if ($input['action'] === 'toggle' && isset($input['id'])) {
|
||||
$stmt = $db->prepare("UPDATE idiomas SET activo = NOT activo WHERE id = ?");
|
||||
$stmt->execute([$input['id']]);
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
elseif ($input['action'] === 'update_flag' && isset($input['id'])) {
|
||||
$flag = $input['flag'] ?? null;
|
||||
$stmt = $db->prepare("UPDATE idiomas SET bandera = ? WHERE id = ?");
|
||||
$stmt->execute([$flag, $input['id']]);
|
||||
jsonResponse(['success' => true]);
|
||||
}
|
||||
elseif ($input['action'] === 'sync') {
|
||||
// 1. Obtener idiomas de LibreTranslate
|
||||
$ltUrl = $_ENV['LIBRETRANSLATE_URL'] ?? getenv('LIBRETRANSLATE_URL') ?? 'http://10.10.4.17:5000';
|
||||
$ch = curl_init("$ltUrl/languages");
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || !$response) {
|
||||
throw new Exception("No se pudo conectar con LibreTranslate ($ltUrl)");
|
||||
}
|
||||
|
||||
$languages = json_decode($response, true);
|
||||
if (!is_array($languages)) {
|
||||
throw new Exception("Respuesta inválida de LibreTranslate");
|
||||
}
|
||||
|
||||
// 2. Actualizar base de datos
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO idiomas (codigo, nombre, nombre_nativo, activo)
|
||||
VALUES (?, ?, ?, 1)
|
||||
ON DUPLICATE KEY UPDATE nombre = VALUES(nombre), nombre_nativo = VALUES(nombre_nativo)
|
||||
");
|
||||
|
||||
$count = 0;
|
||||
foreach ($languages as $lang) {
|
||||
// LibreTranslate devuelve: code, name, targets
|
||||
// No siempre devuelve nombre nativo, usaremos el nombre en inglés por defecto
|
||||
$stmt->execute([
|
||||
$lang['code'],
|
||||
$lang['name'],
|
||||
$lang['name'], // Usamos el mismo nombre ya que LT no siempre da el nativo en /languages
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'message' => str_replace('{count}', $count, __('synced_languages'))]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
jsonResponse(['success' => false, 'error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener idiomas
|
||||
$stmt = $db->query("SELECT * FROM idiomas ORDER BY nombre ASC");
|
||||
$idiomas = $stmt->fetchAll();
|
||||
|
||||
// URL de LibreTranslate desde .env
|
||||
$libreTranslateUrl = $_ENV['LIBRETRANSLATE_URL'] ?? getenv('LIBRETRANSLATE_URL') ?? 'http://10.10.4.17:5000';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo $userData->idioma ?? 'es'; ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo __('language_manager_title'); ?></title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<script src="/shared/public/js/notifications.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #5865F2;
|
||||
--secondary-color: #6c757d;
|
||||
--success-color: #28a745;
|
||||
--danger-color: #dc3545;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 20px 30px;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #444;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.test-area textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #eee;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.test-result {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-flag {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-flag:hover {
|
||||
background: #f0f0f0;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Modal Banderas */
|
||||
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); }
|
||||
.modal-content { background: white; margin: 10% auto; padding: 25px; width: 90%; max-width: 600px; border-radius: 15px; position: relative; }
|
||||
.close-modal { position: absolute; top: 15px; right: 20px; font-size: 24px; cursor: pointer; color: #aaa; }
|
||||
.flag-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 10px; margin-top: 15px; max-height: 300px; overflow-y: auto; padding: 5px; }
|
||||
.flag-option { font-size: 24px; cursor: pointer; padding: 5px; border-radius: 5px; text-align: center; }
|
||||
.flag-option:hover { background: #f0f0f0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1><i class="fas fa-language"></i> <?php echo __('language_manager_header'); ?></h1>
|
||||
<a href="/index.php" class="btn btn-secondary">
|
||||
<i class="fas fa-home"></i> <?php echo __('back_to_dashboard'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span><?php echo __('available_languages'); ?></span>
|
||||
<button onclick="syncLanguages()" class="btn btn-success" style="font-size: 12px; padding: 5px 10px;">
|
||||
<i class="fas fa-sync"></i> <?php echo __('sync_with_libretranslate'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo __('flag'); ?></th>
|
||||
<th><?php echo __('code'); ?></th>
|
||||
<th><?php echo __('name'); ?></th>
|
||||
<th><?php echo __('native'); ?></th>
|
||||
<th><?php echo __('status'); ?></th>
|
||||
<th><?php echo __('action'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($idiomas as $lang): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<button class="btn-flag" onclick="openFlagModal(<?php echo $lang['id']; ?>, '<?php echo $lang['bandera'] ?? ''; ?>')">
|
||||
<?php echo $lang['bandera'] ? $lang['bandera'] : '<i class="fas fa-plus" style="font-size: 10px; color: #ccc;"></i>'; ?>
|
||||
</button>
|
||||
</td>
|
||||
<td><code><?php echo htmlspecialchars($lang['codigo']); ?></code></td>
|
||||
<td><?php echo htmlspecialchars($lang['nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($lang['nombre_nativo']); ?></td>
|
||||
<td>
|
||||
<span class="status-badge <?php echo $lang['activo'] ? 'status-active' : 'status-inactive'; ?>">
|
||||
<?php echo $lang['activo'] ? __('active') : __('inactive'); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<label class="switch">
|
||||
<input type="checkbox"
|
||||
onchange="toggleLanguage(<?php echo $lang['id']; ?>)"
|
||||
<?php echo $lang['activo'] ? 'checked' : ''; ?>>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><?php echo __('translation_test'); ?></div>
|
||||
<p style="margin-bottom: 15px; color: #666; font-size: 14px;">
|
||||
<?php echo __('test_connection_with_libretranslate'); ?> (<?php echo htmlspecialchars($libreTranslateUrl); ?>).
|
||||
</p>
|
||||
|
||||
<div class="test-area">
|
||||
<textarea id="sourceText" placeholder="<?php echo __('type_something_to_translate'); ?>"></textarea>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display:block; margin-bottom:5px; font-weight:600;"><?php echo __('translate_to'); ?></label>
|
||||
<select id="targetLang" style="width:100%; padding:8px; border-radius:5px; border:1px solid #ddd;">
|
||||
<?php foreach ($idiomas as $lang): ?>
|
||||
<?php if ($lang['activo']): ?>
|
||||
<option value="<?php echo $lang['codigo']; ?>">
|
||||
<?php echo htmlspecialchars($lang['nombre']); ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onclick="testTranslation()" class="btn btn-primary" style="width: 100%;">
|
||||
<i class="fas fa-magic"></i> <?php echo __('translate'); ?>
|
||||
</button>
|
||||
|
||||
<div id="translationResult" class="test-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Selección de Bandera -->
|
||||
<div id="flagModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-modal" onclick="closeFlagModal()">×</span>
|
||||
<h2 style="margin-bottom: 15px;"><?php echo __('select_flag'); ?></h2>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
|
||||
<input type="text" id="customFlag" placeholder="<?php echo __('paste_emoji_here'); ?>" class="form-control" style="flex: 1; padding: 10px; border: 2px solid #eee; border-radius: 8px; font-size: 18px;">
|
||||
<button onclick="saveCustomFlag()" class="btn btn-primary"><?php echo __('save'); ?></button>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; font-size: 14px;"><?php echo __('or_select_common_one'); ?></p>
|
||||
<div class="flag-grid">
|
||||
<!-- Banderas comunes -->
|
||||
<div class="flag-option" onclick="selectFlag('🇺🇸')">🇺🇸</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇬🇧')">🇬🇧</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇪🇸')">🇪🇸</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇲🇽')">🇲🇽</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇫🇷')">🇫🇷</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇩🇪')">🇩🇪</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇮🇹')">🇮🇹</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇵🇹')">🇵🇹</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇧🇷')">🇧🇷</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇷🇺')">🇷🇺</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇨🇳')">🇨🇳</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇯🇵')">🇯🇵</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇰🇷')">🇰🇷</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇮🇳')">🇮🇳</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇸🇦')">🇸🇦</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇹🇷')">🇹🇷</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇳🇱')">🇳🇱</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇵🇱')">🇵🇱</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇸🇪')">🇸🇪</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇺🇦')">🇺🇦</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇨🇦')">🇨🇦</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇦🇺')">🇦🇺</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇦🇷')">🇦🇷</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇨🇴')">🇨🇴</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇨🇱')">🇨🇱</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇵🇪')">🇵🇪</div>
|
||||
<div class="flag-option" onclick="selectFlag('🇻🇪')">🇻🇪</div>
|
||||
<div class="flag-option" onclick="selectFlag('🌍')">🌍</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentLangId = null;
|
||||
|
||||
function openFlagModal(id, currentFlag) {
|
||||
currentLangId = id;
|
||||
document.getElementById('customFlag').value = currentFlag;
|
||||
document.getElementById('flagModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeFlagModal() {
|
||||
document.getElementById('flagModal').style.display = 'none';
|
||||
currentLangId = null;
|
||||
}
|
||||
|
||||
function selectFlag(flag) {
|
||||
document.getElementById('customFlag').value = flag;
|
||||
saveCustomFlag();
|
||||
}
|
||||
|
||||
async function saveCustomFlag() {
|
||||
const flag = document.getElementById('customFlag').value;
|
||||
if (!currentLangId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
action: 'update_flag',
|
||||
id: currentLangId,
|
||||
flag: flag
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
showNotification("<?php echo __('error'); ?>" + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showNotification("<?php echo __('connection_error'); ?>", 'error');
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target == document.getElementById('flagModal')) {
|
||||
closeFlagModal();
|
||||
}
|
||||
}
|
||||
async function syncLanguages() {
|
||||
if (!confirm("<?php echo __('confirm_sync'); ?>")) return;
|
||||
|
||||
const btn = document.querySelector('.btn-success');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?php echo __('syncing'); ?>';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
action: 'sync'
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showNotification(result.message, 'success');
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
showNotification("<?php echo __('sync_error'); ?>" + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showNotification("<?php echo __('connection_error'); ?>", 'error');
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleLanguage(id) {
|
||||
try {
|
||||
const response = await fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
action: 'toggle',
|
||||
id: id
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Recargar para actualizar UI visualmente (badges)
|
||||
location.reload();
|
||||
} else {
|
||||
showNotification("<?php echo __('language_update_error'); ?>: " + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showNotification("<?php echo __('connection_error'); ?>", 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testTranslation() {
|
||||
const text = document.getElementById('sourceText').value;
|
||||
const target = document.getElementById('targetLang').value;
|
||||
const resultDiv = document.getElementById('translationResult');
|
||||
|
||||
if (!text) return;
|
||||
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?php echo __('translating'); ?>';
|
||||
|
||||
try {
|
||||
// Usar el endpoint de LibreTranslate directamente o a través de un proxy PHP si hay CORS
|
||||
// Por ahora intentamos directo, si falla implementaremos proxy
|
||||
const response = await fetch('<?php echo $libreTranslateUrl; ?>/translate', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
q: text,
|
||||
source: 'auto',
|
||||
target: target,
|
||||
format: 'text'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.translatedText) {
|
||||
resultDiv.innerHTML = '<strong><?php echo __('result'); ?></strong><br>' + data.translatedText;
|
||||
} else {
|
||||
resultDiv.innerHTML = `<span style="color:red"><?php echo __('error'); ?>${data.error || 'Desconocido'}</span>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
resultDiv.innerHTML = `<span style="color:red"><?php echo __('libretranslate_connection_error'); ?></span>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
72
shared/public/js/notifications.js
Executable file
72
shared/public/js/notifications.js
Executable file
@@ -0,0 +1,72 @@
|
||||
// shared/public/js/notifications.js
|
||||
|
||||
/**
|
||||
* Muestra una notificación tipo "toast".
|
||||
* @param {string} message El mensaje a mostrar.
|
||||
* @param {string} type El tipo de notificación ('success', 'error', 'info').
|
||||
* @param {number} duration La duración en milisegundos.
|
||||
*/
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
// Crear el contenedor de notificaciones si no existe
|
||||
let container = document.getElementById('notification-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'notification-container';
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Añadir estilos al head
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
#notification-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.toast-notification {
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
color: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
}
|
||||
.toast-notification.show {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
.toast-notification.success { background-color: #28a745; }
|
||||
.toast-notification.error { background-color: #dc3545; }
|
||||
.toast-notification.info { background-color: #17a2b8; }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `toast-notification ${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
// Trigger the animation
|
||||
setTimeout(() => {
|
||||
notification.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// Hide and remove the notification after the duration
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
if (container.children.length === 0) {
|
||||
container.remove();
|
||||
}
|
||||
}, 500); // Wait for fade out animation
|
||||
}, duration);
|
||||
}
|
||||
75
shared/translations/en.json
Executable file
75
shared/translations/en.json
Executable file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"main_panel_title": "Main Dashboard - Bot System",
|
||||
"bot_admin_system_title": "Bot Administration System",
|
||||
"languages": "Languages",
|
||||
"logout": "Logout",
|
||||
"discord": "Discord",
|
||||
"discord_description": "Manage your Discord bot, send messages, manage templates, and more",
|
||||
"users": "Users",
|
||||
"messages": "Messages",
|
||||
"templates": "Templates",
|
||||
"telegram": "Telegram",
|
||||
"telegram_description": "Manage your Telegram bot, send messages, manage templates, and more",
|
||||
"discord_dashboard_title": "Discord Dashboard - Bot System",
|
||||
"discord_dashboard_header": "Discord Dashboard",
|
||||
"back_to_main_dashboard": "Back to Main Dashboard",
|
||||
"templates_module_title": "Templates",
|
||||
"templates_module_description": "Manage message templates",
|
||||
"create_message_module_title": "Create Message",
|
||||
"create_message_module_description": "Send messages to Discord",
|
||||
"sent_messages_module_title": "Sent Messages",
|
||||
"sent_messages_module_description": "Message history",
|
||||
"recipients_module_title": "Recipients",
|
||||
"recipients_module_description": "Manage users and channels",
|
||||
"commands_module_title": "Commands",
|
||||
"commands_module_description": "View executed commands",
|
||||
"welcome_message_module_title": "Welcome Message",
|
||||
"welcome_message_module_description": "Configure welcome message",
|
||||
"system_logs_module_title": "System Logs",
|
||||
"system_logs_module_description": "History of errors and events",
|
||||
"gallery_module_title": "Gallery",
|
||||
"gallery_module_description": "Manage images",
|
||||
"languages_module_title": "Languages",
|
||||
"languages_module_description": "Manage translations",
|
||||
"connection_test_module_title": "Connection Test",
|
||||
"connection_test_module_description": "Test connection with Discord",
|
||||
"telegram_dashboard_title": "Telegram Dashboard - Bot System",
|
||||
"telegram_dashboard_header": "Telegram Dashboard",
|
||||
"recipients_module_description_telegram": "Manage users and chats",
|
||||
"connection_test_module_description_telegram": "Test connection with Telegram",
|
||||
"language_manager_title": "Language Management - Bot System",
|
||||
"language_manager_header": "Language Management",
|
||||
"back_to_dashboard": "Back to Dashboard",
|
||||
"available_languages": "Available Languages",
|
||||
"sync_with_libretranslate": "Sync with LibreTranslate",
|
||||
"flag": "Flag",
|
||||
"code": "Code",
|
||||
"name": "Name",
|
||||
"native": "Native",
|
||||
"status": "Status",
|
||||
"action": "Action",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"translation_test": "Translation Test",
|
||||
"test_connection_with_libretranslate": "Test the connection with LibreTranslate",
|
||||
"translate_to": "Translate to:",
|
||||
"type_something_to_translate": "Type something to translate...",
|
||||
"translate": "Translate",
|
||||
"result": "Result:",
|
||||
"error": "Error: ",
|
||||
"libretranslate_connection_error": "Connection error with LibreTranslate. Verify that the service is running.",
|
||||
"select_flag": "Select Flag",
|
||||
"paste_emoji_here": "Paste an emoji here...",
|
||||
"save": "Save",
|
||||
"or_select_common_one": "Or select a common one:",
|
||||
"confirm_sync": "Sync languages with LibreTranslate? This will add new available languages.",
|
||||
"syncing": "Syncing...",
|
||||
"synced_languages": "Synced {count} languages",
|
||||
"sync_error": "Error while syncing: ",
|
||||
"connection_error": "Connection error",
|
||||
"confirm_toggle_language": "Activate/deactivate this language?",
|
||||
"language_updated": "Language updated",
|
||||
"language_update_error": "Error updating language",
|
||||
"translating": "Translating...",
|
||||
"json_parse_error": "Error parsing JSON"
|
||||
}
|
||||
75
shared/translations/es.json
Executable file
75
shared/translations/es.json
Executable file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"main_panel_title": "Panel Principal - Sistema de Bots",
|
||||
"bot_admin_system_title": "Sistema de Administración de Bots",
|
||||
"languages": "Idiomas",
|
||||
"logout": "Cerrar Sesión",
|
||||
"discord": "Discord",
|
||||
"discord_description": "Administra tu bot de Discord, envía mensajes, gestiona plantillas y más",
|
||||
"users": "Usuarios",
|
||||
"messages": "Mensajes",
|
||||
"templates": "Plantillas",
|
||||
"telegram": "Telegram",
|
||||
"telegram_description": "Administra tu bot de Telegram, envía mensajes, gestiona plantillas y más",
|
||||
"discord_dashboard_title": "Dashboard Discord - Sistema de Bots",
|
||||
"discord_dashboard_header": "Dashboard Discord",
|
||||
"back_to_main_dashboard": "Volver al Panel Principal",
|
||||
"templates_module_title": "Plantillas",
|
||||
"templates_module_description": "Gestionar plantillas de mensajes",
|
||||
"create_message_module_title": "Crear Mensaje",
|
||||
"create_message_module_description": "Enviar mensajes a Discord",
|
||||
"sent_messages_module_title": "Mensajes Enviados",
|
||||
"sent_messages_module_description": "Historial de mensajes",
|
||||
"recipients_module_title": "Destinatarios",
|
||||
"recipients_module_description": "Gestionar usuarios y canales",
|
||||
"commands_module_title": "Comandos",
|
||||
"commands_module_description": "Ver comandos ejecutados",
|
||||
"welcome_message_module_title": "Mensaje de Bienvenida",
|
||||
"welcome_message_module_description": "Configurar mensaje de bienvenida",
|
||||
"system_logs_module_title": "Logs del Sistema",
|
||||
"system_logs_module_description": "Historial de errores y eventos",
|
||||
"gallery_module_title": "Galería",
|
||||
"gallery_module_description": "Gestionar imágenes",
|
||||
"languages_module_title": "Idiomas",
|
||||
"languages_module_description": "Gestionar traducciones",
|
||||
"connection_test_module_title": "Test de Conexión",
|
||||
"connection_test_module_description": "Probar conexión con Discord",
|
||||
"telegram_dashboard_title": "Dashboard Telegram - Sistema de Bots",
|
||||
"telegram_dashboard_header": "Dashboard Telegram",
|
||||
"recipients_module_description_telegram": "Gestionar usuarios y chats",
|
||||
"connection_test_module_description_telegram": "Probar conexión con Telegram",
|
||||
"language_manager_title": "Gestión de Idiomas - Sistema de Bots",
|
||||
"language_manager_header": "Gestión de Idiomas",
|
||||
"back_to_dashboard": "Volver al Panel",
|
||||
"available_languages": "Idiomas Disponibles",
|
||||
"sync_with_libretranslate": "Sincronizar con LibreTranslate",
|
||||
"flag": "Bandera",
|
||||
"code": "Código",
|
||||
"name": "Nombre",
|
||||
"native": "Nativo",
|
||||
"status": "Estado",
|
||||
"action": "Acción",
|
||||
"active": "Activo",
|
||||
"inactive": "Inactivo",
|
||||
"translation_test": "Prueba de Traducción",
|
||||
"test_connection_with_libretranslate": "Prueba la conexión con LibreTranslate",
|
||||
"translate_to": "Traducir a:",
|
||||
"type_something_to_translate": "Escribe algo para traducir...",
|
||||
"translate": "Traducir",
|
||||
"result": "Resultado:",
|
||||
"error": "Error: ",
|
||||
"libretranslate_connection_error": "Error de conexión con LibreTranslate. Verifica que el servicio esté corriendo.",
|
||||
"select_flag": "Seleccionar Bandera",
|
||||
"paste_emoji_here": "Pega un emoji aquí...",
|
||||
"save": "Guardar",
|
||||
"or_select_common_one": "O selecciona una común:",
|
||||
"confirm_sync": "¿Sincronizar idiomas con LibreTranslate? Esto agregará nuevos idiomas disponibles.",
|
||||
"syncing": "Sincronizando...",
|
||||
"synced_languages": "Sincronizados {count} idiomas",
|
||||
"sync_error": "Error al sincronizar: ",
|
||||
"connection_error": "Error de conexión",
|
||||
"confirm_toggle_language": "¿Activar/desactivar este idioma?",
|
||||
"language_updated": "Idioma actualizado",
|
||||
"language_update_error": "Error al actualizar idioma",
|
||||
"translating": "Traduciendo...",
|
||||
"json_parse_error": "Error al parsear JSON"
|
||||
}
|
||||
58
shared/translations/manager.php
Executable file
58
shared/translations/manager.php
Executable file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Translation Manager
|
||||
*/
|
||||
|
||||
class TranslationManager {
|
||||
private static $instance = null;
|
||||
private $translations = [];
|
||||
private $language = 'es'; // Idioma por defecto
|
||||
|
||||
private function __construct() {
|
||||
// Obtener el idioma del usuario desde el JWT
|
||||
if (class_exists('JWTAuth')) {
|
||||
$userData = JWTAuth::getUserData();
|
||||
if (isset($userData->idioma)) {
|
||||
$this->language = $userData->idioma;
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadTranslations();
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance == null) {
|
||||
self::$instance = new TranslationManager();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function loadTranslations() {
|
||||
$filePath = __DIR__ . '/' . $this->language . '.json';
|
||||
if (file_exists($filePath)) {
|
||||
$json = file_get_contents($filePath);
|
||||
$this->translations = json_decode($json, true);
|
||||
} else {
|
||||
// Si el archivo de idioma no existe, cargar español por defecto
|
||||
$defaultFilePath = __DIR__ . '/es.json';
|
||||
if (file_exists($defaultFilePath)) {
|
||||
$json = file_get_contents($defaultFilePath);
|
||||
$this->translations = json_decode($json, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get($key, $default = null) {
|
||||
return $this->translations[$key] ?? $default ?? $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper global para traducciones
|
||||
* @param string $key La clave de traducción.
|
||||
* @param string|null $default Un valor por defecto si la clave no se encuentra.
|
||||
* @return string
|
||||
*/
|
||||
function __($key, $default = null) {
|
||||
return TranslationManager::getInstance()->get($key, $default);
|
||||
}
|
||||
75
shared/translations/pt.json
Executable file
75
shared/translations/pt.json
Executable file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"main_panel_title": "Painel Principal - Sistema de Bots",
|
||||
"bot_admin_system_title": "Sistema de Administração de Bots",
|
||||
"languages": "Idiomas",
|
||||
"logout": "Sair",
|
||||
"discord": "Discord",
|
||||
"discord_description": "Gerencie seu bot do Discord, envie mensagens, gerencie templates e mais",
|
||||
"users": "Usuários",
|
||||
"messages": "Mensagens",
|
||||
"templates": "Templates",
|
||||
"telegram": "Telegram",
|
||||
"telegram_description": "Gerencie seu bot do Telegram, envie mensagens, gerencie templates e mais",
|
||||
"discord_dashboard_title": "Dashboard do Discord - Sistema de Bots",
|
||||
"discord_dashboard_header": "Dashboard do Discord",
|
||||
"back_to_main_dashboard": "Voltar ao Painel Principal",
|
||||
"templates_module_title": "Templates",
|
||||
"templates_module_description": "Gerenciar templates de mensagens",
|
||||
"create_message_module_title": "Criar Mensagem",
|
||||
"create_message_module_description": "Enviar mensagens para o Discord",
|
||||
"sent_messages_module_title": "Mensagens Enviadas",
|
||||
"sent_messages_module_description": "Histórico de mensagens",
|
||||
"recipients_module_title": "Destinatários",
|
||||
"recipients_module_description": "Gerenciar usuários e canais",
|
||||
"commands_module_title": "Comandos",
|
||||
"commands_module_description": "Ver comandos executados",
|
||||
"welcome_message_module_title": "Mensagem de Boas-Vindas",
|
||||
"welcome_message_module_description": "Configurar mensagem de boas-vindas",
|
||||
"system_logs_module_title": "Logs do Sistema",
|
||||
"system_logs_module_description": "Histórico de erros e eventos",
|
||||
"gallery_module_title": "Galeria",
|
||||
"gallery_module_description": "Gerenciar imagens",
|
||||
"languages_module_title": "Idiomas",
|
||||
"languages_module_description": "Gerenciar traduções",
|
||||
"connection_test_module_title": "Teste de Conexão",
|
||||
"connection_test_module_description": "Testar conexão com o Discord",
|
||||
"telegram_dashboard_title": "Dashboard do Telegram - Sistema de Bots",
|
||||
"telegram_dashboard_header": "Dashboard do Telegram",
|
||||
"recipients_module_description_telegram": "Gerenciar usuários e chats",
|
||||
"connection_test_module_description_telegram": "Testar conexão com o Telegram",
|
||||
"language_manager_title": "Gerenciamento de Idiomas - Sistema de Bots",
|
||||
"language_manager_header": "Gerenciamento de Idiomas",
|
||||
"back_to_dashboard": "Voltar ao Painel",
|
||||
"available_languages": "Idiomas Disponíveis",
|
||||
"sync_with_libretranslate": "Sincronizar com LibreTranslate",
|
||||
"flag": "Bandeira",
|
||||
"code": "Código",
|
||||
"name": "Nome",
|
||||
"native": "Nativo",
|
||||
"status": "Status",
|
||||
"action": "Ação",
|
||||
"active": "Ativo",
|
||||
"inactive": "Inativo",
|
||||
"translation_test": "Teste de Tradução",
|
||||
"test_connection_with_libretranslate": "Teste a conexão com o LibreTranslate",
|
||||
"translate_to": "Traduzir para:",
|
||||
"type_something_to_translate": "Digite algo para traduzir...",
|
||||
"translate": "Traduzir",
|
||||
"result": "Resultado:",
|
||||
"error": "Erro: ",
|
||||
"libretranslate_connection_error": "Erro de conexão com o LibreTranslate. Verifique se o serviço está em execução.",
|
||||
"select_flag": "Selecionar Bandeira",
|
||||
"paste_emoji_here": "Cole um emoji aqui...",
|
||||
"save": "Salvar",
|
||||
"or_select_common_one": "Ou selecione um comum:",
|
||||
"confirm_sync": "Sincronizar idiomas com o LibreTranslate? Isso adicionará novos idiomas disponíveis.",
|
||||
"syncing": "Sincronizando...",
|
||||
"synced_languages": "Sincronizados {count} idiomas",
|
||||
"sync_error": "Erro ao sincronizar: ",
|
||||
"connection_error": "Erro de conexão",
|
||||
"confirm_toggle_language": "Ativar/desativar este idioma?",
|
||||
"language_updated": "Idioma atualizado",
|
||||
"language_update_error": "Erro ao atualizar o idioma",
|
||||
"translating": "Traduzindo...",
|
||||
"json_parse_error": "Erro ao analisar JSON"
|
||||
}
|
||||
104
shared/translations/translate.php
Executable file
104
shared/translations/translate.php
Executable file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Habilitar logging para depuración
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
define('DEBUG_MODE', true); // Cambiar a false en producción
|
||||
|
||||
require_once __DIR__ . '/../../shared/utils/helpers.php';
|
||||
require_once __DIR__ . '/../../shared/auth/jwt.php';
|
||||
|
||||
// Verificar autenticación
|
||||
try {
|
||||
$userData = JWTAuth::authenticate();
|
||||
if (!$userData) {
|
||||
jsonResponse(['success' => false, 'error' => 'No autenticado'], 401);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
jsonResponse(['success' => false, 'error' => 'Error de autenticación: ' . $e->getMessage()], 401);
|
||||
}
|
||||
|
||||
// Verificar método
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'Método no permitido'], 405);
|
||||
}
|
||||
|
||||
// Obtener datos de la petición
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$text = $input['text'] ?? '';
|
||||
$targetLang = $input['target'] ?? '';
|
||||
$sourceLang = $input['source'] ?? 'es';
|
||||
|
||||
// Validar parámetros
|
||||
if (empty($text) || empty($targetLang)) {
|
||||
jsonResponse(['success' => false, 'error' => 'Texto o idioma de destino no especificado'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
// URL de LibreTranslate (ajusta según tu configuración)
|
||||
$ltUrl = getenv('LIBRETRANSLATE_URL') ?: 'http://10.10.4.17:5000';
|
||||
|
||||
// Configurar la petición a LibreTranslate
|
||||
$ch = curl_init("$ltUrl/translate");
|
||||
|
||||
$postData = [
|
||||
'q' => $text,
|
||||
'source' => $sourceLang,
|
||||
'target' => $targetLang,
|
||||
'format' => 'html', // Para mantener formato HTML si existe
|
||||
'api_key' => getenv('LIBRETRANSLATE_API_KEY') ?: ''
|
||||
];
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($postData),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
throw new Exception("Error en la petición: $error");
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if ($httpCode !== 200 || !isset($result['translatedText'])) {
|
||||
$errorMsg = $result['error'] ?? 'Error desconocido al traducir';
|
||||
throw new Exception("Error en la traducción: $errorMsg");
|
||||
}
|
||||
|
||||
// Devolver la traducción
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'translatedText' => $result['translatedText']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error en translate.php: " . $e->getMessage());
|
||||
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Error al procesar la traducción',
|
||||
'debug' => DEBUG_MODE ? $e->getMessage() : null
|
||||
], 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía una respuesta JSON y termina la ejecución
|
||||
*/
|
||||
function jsonResponse($data, $statusCode = 200) {
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
159
shared/utils/helpers.php
Executable file
159
shared/utils/helpers.php
Executable file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Archivo de configuración compartido
|
||||
* Carga variables de entorno y proporciona funciones helper
|
||||
*/
|
||||
|
||||
// Cargar variables de entorno si aún no están cargadas
|
||||
if (!isset($_ENV['DB_HOST'])) {
|
||||
if (file_exists(__DIR__ . '/../../.env')) {
|
||||
$lines = file(__DIR__ . '/../../.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) continue;
|
||||
if (strpos($line, '=') === false) continue;
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$_ENV[trim($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timezone
|
||||
date_default_timezone_set($_ENV['TIME_ZONE_ENVIOS'] ?? 'America/Mexico_City');
|
||||
|
||||
/**
|
||||
* Obtener variable de entorno
|
||||
*/
|
||||
function env($key, $default = null) {
|
||||
return $_ENV[$key] ?? getenv($key) ?: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registrar log en archivo
|
||||
*/
|
||||
function logToFile($filename, $message, $level = 'INFO') {
|
||||
$logDir = __DIR__ . '/../../logs/';
|
||||
$logFile = $logDir . $filename;
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logMessage = "[{$timestamp}] [{$level}] {$message}\n";
|
||||
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizar HTML para prevenir XSS
|
||||
*/
|
||||
function sanitizeHTML($html) {
|
||||
return htmlspecialchars($html, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar email
|
||||
*/
|
||||
function isValidEmail($email) {
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generar token aleatorio
|
||||
*/
|
||||
function generateRandomToken($length = 32) {
|
||||
return bin2hex(random_bytes($length / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatear bytes a tamaño legible
|
||||
*/
|
||||
function formatBytes($bytes, $precision = 2) {
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
|
||||
$bytes /= 1024;
|
||||
}
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Respuesta JSON
|
||||
*/
|
||||
function jsonResponse($data, $statusCode = 200) {
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el usuario autenticado tiene un permiso específico.
|
||||
* @param string $permissionName El nombre del permiso a verificar (ej. 'crear_plantillas', 'gestionar_usuarios').
|
||||
* @param string $platform El módulo al que aplica el permiso (ej. 'discord', 'telegram', 'global').
|
||||
* @return bool True si el usuario tiene el permiso, false en caso contrario.
|
||||
*/
|
||||
function hasPermission($permissionName, $platform = 'global') {
|
||||
$userData = JWTAuth::getUserData(); // Obtener datos del usuario logueado
|
||||
|
||||
// Si no hay datos de usuario o no está autenticado, no tiene permisos
|
||||
if (!$userData || !isset($userData->userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Los administradores tienen todos los permisos
|
||||
if (isset($userData->rol) && $userData->rol === 'Admin') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si el usuario no es Admin, verificar permisos específicos
|
||||
// Los permisos se cargan en el JWT al iniciar sesión
|
||||
if (isset($userData->permissions) && is_array($userData->permissions)) {
|
||||
foreach ($userData->permissions as $perm) {
|
||||
// Un permiso 'manage_templates' global debería dar acceso a 'manage_templates' en cualquier plataforma
|
||||
// Un permiso 'manage_templates_discord' solo en Discord
|
||||
if ($perm === $permissionName || $perm === $permissionName . '_' . $platform) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dividir texto en múltiples mensajes según límite de caracteres
|
||||
*/
|
||||
function splitMessage($text, $maxLength = 2000) {
|
||||
if (strlen($text) <= $maxLength) {
|
||||
return [$text];
|
||||
}
|
||||
|
||||
$messages = [];
|
||||
$lines = explode("\n", $text);
|
||||
$currentMessage = '';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strlen($currentMessage) + strlen($line) + 1 > $maxLength) {
|
||||
if ($currentMessage) {
|
||||
$messages[] = trim($currentMessage);
|
||||
$currentMessage = '';
|
||||
}
|
||||
|
||||
// Si una línea sola es muy larga, dividirla
|
||||
if (strlen($line) > $maxLength) {
|
||||
$chunks = str_split($line, $maxLength);
|
||||
foreach ($chunks as $chunk) {
|
||||
$messages[] = $chunk;
|
||||
}
|
||||
} else {
|
||||
$currentMessage = $line . "\n";
|
||||
}
|
||||
} else {
|
||||
$currentMessage .= $line . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentMessage) {
|
||||
$messages[] = trim($currentMessage);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
Reference in New Issue
Block a user