Changes & added Club

This commit is contained in:
naielv
2026-01-26 02:56:57 +01:00
parent 5f45017997
commit 03f6e566fa
18 changed files with 585 additions and 21 deletions

View File

@@ -1,21 +1,14 @@
# Use official PHP image with Apache
FROM php:8.2-apache
# Use FrankenPHP (Caddy + PHP)
FROM dunglas/frankenphp
# Install system dependencies
RUN apt-get update && apt-get install -y \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
zip \
unzip \
&& rm -rf /var/lib/apt/lists/*
# # Install system dependencies
# RUN apt-get update && apt-get install -y \
# zip \
# unzip \
# && rm -rf /var/lib/apt/lists/*
# Configure PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd
# Enable Apache modules
RUN a2enmod rewrite
RUN install-php-extensions gd
# Set working directory
WORKDIR /var/www/html
@@ -23,6 +16,9 @@ WORKDIR /var/www/html
# Copy application files
COPY public_html/ /var/www/html/
# Copy FrankenPHP (Caddy) configuration
COPY docker/Caddyfile /etc/frankenphp/Caddyfile
# Create DATA directory with proper permissions
RUN mkdir -p /DATA && \
chown -R www-data:www-data /DATA && \
@@ -44,6 +40,3 @@ RUN echo "session.cookie_lifetime = 604800" >> /usr/local/etc/php/conf.d/custom.
# Expose port 80
EXPOSE 80
# Start Apache
CMD ["apache2-foreground"]

View File

@@ -1,6 +1,9 @@
services:
axia4-web:
image: ghcr.io/axia4/axia4:main
build:
context: .
dockerfile: Dockerfile
container_name: axia4-app
ports:
- "882:80"
@@ -8,7 +11,7 @@ services:
# Mount the DATA directory for persistent storage
- ./DATA:/DATA
# Optional: Mount the application code for development
# - ./public_html:/var/www/html
- ./public_html:/var/www/html
environment:
- APACHE_DOCUMENT_ROOT=/var/www/html
restart: unless-stopped

27
docker/Caddyfile Normal file
View File

@@ -0,0 +1,27 @@
{
frankenphp
}
:80 {
root * /var/www/html
encode zstd gzip
route {
intercept {
@accel header X-Accel-Redirect *
handle_response @accel {
root * /DATA/
rewrite * {resp.header.X-Accel-Redirect}
method * GET
# Remove the X-Accel-Redirect header set by PHP for increased security
header -X-Accel-Redirect
file_server
}
}
php_server
}
}

View File

@@ -106,14 +106,14 @@ if (!isset($APP_CODE)) {
padding: 15px 25px;
}
.grid-item {
a.grid-item.button {
margin-bottom: 10px !important;
padding: 15px;
width: 250px;
text-align: center;
}
.grid-item img {
a.grid-item.button img {
margin: 0 auto;
height: 100px;
}

View File

@@ -0,0 +1,62 @@
<?php
function generatethumbnail($src, $dest, $targetWidth, $targetHeight) {
$sourceImage = null;
$info = getimagesize($src);
$mime = $info['mime'];
switch ($mime) {
case 'image/jpeg':
$sourceImage = imagecreatefromjpeg($src);
break;
case 'image/png':
$sourceImage = imagecreatefrompng($src);
break;
case 'image/gif':
$sourceImage = imagecreatefromgif($src);
break;
default:
return false;
}
if (file_exists($dest)) {
return false; // Thumbnail already exists
}
$width = imagesx($sourceImage);
$height = imagesy($sourceImage);
// If the target height is not set, calculate it to maintain aspect ratio
if ($targetHeight == 0) {
$targetHeight = (int)($targetWidth * $height / $width);
}
$thumb = imagecreatetruecolor($targetWidth, $targetHeight);
// Preserve transparency for PNG and GIF
if ($mime == 'image/png' || $mime == 'image/gif') {
imagecolortransparent($thumb, imagecolorallocatealpha($thumb, 0, 0, 0, 127));
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
}
// Resize and crop
$sourceAspect = $width / $height;
$thumbAspect = $targetWidth / $targetHeight;
if ($sourceAspect > $thumbAspect) {
// Source is wider
$newHeight = $targetHeight;
$newWidth = (int)($targetHeight * $sourceAspect);
} else {
// Source is taller or equal
$newWidth = $targetWidth;
$newHeight = (int)($targetWidth / $sourceAspect);
}
// Center the image
$xOffset = (int)(($newWidth - $targetWidth) / 2);
$yOffset = (int)(($newHeight - $targetHeight) / 2);
imagecopyresampled($thumb, $sourceImage, -$xOffset, -$yOffset, 0, 0, $newWidth, $newHeight, $width, $height);
// Save as jpg
imagejpeg($thumb, $dest, 85);
imagedestroy($sourceImage);
imagedestroy($thumb);
return true;
}

2
public_html/club/__menu.php Executable file
View File

@@ -0,0 +1,2 @@
<a href="/club/upload/" class="button pseudo">Subir fotos</a>
<!-- <a href="https://www.google.com/maps/d/u/0/embed?mid=1-iMb0N6ZhLDMu1ArK4fCVW_vVTA-giA&ehbc=2E312F&noprof=1" class="button pseudo">Lugares de interes</a> -->

68
public_html/club/cal.php Executable file
View File

@@ -0,0 +1,68 @@
<?php
ini_set("display_errors", 0);
$file = str_replace('/', '_', $_GET["f"]);
$date = implode("/", array_reverse(explode("-", $file)));
$val = json_decode(file_get_contents("/DATA/club/IMG/$file/data.json"), true);
$fotos = glob("/DATA/club/IMG/$file/*/");
$APP_CODE = "club";
$APP_NAME = "La web del Club<sup>3</sup>";
$APP_TITLE = "La web del Club";
require_once "../_incl/pre-body.php"; ?>
<div class="card pad">
<h1><?php echo $date; ?> - <?php echo $val["title"] ?: "Por definir"; ?></h1>
<span>
<a href="/club/" class="button">Volver a Inicio</a>
<a href="/club/edit_data.php?f=<?php echo $file; ?>" class="button">Cambiar datos</a>
<a href="/club/upload/index.php?f=<?php echo $file; ?>" class="button">Subir fotos</a>
<?php if (isset($val["mapa"]["url"]) and $val["mapa"]["url"] != ""): ?>
<a class="button" href="<?php echo $val["mapa"]["url"]; ?>" target="_blank">Abrir ruta interactiva</a>
<?php endif; ?>
</span>
<?php if (isset($val["mapa"]) and $val["mapa"] != ""): ?>
<h2>Ruta y estadísticas</h2>
<?php if (isset($val["mapa"]["route"]) and $val["mapa"]["route"] != ""): ?>
<img height="300" loading="lazy" src="foto_dl.php?f=<?php echo $file . "/" . $val["mapa"]["route"]; ?>" alt="">
<?php endif; ?>
<?php if (isset($val["mapa"]["stats"]) and $val["mapa"]["stats"] != ""): ?>
<img height="300" loading="lazy" src="foto_dl.php?f=<?php echo $file . "/" . $val["mapa"]["stats"]; ?>" alt="">
<?php endif; ?>
<?php endif; ?>
<h2>Fotos</h2>
<div id="grid">
<?php foreach ($fotos as $persona): ?>
<?php $pname = str_replace("/", "", str_replace("/DATA/club/IMG/$file/", "", $persona)); ?>
<?php foreach (preg_grep('/^([^.])/', scandir($persona)) as $foto): ?>
<?php if (is_dir($foto)) {
continue;
} ?>
<?php if (strtolower(pathinfo($foto, PATHINFO_EXTENSION)) == "thumbnail") {
continue;
} ?>
<div style="width: 240px; display: inline-block; margin-bottom: 10px; border: 3px solid black; border-radius: 6.5px;"
class="grid-item">
<?php $dl_url = "foto_dl.php?f=$file/$pname/" . str_replace($persona, "", $foto); ?>
<img class="stack" width="240px" loading="lazy" src="<?php echo $dl_url; ?>&thumbnail=1"
alt="Foto de <?php echo $pname . " - " . str_replace($persona, "", $foto); ?>">
<div style="padding: 5px; text-align: center;">
Subido por <?php echo $pname; ?><br>
<a href="<?php echo $dl_url; ?>" target="_blank" class="button">Abrir</a>
<a href="<?php echo $dl_url; ?>"
download="<?php echo "CLUB-NK5-$file-$pname-" . str_replace($persona, "", $foto); ?>"
class="button">Descargar</a>
</div>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<script>
var msnry = new Masonry('#grid', { "columnWidth": 240, "itemSelector": ".grid-item", "gutter": 10, "transitionDuration": 0 });
setInterval(() => {
msnry.layout()
}, 1000);
</script>
<?php require_once "../_incl/post-body.php"; ?>

