Primer commit del sistema separado falta mejorar mucho

This commit is contained in:
nickpons666
2025-12-30 01:18:46 -06:00
commit 1679c73e52
2384 changed files with 472342 additions and 0 deletions

105
gallery/api/delete.php Executable file
View File

@@ -0,0 +1,105 @@
<?php
/**
* API de Galería - Eliminar imagen
*/
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__ . '/../../shared/database/connection.php';
require_once __DIR__ . '/../../shared/auth/jwt.php';
// Verificar autenticación
if (!isAuthenticated()) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'No autenticado']);
exit;
}
// Verificar permiso
if (!hasPermission('delete_gallery_images')) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'No tienes permiso para eliminar imágenes de la galería.']);
exit;
}
// Solo admins pueden eliminar
if ($userData->rol !== 'Admin' && !JWTAuth::hasPermission($userData, 'gestionar_galeria')) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'No tiene permisos para eliminar imágenes']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE' && $_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['id'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Falta el ID de la imagen']);
exit;
}
try {
$db = getDB();
// Buscar la imagen
$stmt = $db->prepare("SELECT * FROM gallery WHERE id = ?");
$stmt->execute([$input['id']]);
$image = $stmt->fetch();
if (!$image) {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'Imagen no encontrada']);
exit;
}
// Verificar que no esté siendo usada en mensajes de bienvenida
$stmt = $db->query("SELECT COUNT(*) as total FROM bienvenida_discord WHERE imagen_id = " . $input['id']);
$usedInDiscord = $stmt->fetch()['total'];
$stmt = $db->query("SELECT COUNT(*) as total FROM bienvenida_telegram WHERE imagen_id = " . $input['id']);
$usedInTelegram = $stmt->fetch()['total'];
if ($usedInDiscord > 0 || $usedInTelegram > 0) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'No se puede eliminar. La imagen está siendo usada en mensajes de bienvenida']);
exit;
}
// Eliminar archivos físicos
$uploadPath = __DIR__ . '/../uploads/' . $image['nombre'];
$thumbnailPath = __DIR__ . '/../thumbnails/' . $image['nombre'];
if (file_exists($uploadPath)) {
unlink($uploadPath);
}
if (file_exists($thumbnailPath)) {
unlink($thumbnailPath);
}
// Eliminar de la base de datos
$stmt = $db->prepare("DELETE FROM gallery WHERE id = ?");
$stmt->execute([$input['id']]);
echo json_encode(['success' => true, 'message' => 'Imagen eliminada correctamente']);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error del servidor']);
error_log('Error en delete.php: ' . $e->getMessage());
}

67
gallery/api/edit.php Executable file
View File

