Finished (for now) projects module, with secure PDF viewer.
This commit is contained in:
@@ -16,7 +16,7 @@ $aulario = json_decode(file_get_contents("/DATA/entreaulas/Centros/$centro_id/Au
|
|||||||
|
|
||||||
<div id="grid">
|
<div id="grid">
|
||||||
<a href="/entreaulas/paneldiario.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary grid-item">
|
<a href="/entreaulas/paneldiario.php?aulario=<?= urlencode($aulario_id) ?>" class="btn btn-primary grid-item">
|
||||||
<img src="/static/arasaac/pdi.png" height="125">
|
<img src="/static/arasaac/pdi.png" height="125" style="background: white; padding: 5px; border-radius: 10px;">
|
||||||
</br>
|
</br>
|
||||||
Panel Diario
|
Panel Diario
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -156,11 +156,26 @@ switch ($_GET["action"]) {
|
|||||||
$aulario_id = $_GET["aulario"] ?? "";
|
$aulario_id = $_GET["aulario"] ?? "";
|
||||||
$centro_id = $_SESSION["auth_data"]["entreaulas"]["centro"] ?? "";
|
$centro_id = $_SESSION["auth_data"]["entreaulas"]["centro"] ?? "";
|
||||||
|
|
||||||
|
$source_aulario_id = $aulario_id;
|
||||||
|
$is_shared = false;
|
||||||
|
if ($aulario_id !== "" && $centro_id !== "") {
|
||||||
|
$aulario_path = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id.json";
|
||||||
|
$aulario = file_exists($aulario_path) ? json_decode(file_get_contents($aulario_path), true) : null;
|
||||||
|
if ($aulario && !empty($aulario["shared_comedor_from"])) {
|
||||||
|
$shared_from = $aulario["shared_comedor_from"];
|
||||||
|
$shared_aulario_path = "/DATA/entreaulas/Centros/$centro_id/Aularios/$shared_from.json";
|
||||||
|
if (file_exists($shared_aulario_path)) {
|
||||||
|
$source_aulario_id = $shared_from;
|
||||||
|
$is_shared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$dateParam = $_GET["date"] ?? date("Y-m-d");
|
$dateParam = $_GET["date"] ?? date("Y-m-d");
|
||||||
$dateObj = DateTime::createFromFormat("Y-m-d", $dateParam) ?: new DateTime();
|
$dateObj = DateTime::createFromFormat("Y-m-d", $dateParam) ?: new DateTime();
|
||||||
$date = $dateObj->format("Y-m-d");
|
$date = $dateObj->format("Y-m-d");
|
||||||
|
|
||||||
$menuTypesPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Comedor-MenuTypes.json";
|
$menuTypesPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor-MenuTypes.json";
|
||||||
$defaultMenuTypes = [
|
$defaultMenuTypes = [
|
||||||
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
["id" => "basal", "label" => "Menú basal", "color" => "#0d6efd"],
|
||||||
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
|
["id" => "vegetariano", "label" => "Menú vegetariano", "color" => "#198754"],
|
||||||
@@ -184,7 +199,7 @@ switch ($_GET["action"]) {
|
|||||||
|
|
||||||
$ym = $dateObj->format("Y-m");
|
$ym = $dateObj->format("Y-m");
|
||||||
$day = $dateObj->format("d");
|
$day = $dateObj->format("d");
|
||||||
$dataPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$aulario_id/Comedor/$ym/$day/_datos.json";
|
$dataPath = "/DATA/entreaulas/Centros/$centro_id/Aularios/$source_aulario_id/Comedor/$ym/$day/_datos.json";
|
||||||
|
|
||||||
$menuData = [
|
$menuData = [
|
||||||
"date" => $date,
|
"date" => $date,
|
||||||
@@ -208,19 +223,11 @@ switch ($_GET["action"]) {
|
|||||||
?>
|
?>
|
||||||
<script>
|
<script>
|
||||||
function seleccionarMenuTipo(element, hasData) {
|
function seleccionarMenuTipo(element, hasData) {
|
||||||
if (hasData) {
|
|
||||||
element.style.backgroundColor = "#9cff9f"; // Verde
|
element.style.backgroundColor = "#9cff9f"; // Verde
|
||||||
document.getElementById('win-sound').play();
|
document.getElementById('win-sound').play();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "/entreaulas/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>";
|
window.location.href = "/entreaulas/paneldiario.php?aulario=<?php echo urlencode($_GET['aulario'] ?? ''); ?>";
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
|
||||||
element.style.backgroundColor = "#ff9088"; // Rojo
|
|
||||||
document.getElementById('lose-sound').play();
|
|
||||||
setTimeout(() => {
|
|
||||||
element.style.backgroundColor = "";
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class="card pad">
|
<div class="card pad">
|
||||||
@@ -259,18 +266,23 @@ switch ($_GET["action"]) {
|
|||||||
<div class="menu-placeholder">Menú no disponible</div>
|
<div class="menu-placeholder">Menú no disponible</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="menu-lines">
|
<div class="menu-lines">
|
||||||
<?php foreach ($plates as $plateKey => $plateLabel):
|
<?php
|
||||||
|
$loop = 0;
|
||||||
|
foreach ($plates as $plateKey => $plateLabel):
|
||||||
|
$loop ++;
|
||||||
$plate = $menuItem["plates"][$plateKey] ?? ["name" => "", "pictogram" => "", "photo" => ""];
|
$plate = $menuItem["plates"][$plateKey] ?? ["name" => "", "pictogram" => "", "photo" => ""];
|
||||||
$pictSrc = image_src_simple($plate["pictogram"] ?? "", $centro_id, $aulario_id, $date);
|
$pictSrc = image_src_simple($plate["pictogram"] ?? "", $centro_id, $source_aulario_id, $date);
|
||||||
?>
|
?>
|
||||||
<div class="menu-line">
|
<div class="menu-line">
|
||||||
<?php if ($pictSrc !== ""): ?>
|
<?php if ($pictSrc !== ""): ?>
|
||||||
<img class="menu-line-img" src="<?= htmlspecialchars($pictSrc) ?>" alt="<?= htmlspecialchars($plateLabel) ?>">
|
<img class="menu-line-img" src="<?= htmlspecialchars($pictSrc) ?>" alt="<?= htmlspecialchars($plateLabel) ?>">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="menu-line-img placeholder">—</div>
|
<div class="menu-line-img placeholder">
|
||||||
|
<!-- Nª Plato --><?= $loop ?>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="menu-line-name">
|
<div class="menu-line-name">
|
||||||
<?= $plate["name"] !== "" ? htmlspecialchars($plate["name"]) : "Sin nombre" ?>
|
<?= $plate["name"] !== "" ? htmlspecialchars($plate["name"]) : "?" ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -296,10 +308,11 @@ switch ($_GET["action"]) {
|
|||||||
.menu-lines {
|
.menu-lines {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
}
|
}
|
||||||
.menu-line {
|
.menu-line {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px 2fr;
|
grid-template-columns: 100px 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -325,7 +338,8 @@ switch ($_GET["action"]) {
|
|||||||
.menu-line-img.placeholder {
|
.menu-line-img.placeholder {
|
||||||
background: #f1f1f1;
|
background: #f1f1f1;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
color: #777;
|
color: #333;
|
||||||
|
font-size: 5rem;
|
||||||
}
|
}
|
||||||
.menu-line-name {
|
.menu-line-name {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
|||||||
286
public_html/entreaulas/pdf_secure_viewer.php
Normal file
286
public_html/entreaulas/pdf_secure_viewer.php
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<?php
|
||||||
|
require_once "_incl/auth_redir.php";
|
||||||
|
|
||||||
|
$file = $_GET["file"] ?? "";
|
||||||
|
$file = trim($file);
|
||||||
|
|
||||||
|
$is_valid = false;
|
||||||
|
if ($file !== "") {
|
||||||
|
$parsed = parse_url($file);
|
||||||
|
if (!isset($parsed["scheme"]) && !isset($parsed["host"])) {
|
||||||
|
if (strpos($file, "/entreaulas/_filefetch.php") === 0) {
|
||||||
|
$is_valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$is_valid) {
|
||||||
|
header("HTTP/1.1 400 Bad Request");
|
||||||
|
echo "URL de archivo no válida.";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>PDF Seguro</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #222;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.viewer-header {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #fff3cd;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
color: #000;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.viewer-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
.viewer-toolbar button {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.viewer-toolbar button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.zoom-label {
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 64px;
|
||||||
|
}
|
||||||
|
.pdf-container {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
padding: 16px;
|
||||||
|
color: #b00020;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="viewer-header">
|
||||||
|
Este PDF se muestra en modo seguro. No se permite la descarga, impresión o copia del contenido.
|
||||||
|
</div>
|
||||||
|
<div class="viewer-toolbar">
|
||||||
|
<button type="button" id="zoom_out">−</button>
|
||||||
|
<button type="button" id="zoom_reset">100%</button>
|
||||||
|
<button type="button" id="zoom_in">+</button>
|
||||||
|
<span class="zoom-label" id="zoom_label">100%</span>
|
||||||
|
</div>
|
||||||
|
<div class="pdf-container" id="pdf_container">
|
||||||
|
Cargando PDF...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var pdfUrl = <?= json_encode($file, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
var container = document.getElementById('pdf_container');
|
||||||
|
var CDN_BASES = [
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174',
|
||||||
|
'https://unpkg.com/pdfjs-dist@3.11.174/build'
|
||||||
|
];
|
||||||
|
|
||||||
|
function loadScript(src, onload, onerror) {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.src = src;
|
||||||
|
s.onload = onload;
|
||||||
|
s.onerror = onerror;
|
||||||
|
document.head.appendChild(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pdfDoc = null;
|
||||||
|
var currentScale = 1;
|
||||||
|
var minScale = 0.5;
|
||||||
|
var maxScale = 3;
|
||||||
|
var renderToken = 0;
|
||||||
|
|
||||||
|
function updateZoomLabel() {
|
||||||
|
var label = document.getElementById('zoom_label');
|
||||||
|
if (label) {
|
||||||
|
label.textContent = Math.round(currentScale * 100) + '%';
|
||||||
|
}
|
||||||
|
var resetBtn = document.getElementById('zoom_reset');
|
||||||
|
if (resetBtn) {
|
||||||
|
resetBtn.textContent = Math.round(currentScale * 100) + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearContainer() {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAllPages(scale) {
|
||||||
|
if (!pdfDoc) return;
|
||||||
|
renderToken += 1;
|
||||||
|
var token = renderToken;
|
||||||
|
clearContainer();
|
||||||
|
var total = pdfDoc.numPages;
|
||||||
|
for (var i = 1; i <= total; i++) {
|
||||||
|
(function (pageNumber) {
|
||||||
|
pdfDoc.getPage(pageNumber).then(function (page) {
|
||||||
|
if (token !== renderToken) return;
|
||||||
|
var viewport = page.getViewport({ scale: scale });
|
||||||
|
var outputScale = window.devicePixelRatio || 1;
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var context = canvas.getContext('2d');
|
||||||
|
canvas.width = Math.floor(viewport.width * outputScale);
|
||||||
|
canvas.height = Math.floor(viewport.height * outputScale);
|
||||||
|
canvas.style.width = Math.floor(viewport.width) + 'px';
|
||||||
|
canvas.style.height = 'auto';
|
||||||
|
|
||||||
|
var wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'page';
|
||||||
|
wrapper.appendChild(canvas);
|
||||||
|
container.appendChild(wrapper);
|
||||||
|
|
||||||
|
var renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: page.getViewport({ scale: scale * outputScale })
|
||||||
|
};
|
||||||
|
page.render(renderContext);
|
||||||
|
});
|
||||||
|
})(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFitScale() {
|
||||||
|
if (!pdfDoc) return currentScale;
|
||||||
|
var availableWidth = Math.max(320, container.clientWidth || 0);
|
||||||
|
return pdfDoc.getPage(1).then(function (page) {
|
||||||
|
var viewport = page.getViewport({ scale: 1 });
|
||||||
|
var baseWidth = viewport.width || 1;
|
||||||
|
var fitScale = availableWidth / baseWidth;
|
||||||
|
fitScale = Math.min(maxScale, Math.max(minScale, fitScale));
|
||||||
|
return fitScale;
|
||||||
|
}).catch(function () {
|
||||||
|
return currentScale;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPdf() {
|
||||||
|
if (!window.pdfjsLib) {
|
||||||
|
container.innerHTML = '<div class="error">No se pudo cargar el visor PDF.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfjsLib.getDocument(pdfUrl).promise.then(function (pdf) {
|
||||||
|
pdfDoc = pdf;
|
||||||
|
return getFitScale();
|
||||||
|
}).then(function (fitScale) {
|
||||||
|
currentScale = fitScale || 1;
|
||||||
|
updateZoomLabel();
|
||||||
|
renderAllPages(currentScale);
|
||||||
|
}).catch(function () {
|
||||||
|
container.innerHTML = '<div class="error">No se pudo cargar el PDF.</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryLoad(index) {
|
||||||
|
if (index >= CDN_BASES.length) {
|
||||||
|
container.innerHTML = '<div class="error">No se pudo cargar el visor PDF.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var base = CDN_BASES[index];
|
||||||
|
var scriptUrl = base + '/pdf.min.js';
|
||||||
|
var workerUrl = base + '/pdf.worker.min.js';
|
||||||
|
loadScript(scriptUrl, function () {
|
||||||
|
if (window.pdfjsLib) {
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
|
||||||
|
}
|
||||||
|
initPdf();
|
||||||
|
}, function () {
|
||||||
|
tryLoad(index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tryLoad(0);
|
||||||
|
|
||||||
|
var zoomInBtn = document.getElementById('zoom_in');
|
||||||
|
var zoomOutBtn = document.getElementById('zoom_out');
|
||||||
|
var zoomResetBtn = document.getElementById('zoom_reset');
|
||||||
|
|
||||||
|
if (zoomInBtn) {
|
||||||
|
zoomInBtn.addEventListener('click', function () {
|
||||||
|
currentScale = Math.min(maxScale, currentScale + 0.1);
|
||||||
|
updateZoomLabel();
|
||||||
|
renderAllPages(currentScale);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (zoomOutBtn) {
|
||||||
|
zoomOutBtn.addEventListener('click', function () {
|
||||||
|
currentScale = Math.max(minScale, currentScale - 0.1);
|
||||||
|
updateZoomLabel();
|
||||||
|
renderAllPages(currentScale);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (zoomResetBtn) {
|
||||||
|
zoomResetBtn.addEventListener('click', function () {
|
||||||
|
getFitScale().then(function (fitScale) {
|
||||||
|
currentScale = fitScale || 1;
|
||||||
|
updateZoomLabel();
|
||||||
|
renderAllPages(currentScale);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var resizeTimer = null;
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
if (resizeTimer) clearTimeout(resizeTimer);
|
||||||
|
resizeTimer = setTimeout(function () {
|
||||||
|
getFitScale().then(function (fitScale) {
|
||||||
|
currentScale = fitScale || currentScale;
|
||||||
|
updateZoomLabel();
|
||||||
|
renderAllPages(currentScale);
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,12 @@ switch ($_GET['action'] ?? '') {
|
|||||||
Docente
|
Docente
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete">
|
||||||
|
<label class="form-check-label" for="entreaulas-proyectos-delete">
|
||||||
|
Eliminar Proyectos
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,6 +158,12 @@ switch ($_GET['action'] ?? '') {
|
|||||||
Docente
|
Docente
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="permissions[]" value="entreaulas:proyectos:delete" id="entreaulas-proyectos-delete" <?php if (in_array('entreaulas:proyectos:delete', $userdata['permissions'] ?? [])) echo 'checked'; ?>>
|
||||||
|
<label class="form-check-label" for="entreaulas-proyectos-delete">
|
||||||
|
Eliminar Proyectos
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user