56
public_html/club/edit_data.php Executable file
View File

@@ -0,0 +1,56 @@
<?php
ini_set("display_errors", 0);
$file = str_replace('/', '_', $_GET["f"]);
$date = implode("/", array_reverse(explode("-", $file)));
$val = json_decode(file_get_contents("/DATA/club/IMG/$file/data.json"), true);
$config = json_decode(file_get_contents("/DATA/club/config.json"), true);
if(strtoupper($_POST["adminpw"]) == strtoupper($config["adminpw"] ?? "")) {
$data = [
"title" => $_POST["title"],
"note" => $_POST["note"],
"mapa" => [
"url" => $_POST["mapa_url"]
]
];
$file = $_POST["date"];
$val = file_put_contents("/DATA/club/IMG/$file/data.json", json_encode($data, JSON_UNESCAPED_SLASHES));
header("Location: /club/");
die();
}
$APP_CODE = "club";
$APP_NAME = "La web del Club<sup>3</sup>";
$APP_TITLE = "La web del Club";
require_once "../_incl/pre-body.php"; ?>
<div class="card pad">
<h1>Editar datos</h1>
<form method="post">
<fieldset class="card" style="border: 2px solid black; border-radius: 6.5px; padding: 5px 25px; max-width: 500px;">
<label>
<b>Contraseña de administración:</b><br>
<input required type="text" name="adminpw" placeholder="Contraseña admin">
</label><br><br>
<label>
<b>Fecha:</b><br>
<input required type="date" name="date" value="<?php echo $file;?>" placeholder="Fecha">
</label><br><br><hr>
<label>
<b>Titulo:</b><br>
<input required type="text" name="title" value="<?php echo $val["title"] ?: "";?>" placeholder="Titulo">
</label><br><br>
<label>
<b>Descripción:</b><br>
<textarea rows="5" name="note" placeholder="Descripción"><?php echo $val["note"] ?: "";?></textarea>
</label><br><br>
<label>
<b>Enlace ruta mapa:</b><br>
<input type="url" name="mapa_url" value="<?php echo $val["mapa"]["url"] ?: "";?>" placeholder="Enlace Mapa">
</label><br><br>
<button type="submit">Guardar cambios</button>
<br><br>
</fieldset>
</form>
</div>
<?php require_once "../_incl/post-body.php"; ?>

