Files
Axia4/public_html/aulatek/comedor.php

570 lines
20 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once "_incl/auth_redir.php";
require_once "../_incl/tools.security.php";
require_once "../_incl/db.php";
$permissions = $_SESSION["auth_data"]["permissions"] ?? [];
if (!in_array("aulatek:docente", $permissions, true) && !in_array("entreaulas:docente", $permissions, true)) {
header("HTTP/1.1 403 Forbidden");
die("Access denied");
}
$aulario_id = safe_id_segment($_GET["aulario"] ?? "");
$tenant_data = $_SESSION["auth_data"]["aulatek"] ?? ($_SESSION["auth_data"]["entreaulas"] ?? []);
$centro_id = safe_organization_id($tenant_data["organizacion"] ?? ($tenant_data["centro"] ?? ""));
if ($aulario_id === "" || $centro_id === "") {
require_once "_incl/pre-body.php";
?>
<div class="card pad">
<h1>Menú del Comedor</h1>
<p>No se ha indicado un aulario válido.</p>
</div>
<?php
require_once "_incl/post-body.php";
exit;
}
$aulario = db_get_aulario($centro_id, $aulario_id);
// Check if this aulario shares comedor data from another aulario
$source_aulario_id = $aulario_id;
$is_shared = false;
if ($aulario && !empty($aulario["shared_comedor_from"])) {
$shared_from = safe_id_segment($aulario["shared_comedor_from"]);
$shared_aulario = db_get_aulario($centro_id, $shared_from);
if ($shared_aulario) {
$source_aulario_id = $shared_from;
$source_aulario_name = $shared_aulario["name"] ?? $shared_from;
$is_shared = true;
}
}
$defaultMenuTypes = [
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
["id" => "alergias", "label" => "Menú alergias", "color" => "#dc3545"],
];
$menuTypes = db_get_comedor_menu_types($centro_id, $source_aulario_id);
if (!is_array($menuTypes) || count($menuTypes) === 0) {
$menuTypes = $defaultMenuTypes;
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
}
$menuTypeIds = [];
foreach ($menuTypes as $t) {
if (!empty($t["id"])) {
$menuTypeIds[] = $t["id"];
}
}
$dateParam = $_GET["date"] ?? date("Y-m-d");
$dateObj = DateTime::createFromFormat("Y-m-d", $dateParam) ?: new DateTime();
$date = $dateObj->format("Y-m-d");
$menuTypeId = $_GET["menu"] ?? ($menuTypeIds[0] ?? "basal");
if (!in_array($menuTypeId, $menuTypeIds, true)) {
$menuTypeId = $menuTypeIds[0] ?? "basal";
}
$ym = $dateObj->format("Y-m");
$day = $dateObj->format("d");
function blank_menu()
{
return [
"plates" => [
"primero" => ["name" => "", "pictogram" => ""],
"segundo" => ["name" => "", "pictogram" => ""],
"postre" => ["name" => "", "pictogram" => ""],
]
];
}
$menuData = [
"date" => $date,
"menus" => []
];
$existing = db_get_comedor_entry($centro_id, $source_aulario_id, $ym, $day);
if (is_array($existing) && !empty($existing)) {
$menuData = array_merge($menuData, $existing);
}
if (!isset($menuData["menus"][$menuTypeId])) {
$menuData["menus"][$menuTypeId] = blank_menu();
}
$canEdit = in_array("sysadmin:access", $_SESSION["auth_data"]["permissions"] ?? []) && !$is_shared;
$saveNotice = "";
$uploadErrors = [];
function handle_image_upload($fieldName, $targetBaseName, $baseDir, &$uploadErrors)
{
if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]["error"] !== UPLOAD_ERR_OK) {
return null;
}
$ext = strtolower(pathinfo($_FILES[$fieldName]["name"], PATHINFO_EXTENSION));
$allowed = ["jpg", "jpeg", "png", "webp", "gif"];
// Validate by extension first
if (!in_array($ext, $allowed, true)) {
$uploadErrors[] = "El archivo " . htmlspecialchars($_FILES[$fieldName]["name"]) . " no es una imagen válida.";
return null;
}
// Also validate by MIME type / file contents to avoid spoofed extensions
$tmpPath = $_FILES[$fieldName]["tmp_name"];
$mimeType = null;
if (function_exists('finfo_open')) {
$finfo = @finfo_open(FILEINFO_MIME_TYPE);
if ($finfo !== false) {
$mime = @finfo_file($finfo, $tmpPath);
if ($mime !== false) {
$mimeType = $mime;
}
@finfo_close($finfo);
}
}
// Fallback: try exif_imagetype if available and finfo did not work
if ($mimeType === null && function_exists('exif_imagetype')) {
$type = @exif_imagetype($tmpPath);
if ($type !== false) {
switch ($type) {
case IMAGETYPE_JPEG:
$mimeType = 'image/jpeg';
break;
case IMAGETYPE_PNG:
$mimeType = 'image/png';
break;
case IMAGETYPE_GIF:
$mimeType = 'image/gif';
break;
case IMAGETYPE_WEBP:
$mimeType = 'image/webp';
break;
}
}
}
$allowedMime = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
];
if ($mimeType === null || !in_array($mimeType, $allowedMime, true)) {
$uploadErrors[] = "El archivo " . htmlspecialchars($_FILES[$fieldName]["name"]) . " no es una imagen válida.";
return null;
}
if (!is_dir($baseDir)) {
mkdir($baseDir, 0777, true);
}
$target = "$targetBaseName.$ext";
$targetPath = $baseDir . "/" . safe_filename($target);
if (move_uploaded_file($_FILES[$fieldName]["tmp_name"], $targetPath)) {
return basename($targetPath);
}
$uploadErrors[] = "No se pudo guardar " . htmlspecialchars($_FILES[$fieldName]["name"]) . ".";
return null;
}
if ($_SERVER["REQUEST_METHOD"] === "POST" && $canEdit) {
$action = $_POST["action"] ?? "";
if ($action === "add_type") {
$newId = safe_id_segment(strtolower(trim($_POST["new_type_id"] ?? "")));
$newLabel = trim($_POST["new_type_label"] ?? "");
$newColor = trim($_POST["new_type_color"] ?? "#0d6efd");
if ($newId !== "" && $newLabel !== "") {
$exists = false;
foreach ($menuTypes as $t) {
if (($t["id"] ?? "") === $newId) { $exists = true; break; }
}
if (!$exists) {
$menuTypes[] = ["id" => $newId, "label" => $newLabel, "color" => $newColor];
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($newId));
exit;
}
}
}
if ($action === "delete_type") {
$deleteId = safe_id_segment(trim($_POST["delete_type_id"] ?? ""));
if ($deleteId !== "") {
$newMenuTypes = array_values(array_filter($menuTypes, fn($t) => ($t["id"] ?? "") !== $deleteId));
if (count($newMenuTypes) !== count($menuTypes)) {
$menuTypes = $newMenuTypes;
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
$redirectMenuId = !empty($menuTypes) ? $menuTypes[0]["id"] : "basal";
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($redirectMenuId));
exit;
}
}
}
if ($action === "rename_type") {
$renameId = safe_id_segment(trim($_POST["rename_type_id"] ?? ""));
$newLabel = trim($_POST["rename_type_label"] ?? "");
$newColor = trim($_POST["rename_type_color"] ?? "");
if ($renameId !== "" && $newLabel !== "") {
foreach ($menuTypes as &$t) {
if (($t["id"] ?? "") === $renameId) {
$t["label"] = $newLabel;
if ($newColor !== "") { $t["color"] = $newColor; }
break;
}
}
unset($t);
db_set_comedor_menu_types($centro_id, $source_aulario_id, $menuTypes);
header("Location: /aulatek/comedor.php?aulario=" . urlencode($aulario_id) . "&date=" . urlencode($date) . "&menu=" . urlencode($renameId));
exit;
}
}
if ($action === "save") {
$menuTypeId = safe_id_segment($_POST["menu_type"] ?? $menuTypeId);
if (!isset($menuData["menus"][$menuTypeId])) {
$menuData["menus"][$menuTypeId] = blank_menu();
}
// Pictogram images still stored on filesystem in Comedor dir
$baseDir = aulatek_orgs_base_path() . "/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day";
$plates = ["primero", "segundo", "postre"];
foreach ($plates as $plate) {
$name = trim($_POST["name_" . $plate] ?? "");
$menuData["menus"][$menuTypeId]["plates"][$plate]["name"] = $name;
$pictUpload = handle_image_upload("pictogram_file_" . $plate, $menuTypeId . "_" . $plate . "_pict", $baseDir, $uploadErrors);
if ($pictUpload !== null) {
$menuData["menus"][$menuTypeId]["plates"][$plate]["pictogram"] = $pictUpload;
}
}
db_set_comedor_entry($centro_id, $source_aulario_id, $ym, $day, $menuData);
$saveNotice = "Menú guardado correctamente.";
}
}
$menuForType = $menuData["menus"][$menuTypeId] ?? blank_menu();
function image_src($value, $centro_id, $source_aulario_id, $date)
{
if (!$value) {
return "";
}
if (filter_var($value, FILTER_VALIDATE_URL)) {
return $value;
}
return "/aulatek/_filefetch.php?type=comedor_image&org=" . urlencode($centro_id) . "&aulario=" . urlencode($source_aulario_id) . "&date=" . urlencode($date) . "&file=" . urlencode($value);
}
$prevDate = (clone $dateObj)->modify("-1 day")->format("Y-m-d");
$nextDate = (clone $dateObj)->modify("+1 day")->format("Y-m-d");
$userAulas = $tenant_data["aulas"] ?? [];
$aulaOptions = [];
foreach ($userAulas as $aulaId) {
$aulaIdSafe = safe_id_segment($aulaId);
if ($aulaIdSafe === "") {
continue;
}
$aulaData = db_get_aulario($centro_id, $aulaIdSafe);
$aulaOptions[] = [
"id" => $aulaIdSafe,
"name" => $aulaData["name"] ?? $aulaIdSafe,
];
}
require_once "_incl/pre-body.php";
?>
<?php if ($is_shared): ?>
<div class="card pad" style="background: #cfe2ff; color: #084298;">
<strong> Datos compartidos:</strong> Este aulario está mostrando los menús del aulario <?= htmlspecialchars($source_aulario_name) ?>. Para editar, debes acceder al aulario origen o desactivar el acceso compartido en la configuración.
</div>
<?php endif; ?>
<?php if ($saveNotice !== ""): ?>
<div class="card pad" style="background: #d1e7dd; color: #0f5132;">
<?= htmlspecialchars($saveNotice) ?>
</div>
<?php endif; ?>
<?php if (count($uploadErrors) > 0): ?>
<div class="card pad" style="background: #f8d7da; color: #842029;">
<ul style="margin: 0;">
<?php foreach ($uploadErrors as $err): ?>
<li><?= $err ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<!-- Navigation Buttons - Single row -->
<div class="card pad">
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center; justify-content: center; flex-direction: row;">
<a class="btn btn-outline-dark" href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($prevDate) ?>&menu=<?= urlencode($menuTypeId) ?>">⟵ Día anterior</a>
<input type="date" id="datePicker" class="form-control form-control-lg" value="<?= htmlspecialchars($date) ?>" style="max-width: 200px;">
<a class="btn btn-outline-dark" href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($nextDate) ?>&menu=<?= urlencode($menuTypeId) ?>">Día siguiente ⟶</a>
</div>
<div style="margin-top: 10px; text-align: center;">
<label for="aularioPicker" class="form-label" style="margin-right: 10px;">Aulario:</label>
<select id="aularioPicker" class="form-select form-select-lg" style="max-width: 300px; display: inline-block;">
<?php foreach ($aulaOptions as $option):
$isSelected = ($option["id"] ?? "") === $aulario_id;
?>
<option value="<?= htmlspecialchars($option["id"] ?? "") ?>" <?= $isSelected ? "selected" : "" ?>>
<?= htmlspecialchars($option["name"] ?? $option["id"]) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="card pad">
<h2 style="margin-bottom: 10px;">Tipos de menú</h2>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<?php foreach ($menuTypes as $type):
$isActive = ($type["id"] ?? "") === $menuTypeId;
$color = $type["color"] ?? "#0d6efd";
?>
<a href="/aulatek/comedor.php?aulario=<?= urlencode($aulario_id) ?>&date=<?= urlencode($date) ?>&menu=<?= urlencode($type["id"]) ?>"
class="btn btn-lg" style="background: <?= htmlspecialchars($color) ?>; color: white; border: 3px solid <?= $isActive ? "#000" : "transparent" ?>;">
<?= htmlspecialchars($type["label"] ?? $type["id"]) ?>
</a>
<?php endforeach; ?>
</div>
</div>
<div class="menu-grid">
<?php
$plates = [
"primero" => "Primer plato",
"segundo" => "Segundo plato",
"postre" => "Postre"
];
foreach ($plates as $plateKey => $plateLabel):
$plate = $menuForType["plates"][$plateKey] ?? ["name" => "", "pictogram" => ""];
$pictSrc = image_src($plate["pictogram"] ?? "", $centro_id, $source_aulario_id, $date);
?>
<div class="card pad menu-card">
<h3 class="menu-title"><?= htmlspecialchars($plateLabel) ?></h3>
<div class="menu-images">
<div class="menu-img-block">
<div class="menu-img-label">Pictograma</div>
<?php if ($pictSrc !== ""): ?>
<img class="menu-img" src="<?= htmlspecialchars($pictSrc) ?>" alt="Pictograma de <?= htmlspecialchars($plateLabel) ?>">
<?php else: ?>
<div class="menu-placeholder">Sin pictograma</div>
<?php endif; ?>
</div>
</div>
<div class="menu-name">
<?= $plate["name"] !== "" ? htmlspecialchars($plate["name"]) : "Sin nombre" ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if ($canEdit): ?>
<details class="card pad" open>
<summary><strong>Editar menú</strong></summary>
<form method="post" enctype="multipart/form-data" style="margin-top: 10px;">
<input type="hidden" name="action" value="save">
<input type="hidden" name="menu_type" value="<?= htmlspecialchars($menuTypeId) ?>">
<div class="edit-grid">
<?php foreach ($plates as $plateKey => $plateLabel):
$plate = $menuForType["plates"][$plateKey] ?? ["name" => "", "pictogram" => ""];
?>
<div class="card pad" style="background: #f8f9fa;">
<h4><?= htmlspecialchars($plateLabel) ?></h4>
<label class="form-label">Nombre del plato</label>
<input type="text" class="form-control form-control-lg" name="name_<?= $plateKey ?>" value="<?= htmlspecialchars($plate["name"] ?? "") ?>" placeholder="Ej. Lentejas">
<label class="form-label" style="margin-top: 10px;">Pictograma (archivo)</label>
<input type="file" class="form-control form-control-lg" name="pictogram_file_<?= $plateKey ?>" accept="image/*">
</div>
<?php endforeach; ?>
</div>
<button type="submit" class="btn btn-success btn-lg" style="margin-top: 10px;">Guardar menú</button>
</form>
</details>
<details class="card pad">
<summary><strong>Administrar tipos de menú</strong></summary>
<!-- Add new menu type -->
<div style="margin-top: 15px; padding: 15px; background: #e7f1ff; border-radius: 8px;">
<h4 style="margin-bottom: 10px;">Añadir nuevo tipo de menú</h4>
<form method="post">
<input type="hidden" name="action" value="add_type">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label">ID</label>
<input type="text" name="new_type_id" class="form-control form-control-lg" placeholder="basal" required>
</div>
<div class="col-md-5">
<label class="form-label">Nombre</label>
<input type="text" name="new_type_label" class="form-control form-control-lg" placeholder="Menú basal" required>
</div>
<div class="col-md-3">
<label class="form-label">Color</label>
<input type="color" name="new_type_color" class="form-control form-control-lg" value="#0d6efd">
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg" style="margin-top: 10px;">Añadir tipo</button>
</form>
</div>
<!-- List existing menu types with edit/delete options -->
<div style="margin-top: 20px; padding: 15px; background: #fff3cd; border-radius: 8px;">
<h4 style="margin-bottom: 15px;">Tipos de menú existentes</h4>
<?php foreach ($menuTypes as $type): ?>
<div style="margin-bottom: 15px; padding: 12px; background: white; border-radius: 6px; border: 2px solid <?= htmlspecialchars($type["color"] ?? "#ccc") ?>;">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
<div style="flex: 1; min-width: 200px;">
<strong style="font-size: 1.2rem;"><?= htmlspecialchars($type["label"] ?? $type["id"]) ?></strong>
<br>
<small style="color: #666;">ID: <?= htmlspecialchars($type["id"] ?? "") ?></small>
<br>
<small style="color: #666;">Color: <span style="display: inline-block; width: 20px; height: 20px; background: <?= htmlspecialchars($type["color"] ?? "#ccc") ?>; border-radius: 3px; vertical-align: middle;"></span> <?= htmlspecialchars($type["color"] ?? "") ?></small>
</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<!-- Rename form -->
<button type="button" class="btn btn-warning" onclick="toggleRenameForm('<?= htmlspecialchars($type["id"] ?? "") ?>')">Renombrar</button>
<!-- Delete form -->
<form method="post" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este tipo de menú?');">
<input type="hidden" name="action" value="delete_type">
<input type="hidden" name="delete_type_id" value="<?= htmlspecialchars($type["id"] ?? "") ?>">
<button type="submit" class="btn btn-danger">Eliminar</button>
</form>
</div>
</div>
<!-- Rename form (hidden by default) -->
<div id="rename-form-<?= htmlspecialchars($type["id"] ?? "") ?>" style="display: none; margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
<form method="post">
<input type="hidden" name="action" value="rename_type">
<input type="hidden" name="rename_type_id" value="<?= htmlspecialchars($type["id"] ?? "") ?>">
<div class="row g-2">
<div class="col-md-8">
<label class="form-label">Nuevo nombre</label>
<input type="text" name="rename_type_label" class="form-control" value="<?= htmlspecialchars($type["label"] ?? "") ?>" required>
</div>
<div class="col-md-4">
<label class="form-label">Nuevo color</label>
<input type="color" name="rename_type_color" class="form-control" value="<?= htmlspecialchars($type["color"] ?? "#0d6efd") ?>">
</div>
</div>
<div style="margin-top: 8px;">
<button type="submit" class="btn btn-success">Guardar cambios</button>
<button type="button" class="btn btn-secondary" onclick="toggleRenameForm('<?= htmlspecialchars($type["id"] ?? "") ?>')">Cancelar</button>
</div>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
</details>
<script>
function toggleRenameForm(typeId) {
// Sanitize typeId to prevent potential XSS
const sanitizedId = typeId.replace(/[^a-zA-Z0-9_-]/g, '');
const formDiv = document.getElementById('rename-form-' + sanitizedId);
if (formDiv) {
formDiv.style.display = formDiv.style.display === 'none' ? 'block' : 'none';
}
}
</script>
<?php endif; ?>
<style>
.menu-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 12px;
}
.menu-card {
min-height: 320px;
}
.menu-title {
font-size: 1.6rem;
}
.menu-images {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
margin: 10px 0;
}
.menu-img-block {
text-align: center;
}
.menu-img-label {
font-weight: bold;
margin-bottom: 6px;
}
.menu-img {
max-width: 100%;
height: 140px;
object-fit: contain;
background: #fff;
border-radius: 12px;
padding: 6px;
border: 2px solid #ddd;
}
.menu-placeholder {
height: 140px;
display: flex;
align-items: center;
justify-content: center;
background: #f1f1f1;
border-radius: 12px;
border: 2px dashed #aaa;
color: #666;
font-weight: bold;
}
.menu-name {
font-size: 1.4rem;
font-weight: bold;
text-align: center;
padding: 6px;
background: #fff3cd;
border-radius: 8px;
}
.edit-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
}
.form-control-lg {
font-size: 1.1rem;
}
</style>
<script>
const datePicker = document.getElementById("datePicker");
const aularioPicker = document.getElementById("aularioPicker");
function goToSelection() {
const dateValue = datePicker ? datePicker.value : "";
const aularioValue = aularioPicker ? aularioPicker.value : "";
if (!dateValue || !aularioValue) return;
const params = new URLSearchParams(window.location.search);
params.set("date", dateValue);
params.set("aulario", aularioValue);
params.set("menu", "<?= htmlspecialchars($menuTypeId) ?>");
window.location.href = "/aulatek/comedor.php?" + params.toString();
}
if (datePicker) {
datePicker.addEventListener("change", goToSelection);
}
if (aularioPicker) {
aularioPicker.addEventListener("change", goToSelection);
}
</script>
<?php require_once "_incl/post-body.php"; ?>