Primer commit del sistema separado falta mejorar mucho
This commit is contained in:
105
gallery/api/delete.php
Executable file
105
gallery/api/delete.php
Executable 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
67
gallery/api/edit.php
Executable 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
109
gallery/api/list.php
Executable 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
24
gallery/api/php_errors.log
Executable 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
237
gallery/api/upload.php
Executable 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);
|
||||
}
|
||||
411
gallery/index.php
Executable file
411
gallery/index.php
Executable file
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// 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/auth/jwt.php';
|
||||
$userData = JWTAuth::requireAuth();
|
||||
?>
|
||||
<!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>Galería de Imágenes - Sistema de Bots</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #5865F2;
|
||||
--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: var(--primary-color); font-size: 24px; }
|
||||
|
||||
.container { max-width: 1400px; margin: 0 auto; }
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-primary { background: var(--primary-color); color: white; }
|
||||
.btn-secondary { background: #6c757d; color: white; }
|
||||
.btn-success { background: var(--success-color); color: white; }
|
||||
.btn-danger { background: var(--danger-color); color: white; }
|
||||
|
||||
.upload-area {
|
||||
border: 3px dashed #ddd;
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: var(--primary-color);
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.gallery-item {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.gallery-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.gallery-item img {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.gallery-item-info {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.gallery-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.gallery-item-meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gallery-item-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.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: 500px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #eee;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1><i class="fas fa-images"></i> Galería de Imágenes</h1>
|
||||
<a href="/index.php" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="upload-area" onclick="document.getElementById('fileInput').click()">
|
||||
<i class="fas fa-cloud-upload-alt" style="font-size: 48px; color: var(--primary-color); margin-bottom: 15px;"></i>
|
||||
<h3>Arrastra imágenes aquí o haz clic para seleccionar</h3>
|
||||
<p style="color: #666; margin-top: 10px;">Formatos permitidos: JPG, PNG, GIF, WEBP (Máx. 5MB)</p>
|
||||
</div>
|
||||
<input type="file" id="fileInput" accept="image/*" multiple style="display: none;">
|
||||
|
||||
<div id="uploadProgress" style="display: none; margin-top: 20px;">
|
||||
<div style="background: #f8f9fa; padding: 10px; border-radius: 8px;">
|
||||
<div id="progressText">Subiendo...</div>
|
||||
<div style="background: #ddd; height: 20px; border-radius: 10px; margin-top: 10px; overflow: hidden;">
|
||||
<div id="progressBar" style="background: var(--success-color); height: 100%; width: 0%; transition: width 0.3s;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0;">Mis Imágenes</h2>
|
||||
<input type="text" id="searchInput" placeholder="Buscar..." class="form-control" style="max-width: 300px; margin: 0;">
|
||||
</div>
|
||||
|
||||
<div id="galleryContainer" class="gallery-grid">
|
||||
<p style="text-align: center; color: #666;">Cargando...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Editar -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Editar Imagen</h2>
|
||||
<input type="text" id="editName" class="form-control" placeholder="Nombre de la imagen">
|
||||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||||
<button class="btn btn-success" onclick="saveEdit()">Guardar</button>
|
||||
<button class="btn btn-secondary" onclick="closeEditModal()">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentEditId = null;
|
||||
|
||||
// Subir archivos
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const uploadArea = document.querySelector('.upload-area');
|
||||
|
||||
fileInput.addEventListener('change', handleFiles);
|
||||
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.borderColor = 'var(--primary-color)';
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.style.borderColor = '#ddd';
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.borderColor = '#ddd';
|
||||
fileInput.files = e.dataTransfer.files;
|
||||
handleFiles();
|
||||
});
|
||||
|
||||
async function handleFiles() {
|
||||
const files = fileInput.files;
|
||||
if (!files.length) return;
|
||||
|
||||
const progressDiv = document.getElementById('uploadProgress');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
progressDiv.style.display = 'block';
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
progressText.textContent = `Subiendo ${i + 1}/${files.length}: ${file.name}`;
|
||||
progressBar.style.width = ((i / files.length) * 100) + '%';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/gallery/api/upload.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
alert('Error subiendo ' + file.name + ': ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error subiendo ' + file.name);
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.style.width = '100%';
|
||||
progressText.textContent = '¡Completado!';
|
||||
|
||||
setTimeout(() => {
|
||||
progressDiv.style.display = 'none';
|
||||
loadGallery();
|
||||
fileInput.value = '';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Cargar galería
|
||||
async function loadGallery(search = '') {
|
||||
const container = document.getElementById('galleryContainer');
|
||||
container.innerHTML = '<p style="text-align: center; color: #666;">Cargando...</p>';
|
||||
|
||||
try {
|
||||
const url = '/gallery/api/list.php' + (search ? `?search=${encodeURIComponent(search)}` : '');
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success || !data.images.length) {
|
||||
container.innerHTML = '<p style="text-align: center; color: #666;">No hay imágenes</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
data.images.forEach(img => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'gallery-item';
|
||||
div.innerHTML = `
|
||||
<img src="${img.url_thumbnail}" alt="${img.nombre_original}">
|
||||
<div class="gallery-item-info">
|
||||
<div class="gallery-item-name" title="${img.nombre_original}">${img.nombre_original}</div>
|
||||
<div class="gallery-item-meta">
|
||||
${Math.round(img.tamano / 1024)} KB • ${img.ancho}x${img.alto}
|
||||
</div>
|
||||
<div class="gallery-item-actions">
|
||||
<button class="btn btn-primary" style="font-size: 11px; padding: 5px 10px;" onclick="openEditModal(${img.id}, '${img.nombre_original.replace(/'/g, "\\'")}')">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" style="font-size: 11px; padding: 5px 10px;" onclick="deleteImage(${img.id})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
} catch (error) {
|
||||
container.innerHTML = '<p style="text-align: center; color: red;">Error cargando imágenes</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function openEditModal(id, name) {
|
||||
currentEditId = id;
|
||||
document.getElementById('editName').value = name;
|
||||
document.getElementById('editModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
document.getElementById('editModal').style.display = 'none';
|
||||
currentEditId = null;
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
const newName = document.getElementById('editName').value;
|
||||
if (!newName) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/gallery/api/edit.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
id: currentEditId,
|
||||
nombre_original: newName
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
closeEditModal();
|
||||
loadGallery();
|
||||
} else {
|
||||
alert('Error: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error de conexión');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteImage(id) {
|
||||
if (!confirm('¿Eliminar esta imagen?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/gallery/api/delete.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({id: id})
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
loadGallery();
|
||||
} else {
|
||||
alert('Error: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error de conexión');
|
||||
}
|
||||
}
|
||||
|
||||
// Buscador
|
||||
let searchTimeout;
|
||||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
loadGallery(e.target.value);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Cargar al inicio
|
||||
loadGallery();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
gallery/thumbnails/1764412975_c67c86ed901f1e25.webp
Executable file
BIN
gallery/thumbnails/1764412975_c67c86ed901f1e25.webp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
gallery/uploads/1764412975_c67c86ed901f1e25.webp
Executable file
BIN
gallery/uploads/1764412975_c67c86ed901f1e25.webp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Reference in New Issue
Block a user