28
public_html/club/foto_dl.php Executable file
View File

@@ -0,0 +1,28 @@
<?php
ini_set("display_errors", 0);
ob_implicit_flush(true);
ob_end_flush();
ini_set('memory_limit', '1G');
$file = str_replace('..', '_', $_GET["f"]);
header("Access-Control-Allow-Origin: *");
$path = "/DATA/club/IMG/$file";
$uripath = "/club/IMG/$file";
if (!file_exists($path) || !is_file($path)) {
header("HTTP/1.1 404 Not Found");
die("File not found");
}
if (file_exists($path . ".thumbnail") && $_GET["thumbnail"] == "1") {
$path .= ".thumbnail";
$uripath .= ".thumbnail";
// die();
}
header("Content-Type: " . mime_content_type($path));
header('Content-Length: ' . filesize($path));
header('Cache-Control: max-age=7200');
header("X-Accel-Redirect: $uripath");
// // stream the file
// $fp = fopen($path, 'rb');
// fpassthru($fp);
exit;

25
public_html/club/index.php Executable file
View File

@@ -0,0 +1,25 @@
<?php
ini_set("display_errors", 0);
$files = glob("/DATA/club/IMG/*/");
sort($files);
$files = array_reverse($files);
$APP_CODE = "club";
$APP_NAME = "La web del Club<sup>3</sup>";
$APP_TITLE = "La web del Club";
require_once "../_incl/pre-body.php"; ?>
<div class="card pad">
<h2>Calendario:</h2>
<ul>
<?php foreach ($files as $file) {
$filenam = str_replace("/", "", str_replace("/DATA/club/IMG/", "", $file));
$date = implode("/", array_reverse(explode("-", $filenam)));
$val = json_decode(file_get_contents($file . "data.json"), true)
?>
<li><a href="cal.php?f=<?php echo $filenam; ?>"><b><?php echo $date; ?></b></a> -
<?php echo $val["title"] ?: "Por nombrar"; ?>
</li>
<?php } ?>
</ul>
</div>
<?php require_once "../_incl/post-body.php"; ?>

View File

