Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
545d6e3
Initial plan
Copilot May 21, 2026
63a07d3
feat: add CSV import for tempos in admin panel
Copilot May 21, 2026
f6b35ff
fix: add csrf validation to tempos CSV import form
Copilot May 21, 2026
99cf827
revert: remove tempos CSV import changes
Copilot May 21, 2026
f93b8fc
feat: add CSV import for reservas em massa
Copilot May 21, 2026
9b07a15
fix: harden reserva CSV import validation flow
Copilot May 21, 2026
a332071
fix: improve CSV import header and error feedback
Copilot May 21, 2026
d711f8f
feat: add tabbed modal ID lookup for CSV imports
Copilot May 21, 2026
1c5228a
fix: build lookup API URLs with URLSearchParams
Copilot May 21, 2026
4cd4c38
fix: avoid origin-based URL construction in lookup fetch
Copilot May 21, 2026
3dfce7d
fix: optimize lookup API matching and error logging
Copilot May 21, 2026
d741a96
refactor: rename lookup html escape helper
Copilot May 21, 2026
2c8886e
fix: harden lookup fetch handling and simplify API filters
Copilot May 21, 2026
5c61670
fix: use relative lookup API endpoints in admin modal
Copilot May 21, 2026
45815ec
perf: defer modal lookup fetch until modal is opened
Copilot May 21, 2026
efbe4c5
fix: remover texto de (limitado a 10 para reduzir carga, texto gerado…
marpisco May 21, 2026
9035d51
fix: accept 4-column rows in reserva CSV import
Copilot May 21, 2026
7b54cbd
Changes before error encountered
Copilot May 21, 2026
74f95cc
perf: replace full-table loads with point-query validation in CSV import
marpisco May 22, 2026
c10a15a
perf: cap error message collection to display limit in CSV import
marpisco May 22, 2026
21f116d
fix: add file size limit and encoding detection to CSV import
marpisco May 22, 2026
25452cb
fix: add CSRF protection and file size limit to materiais CSV import
marpisco May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions admin/api/requisitor_lookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once(__DIR__ . '/../../src/db.php');
require_once(__DIR__ . '/../../func/validation.php');
if (session_status() === PHP_SESSION_NONE) { session_start(); }

if (!isset($_SESSION['admin']) || !$_SESSION['admin']) {
http_response_code(403);
echo json_encode(['error' => 'Acesso negado']);
exit;
}

$query = isset($_GET['q']) ? sanitize_input($_GET['q'], 100) : '';
$query = trim($query);

if (mb_strlen($query) < 2) {
echo json_encode(['items' => [], 'limit' => 10, 'requiresFilter' => true]);
$db->close();
exit;
}

$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $query);
$idPrefixParam = $escaped . '%';
$searchParam = '%' . $escaped . '%';
$limit = 10;

$stmt = $db->prepare("SELECT id, nome, email FROM cache WHERE id LIKE ? ESCAPE '\\\\' OR nome LIKE ? ESCAPE '\\\\' OR email LIKE ? ESCAPE '\\\\' ORDER BY nome ASC LIMIT ?");
$stmt->bind_param("sssi", $idPrefixParam, $searchParam, $searchParam, $limit);
$stmt->execute();
$result = $stmt->get_result();

$items = [];
while ($row = $result->fetch_assoc()) {
$items[] = [
'id' => $row['id'],
'title' => $row['nome'],
'subtitle' => $row['email']
];
}

$stmt->close();
$db->close();

echo json_encode(['items' => $items, 'limit' => $limit, 'requiresFilter' => false]);
?>
44 changes: 44 additions & 0 deletions admin/api/sala_lookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once(__DIR__ . '/../../src/db.php');
require_once(__DIR__ . '/../../func/validation.php');
if (session_status() === PHP_SESSION_NONE) { session_start(); }

if (!isset($_SESSION['admin']) || !$_SESSION['admin']) {
http_response_code(403);
echo json_encode(['error' => 'Acesso negado']);
exit;
}

$query = isset($_GET['q']) ? sanitize_input($_GET['q'], 100) : '';
$query = trim($query);

if (mb_strlen($query) < 2) {
echo json_encode(['items' => [], 'limit' => 10, 'requiresFilter' => true]);
$db->close();
exit;
}

$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $query);
$idPrefixParam = $escaped . '%';
$searchParam = '%' . $escaped . '%';
$limit = 10;

$stmt = $db->prepare("SELECT id, nome FROM salas WHERE id LIKE ? ESCAPE '\\\\' OR nome LIKE ? ESCAPE '\\\\' ORDER BY nome ASC LIMIT ?");
$stmt->bind_param("ssi", $idPrefixParam, $searchParam, $limit);
$stmt->execute();
$result = $stmt->get_result();