@@ -0,0 +1,67 @@
<?php
/**
* API de Galería - Editar nombre de imagen
*/
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__ . '/../../shared/database/connection.php';
require_once __DIR__ . '/../../shared/auth/jwt.php';
// Verificar autenticación
if (!isAuthenticated()) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'No autenticado']);
exit;
}
// Verificar permiso
if (!hasPermission('edit_gallery_images')) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'No tienes permiso para editar imágenes de la galería.']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'PUT' && $_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['id']) || !isset($input['nombre_original'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Faltan parámetros']);
exit;
}
try {
$db = getDB();
$stmt = $db->prepare("UPDATE gallery SET nombre_original = ? WHERE id = ?");
$stmt->execute([$input['nombre_original'], $input['id']]);
if ($stmt->rowCount() > 0) {
echo json_encode(['success' => true, 'message' => 'Nombre actualizado correctamente']);
} else {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'Imagen no encontrada']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error del servidor']);
error_log('Error en edit.php: ' . $e->getMessage());
}

109
gallery/api/list.php Executable file
View File

@@ -0,0 +1,109 @@
<?php
/**
* API de Galería - Listar imágenes
*/
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__ . '/../../shared/database/connection.php';
require_once __DIR__ . '/../../shared/auth/jwt.php';
// Verificar autenticación
$userData = JWTAuth::authenticate();
if (!$userData) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'No autenticado']);
exit;
}
// Permitir acceso a cualquier usuario autenticado
// Opcional: Restringir si es necesario, pero por ahora es compartido
// if (!hasPermission('editar_plantillas') && !hasPermission('crear_mensajes')) { ... }
try {
$db = getDB();
// Parámetros de paginación
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$perPage = isset($_GET['per_page']) ? min(100, max(10, intval($_GET['per_page']))) : 20;
$offset = ($page - 1) * $perPage;
// Búsqueda
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
// Construir query
$where = [];
$params = [];
if ($search) {
$where[] = "(nombre_original LIKE ? OR nombre LIKE ?)";
$params[] = "%{$search}%";
$params[] = "%{$search}%";
}
$whereClause = $where ? 'WHERE ' . implode(' AND ', $where) : '';
// Contar total
$stmt = $db->prepare("SELECT COUNT(*) as total FROM gallery $whereClause");
$stmt->execute($params);
$total = $stmt->fetch()['total'];
// Obtener imágenes
$stmt = $db->prepare("
SELECT g.*, u.username
FROM gallery g
LEFT JOIN usuarios u ON g.usuario_id = u.id
$whereClause
ORDER BY g.fecha_subida DESC
LIMIT ? OFFSET ?
");
$params[] = $perPage;
$params[] = $offset;
$stmt->execute($params);
$images = $stmt->fetchAll();
// Formatear respuesta
$formattedImages = array_map(function($img) {
return [
'id' => $img['id'],
'nombre' => $img['nombre'],
'nombre_original' => $img['nombre_original'],
'url' => '/gallery/uploads/' . $img['nombre'],
'url_thumbnail' => '/gallery/thumbnails/' . $img['nombre'],
'tipo_mime' => $img['tipo_mime'],
'tamano' => $img['tamano'],
'ancho' => $img['ancho'],
'alto' => $img['alto'],
'usuario' => $img['username'],
'fecha_subida' => $img['fecha_subida']
];
}, $images);
echo json_encode([
'success' => true,
'images' => $formattedImages,
'pagination' => [
'page' => $page,
'per_page' => $perPage,
'total' => $total,
'total_pages' => ceil($total / $perPage)
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error del servidor']);
error_log('Error en list.php: ' . $e->getMessage());
}

24
gallery/api/php_errors.log Executable file
View File

@@ -0,0 +1,24 @@
[30-Nov-2025 06:47:39 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23
[30-Nov-2025 06:47:42 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23
[30-Nov-2025 06:47:50 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23
[30-Nov-2025 21:18:28 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23
[30-Nov-2025 21:19:22 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23
[30-Nov-2025 21:20:14 UTC] PHP Fatal error: Uncaught Error: Call to undefined function isAuthenticated() in /var/www/html/bot/gallery/api/list.php:23
Stack trace:
#0 {main}
thrown in /var/www/html/bot/gallery/api/list.php on line 23

237
gallery/api/upload.php Executable file
View File

@@ -0,0 +1,237 @@
<?php
/**
* API de Galería - Subida de imágenes
*/
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__ . '/../../shared/database/connection.php';
require_once __DIR__ . '/../../shared/auth/jwt.php';
// Verificar autenticación
$userData = JWTAuth::authenticate();
if (!$userData) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'No autenticado']);
exit;
}
// Permitir subida a cualquier usuario autenticado
// Opcional: Restringir si es necesario
// if (!hasPermission('editar_plantillas') && !hasPermission('crear_mensajes')) { ... }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
exit;
}
if (!isset($_FILES['image'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'No se envió ninguna imagen']);
exit;
}
$file = $_FILES['image'];
// Validar errores de subida
if ($file['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Error al subir el archivo']);
exit;
}
// Validar tipo de archivo
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $allowedTypes)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Tipo de archivo no permitido. Solo se aceptan JPG, PNG, GIF y WebP']);
exit;
}
// Validar tamaño (máximo 5MB)
if ($file['size'] > 5 * 1024 * 1024) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'El archivo es muy grande. Máximo 5MB']);
exit;
}
try {
$db = getDB();
// Calcular hash MD5 del archivo
$hash = md5_file($file['tmp_name']);
// Verificar si ya existe una imagen con el mismo hash
$stmt = $db->prepare("SELECT * FROM gallery WHERE hash_md5 = ?");
$stmt->execute([$hash]);
$existing = $stmt->fetch();
if ($existing) {
// La imagen ya existe, devolver la existente
echo json_encode([
'success' => true,
'duplicate' => true,
'image' => [
'id' => $existing['id'],
'nombre' => $existing['nombre'],
'ruta' => $existing['ruta'],
'ruta_thumbnail' => $existing['ruta_thumbnail'],
'url' => '/gallery/uploads/' . $existing['nombre'],
'url_thumbnail' => '/gallery/thumbnails/' . $existing['nombre']
]
]);
exit;
}
// Obtener dimensiones de la imagen
$imageInfo = getimagesize($file['tmp_name']);
$width = $imageInfo[0];
$height = $imageInfo[1];
// Generar nombre único para el archivo
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = time() . '_' . bin2hex(random_bytes(8)) . '.' . $extension;
// Rutas de destino
$uploadsDir = __DIR__ . '/../uploads/';
$thumbnailsDir = __DIR__ . '/../thumbnails/';
$uploadPath = $uploadsDir . $newName;
$thumbnailPath = $thumbnailsDir . $newName;
// Crear directorios si no existen
if (!is_dir($uploadsDir)) {
mkdir($uploadsDir, 0755, true);
}
if (!is_dir($thumbnailsDir)) {
mkdir($thumbnailsDir, 0755, true);
}
// Mover archivo subido
if (!move_uploaded_file($file['tmp_name'], $uploadPath)) {
throw new Exception('Error al guardar el archivo');
}
// Crear thumbnail
createThumbnail($uploadPath, $thumbnailPath, 300, 300);
// Guardar en la base de datos
$stmt = $db->prepare("
INSERT INTO gallery (nombre, nombre_original, ruta, ruta_thumbnail, hash_md5, tipo_mime, tamano, ancho, alto, usuario_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$newName,
$file['name'],
'uploads/' . $newName,
'thumbnails/' . $newName,
$hash,
$mimeType,
$file['size'],
$width,
$height,
$userData->userId
]);
$imageId = $db->lastInsertId();
echo json_encode([
'success' => true,
'duplicate' => false,
'image' => [
'id' => $imageId,
'nombre' => $newName,
'ruta' => 'uploads/' . $newName,
'ruta_thumbnail' => 'thumbnails/' . $newName,
'url' => '/gallery/uploads/' . $newName,
'url_thumbnail' => '/gallery/thumbnails/' . $newName
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error del servidor: ' . $e->getMessage()]);
error_log('Error en upload.php: ' . $e->getMessage());
}
/**
* Crear thumbnail de una imagen
*/
function createThumbnail($sourcePath, $destPath, $maxWidth, $maxHeight) {
$imageInfo = getimagesize($sourcePath);
$width = $imageInfo[0];
$height = $imageInfo[1];
$mimeType = $imageInfo['mime'];
// Cargar imagen según el tipo
switch ($mimeType) {
case 'image/jpeg':
$source = imagecreatefromjpeg($sourcePath);
break;
case 'image/png':
$source = imagecreatefrompng($sourcePath);
break;
case 'image/gif':
$source = imagecreatefromgif($sourcePath);
break;
case 'image/webp':
$source = imagecreatefromwebp($sourcePath);
break;
default:
throw new Exception('Tipo de imagen no soportado');
}
// Calcular nuevas dimensiones manteniendo el aspecto
$ratio = min($maxWidth / $width, $maxHeight / $height);
$newWidth = intval($width * $ratio);
$newHeight = intval($height * $ratio);
// Crear thumbnail
$thumbnail = imagecreatetruecolor($newWidth, $newHeight);
// Preservar transparencia para PNG y GIF
if ($mimeType === 'image/png' || $mimeType === 'image/gif') {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
$transparent = imagecolorallocatealpha($thumbnail, 255, 255, 255, 127);
imagefilledrectangle($thumbnail, 0, 0, $newWidth, $newHeight, $transparent);
}
// Redimensionar
imagecopyresampled($thumbnail, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
// Guardar thumbnail
switch ($mimeType) {
case 'image/jpeg':
imagejpeg($thumbnail, $destPath, 85);
break;
case 'image/png':
imagepng($thumbnail, $destPath, 8);
break;
case 'image/gif':
imagegif($thumbnail, $destPath);
break;
case 'image/webp':
imagewebp($thumbnail, $destPath, 85);
break;
}
imagedestroy($source);
imagedestroy($thumbnail);
}