@@ -0,0 +1,52 @@
div#alert {
font-family: Arial, Helvetica, serif;
font-weight: bold;
margin: 20px 0px;
}
div#alert.green {
color: green;
}
div#alert.red {
color: red;
}
div#progress-outer {
width: 315px;
height: 30px;
border: 1px solid white;
border-radius: 30px 30px 30px 30px;
-moz-border-radius: 30px;
-webkit-border-radius: 30px;
padding: 5px;
position: relative;
margin-top: 30px;
overflow: hidden;
display: none;
}
div#progress-inner {
width: 0%;
height: 100%;
background-color: white;
border-radius: 30px 30px 30px 30px;
-moz-border-radius: 30px;
-webkit-border-radius: 30px;
transition: 0.25s all ease-in-out;
-moz-transition: 0.25s all ease-in-out;
-webkit-transition: 0.25s all ease-in-out;
}
div#progress-text {
width: 100%;
font-family: Arial, Helvetica, serif;
font-weight: bold;
font-size: 18px;
color: green;
text-align: center;
position: absolute;
top: 10px;
left: 0px;
z-index: 2;
}

View File

@@ -0,0 +1,36 @@
<?php
ini_set("display_errors", 0);
if (strtoupper($_GET["p"]) != "ELPEPE") {
header("Location: unauth.php");
die();
}
$APP_CODE = "club";
$APP_NAME = "La web del Club<sup>3</sup>";
$APP_TITLE = "La web del Club";
require_once "/var/www/_incl/pre-body.php"; ?>
<div class="card pad">
<h1>Subir fotos</h1>
<form id="upload" encType="multipart/form-data">
<fieldset class="card"
style="border: 2px solid black; border-radius: 6.5px; padding: 5px 25px; max-width: 500px;">
<label id="uploader0">
<b>Elegir los archivos a subir (max 10)</b>
<input type="file" name="file[]" multiple="true" id="uploaderfileinp" />
</label>
<hr>
<span>
<b>Progreso:</b> <span id="alert">Por subir...</span>
</span>
<progress id="fileuploaderprog" max="100" value="0" style="width: 100%;">0%</progress>
<br>
<button type="submit" class="button">Subir fotos</button>
</fieldset>
</form>
<script src="js/plugin/jquery-3.6.0.min.js"></script>
<script src="js/main.js"></script>
</div>
<?php require_once "/var/www/_incl/post-body.php"; ?>

View File

@@ -0,0 +1,30 @@
<?php ini_set("display_errors", "off");
$APP_CODE = "club";
$APP_NAME = "La web del Club<sup>3</sup>";
$APP_TITLE = "La web del Club";
require_once "/var/www/_incl/pre-body.php"; ?>
<div class="card pad">
<h1>Subir fotos</h1>
<form action="form.php" method="get">
<fieldset class="card" style="border: 2px solid black; border-radius: 6.5px; padding: 5px 25px; max-width: 500px;">
<label>
<b>Tu nombre:</b>
<input required type="text" name="n" value="<?php echo $_GET["n"] ?: "";?>" placeholder="Nombre...">
</label>
<br>
<label>
<b>Fecha:</b>
<input required type="date" name="f" value="<?php echo $_GET["f"] ?: "";?>" placeholder="Fecha...">
</label>
<br>
<label>
<b>La contraseña:</b>
<input required type="text" name="p" value="" placeholder="Contraseña...">
</label>
<br>
<button type="submit">Continuar...</button>
</fieldset>
</form>
</div>
<?php require_once "/var/www/_incl/post-body.php"; ?>

View File