$items = [];
while ($row = $result->fetch_assoc()) {
$items[] = [
'id' => $row['id'],
'title' => $row['nome']
];
}

$stmt->close();
$db->close();

echo json_encode(['items' => $items, 'limit' => $limit, 'requiresFilter' => false]);
?>
44 changes: 44 additions & 0 deletions admin/api/tempo_lookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once(__DIR__ . '/../../src/db.php');
require_once(__DIR__ . '/../../func/validation.php');
if (session_status() === PHP_SESSION_NONE) { session_start(); }

if (!isset($_SESSION['admin']) || !$_SESSION['admin']) {
http_response_code(403);
echo json_encode(['error' => 'Acesso negado']);
exit;
}

$query = isset($_GET['q']) ? sanitize_input($_GET['q'], 100) : '';
$query = trim($query);

if (mb_strlen($query) < 2) {
echo json_encode(['items' => [], 'limit' => 10, 'requiresFilter' => true]);
$db->close();
exit;
}

$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $query);
$idPrefixParam = $escaped . '%';
$searchParam = '%' . $escaped . '%';
$limit = 10;

$stmt = $db->prepare("SELECT id, horashumanos FROM tempos WHERE id LIKE ? ESCAPE '\\\\' OR horashumanos LIKE ? ESCAPE '\\\\' ORDER BY horashumanos ASC LIMIT ?");
$stmt->bind_param("ssi", $idPrefixParam, $searchParam, $limit);
$stmt->execute();
$result = $stmt->get_result();

$items = [];
while ($row = $result->fetch_assoc()) {
$items[] = [
'id' => $row['id'],
'title' => $row['horashumanos']
];
}

$stmt->close();
$db->close();

echo json_encode(['items' => $items, 'limit' => $limit, 'requiresFilter' => false]);
?>
18 changes: 14 additions & 4 deletions admin/materiais.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<?php require 'index.php'; ?>
<?php require 'index.php';
require_once(__DIR__ . '/../func/csrf.php');
?>
<div style="margin-left: 10%; margin-right: 10%; text-align: center;">
<h3>Gestão de Materiais</h3>

<div class="mb-4">
<h5>Importar Materiais via CSV</h5>
<a href="/assets/csvsample.csv" download>Download do modelo CSV</a>
<a href="/assets/csvsample_materiais.csv" download>Download do modelo CSV</a>
<p class="text-muted small"><strong>Nota:</strong> Para obter o RoomID de uma sala, consulte a gestão de salas ou use a listagem abaixo.</p>
<p class="small" style="color:red;font-weight:bold;">Deve de consultar o manual do administrador para mais informações.</p>
<form action="materiais.php?action=import" method="POST" enctype="multipart/form-data" class="d-flex align-items-center justify-content-center">
<?php echo csrf_token_field(); ?>
<div class="me-2">
<input type="file" class="form-control" id="csvfile" name="csvfile" accept=".csv" required>
</div>
Expand All @@ -26,9 +29,16 @@
switch (isset($_GET['action']) ? $_GET['action'] : null){
// Import CSV
case "import":
if (!isset($_FILES['csvfile']) || $_FILES['csvfile']['error'] !== UPLOAD_ERR_OK) {
$maxFileSize = 2 * 1024 * 1024; // 2 MB
if (!isset($_POST['csrf_token']) || !verify_csrf_token($_POST['csrf_token'])) {
echo "<div class='alert alert-danger fade show' role='alert'><strong>Erro:</strong> Token CSRF inválido.</div>";
break;
} elseif (!isset($_FILES['csvfile']) || $_FILES['csvfile']['error'] !== UPLOAD_ERR_OK) {
echo "<div class='alert alert-danger fade show' role='alert'>Erro ao fazer upload do ficheiro.</div>";
break;
} elseif ($_FILES['csvfile']['size'] > $maxFileSize) {
echo "<div class='alert alert-danger fade show' role='alert'><strong>Erro:</strong> Ficheiro demasiado grande (máximo 2 MB).</div>";
break;
}

// Set database charset to utf8mb4
Expand All @@ -53,7 +63,7 @@
$errors = [];
$lineNumber = 0;

while (($data = fgetcsv($tempFile, 0, ';')) !== FALSE) { // Changed: Added ';' as delimiter
while (($data = fgetcsv($tempFile, 0, ',')) !== FALSE) {
$lineNumber++;

// Skip empty lines
Expand Down
Loading
Loading