@@ -0,0 +1,61 @@
var urlParams = new URLSearchParams(window.location.search);
$(document).ready(function() {
// on form submit, upload file:
$("form#upload").submit(function(event) {
event.preventDefault();
$.ajax({
type: "POST",
url: "upload.php?pw=" + urlParams.get("p") + "&folder=/IMG/" + urlParams.get("f") + "/" + urlParams.get("n") + "/",
data: new FormData($(this)[0]),
cache: false,
contentType: false,
processData: false,
xhr: function() {
var my_xhr = $.ajaxSettings.xhr();
document.getElementById('alert').innerText = "Subiendo...";
if (my_xhr.upload) {
my_xhr.upload.addEventListener("progress", function(event) {
Progress(event.loaded, event.total);
});
}
return my_xhr;
},
success: function() {
document.getElementById('alert').innerText = "Subido!";
Finished();
},
error: function(xhr, status, message) {
document.getElementById('alert').innerText = "Error! " + xhr.status + " " + status + " - " + message;
}
});
});
});
// progress bar:
function Progress(current, total) {
var percent = ((current / total) * 100).toFixed(0) + "%";
document.getElementById('fileuploaderprog').value = ((current / total) * 100).toFixed(0);
document.getElementById('fileuploaderprog').innerText = percent;
}
// upload finished:
function Finished() {
setTimeout(function() {
$("form#upload input[type='file']").val("");
document.getElementById('uploaderfileinp').value = "";
document.getElementById('fileuploaderprog').innerText = "Subido!"
setTimeout(function() {
document.getElementById('fileuploaderprog').innerText = "0%"
location.href="../cal.php?f=" + urlParams.get("f")
}, 3000);
}, 500);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
<?php ini_set("display_errors", "off");?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>La web del club</title>
<link rel="stylesheet" href="/newcss-terminal.css">
</head>
<body>
<header>
<h1>La web del Club</h1>
<a href="/club/">[ Inicio ]</a>
<a href="/">[ Volver a tech.eus ]</a>
</header>
<h1>Subir fotos</h1>
<h2>La contraseña es incorrecta.</h2>
</body>
</html>

View File

@@ -0,0 +1,43 @@
<?php
ini_set("display_errors", 0);
$config = json_decode(file_get_contents("/DATA/club/config.json"), true);
if (strtoupper($_GET["pw"]) != $config["uploadpw"]) {
header("HTTP/1.1 401 Unauthorized");
die();
}
//remove files with error
$error_files = array();
foreach ($_FILES["file"]["error"] as $key => $error) {
if ($error != UPLOAD_ERR_OK) {
$error_files[] = $_FILES["file"]["name"][$key];
}
}
foreach ($error_files as $file) {
$key = array_search($file, $_FILES["file"]["name"]);
unset($_FILES["file"]["name"][$key]);
unset($_FILES["file"]["type"][$key]);
unset($_FILES["file"]["tmp_name"][$key]);
unset($_FILES["file"]["error"][$key]);
unset($_FILES["file"]["size"][$key]);
}
$file_count = sizeof($_FILES["file"]["name"]);
for ($i = 0; $i < $file_count; $i++) {
$file_name = $_FILES["file"]["name"][$i];
$folder = $_GET["folder"];
$location = "/DATA/club$folder" . $file_name;
if (!is_dir("/DATA/club$folder")) {
mkdir("/DATA/club$folder", recursive: true);
}
if (move_uploaded_file($_FILES["file"]["tmp_name"][$i], $location)) {
// Generate thumbnail
require_once "../_incl/tools.photos.php";
$thumbnail_path = $location . ".thumbnail";
if (!file_exists($thumbnail_path)) {
generatethumbnail($location, $thumbnail_path, 240, 0);
}
header("HTTP/1.1 200 OK");
} else {
header("HTTP/1.1 500 Internal Server Error");
}
}

View File

@@ -0,0 +1,54 @@
<?php
require_once "_incl/auth_redir.php";
require_once "_incl/pre-body.php";
require_once "../_incl/tools.photos.php";
switch ($_GET["action"]) {
case "generate_thumbs":
ini_set("max_execution_time", 300);
ini_set("memory_limit", "1024M");
// ini_set("display_errors", 1);
echo "<div class='card pad'><h1>Generando Miniaturas...</h1>";
// Iterate over all club photos and generate thumbnails if they don't exist
$club_cal_folders = array_filter(glob("/DATA/club/IMG/*"), 'is_dir');
foreach ($club_cal_folders as $cal_folder) {
$personas = array_filter(glob("$cal_folder/*"), 'is_dir');
foreach ($personas as $persona) {
$fotos = preg_grep('/^([^.])/', scandir($persona));
foreach ($fotos as $foto) {
$foto_path = "$persona/$foto";
$thumbnail_path = "$foto_path.thumbnail";
if (file_exists($thumbnail_path)) {
continue;
}
// Extension is not thumbnail
if (strtolower(pathinfo($foto_path, PATHINFO_EXTENSION)) == "thumbnail") {
continue;
}
generatethumbnail($foto_path, $thumbnail_path, 240, 0);
echo "Generated thumbnail for $foto_path<br>";
flush();
}
}
}
echo "<h2>Proceso completado.</h2></div>";
break;
case "index":
default:
?>
<div class="card pad">
<h1>Generar Miniaturas para Fotos del Club</h1>
<span>
Desde esta sección puedes generar miniaturas para las fotos subidas al Club que aún no las tengan.
</span>
<form method="get" action="">
<input type="hidden" name="action" value="generate_thumbs">
<button type="submit">Generar Miniaturas</button>
</form>
</div>
<?php
break;
}
require_once "_incl/post-body.php"; ?>