Skip to content

Adicionar importação de reservas em massa via CSV com modal de pesquisa de IDs no painel admin#152

Merged
marpisco merged 22 commits into
mainfrom
copilot/importar-horarios-por-csv
May 22, 2026
Merged

Adicionar importação de reservas em massa via CSV com modal de pesquisa de IDs no painel admin#152
marpisco merged 22 commits into
mainfrom
copilot/importar-horarios-por-csv

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 21, 2026

Este PR implementa a importação de reservas em massa via CSV no script correto da issue original: admin/reservaemmassa.php, incluindo um modal de apoio para pesquisa de IDs via API.

Alterações

  • Adicionado novo bloco de UI em admin/reservaemmassa.php para upload de CSV:
    • link para download do modelo,
    • formulário dedicado de importação.
  • Implementado novo fluxo action=import_csv para:
    • leitura/parsing de CSV com delimitador , (compatível com Excel),
    • validação de token CSRF,
    • validação de referências (SalaID, RequisitorID, TempoID) via consultas pontuais SELECT 1 ... WHERE id = ? LIMIT 1 com cache por ID (sem carregar tabelas completas para memória),
    • validação de data (YYYY-MM-DD),
    • deteção e ignorar duplicados já existentes em reservas,
    • inserção de reservas com aprovado = 1,
    • feedback agregado de sucesso/avisos/erros.
  • Registo de ação administrativa no padrão existente via acaoexecutada(...).
  • Adicionado template de suporte assets/csvsample_reservas.csv (com vírgulas).
  • Adicionado modal de pesquisa de IDs em admin/reservaemmassa.php com 3 tabs:
    • requisitorID,
    • tempoID,
    • salaID.
  • Adicionados endpoints dedicados em /admin/api para suportar a pesquisa no modal:
    • admin/api/requisitor_lookup.php,
    • admin/api/tempo_lookup.php,
    • admin/api/sala_lookup.php.
  • Pesquisa no modal com:
    • placeholders/skeletons Bootstrap durante carregamento,
    • filtro mínimo obrigatório (2 caracteres),
    • limite de 10 resultados por pesquisa para reduzir carga na base de dados.

Resultado

A importação CSV fica disponível no módulo de reservas em massa (admin/reservaemmassa.php), alinhada com o escopo correto da issue, e passa a ter apoio de pesquisa rápida de IDs (requisitor, tempo e sala) para facilitar a preparação dos ficheiros CSV.

Copilot AI requested review from Copilot and removed request for Copilot May 21, 2026 21:47
Copilot AI linked an issue May 21, 2026 that may be closed by this pull request
Agent-Logs-Url: https://github.com/marpisco/ClassLink/sessions/c15f61c7-4d55-40de-b428-433cbce7baa9

Co-authored-by: marpisco <162377105+marpisco@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 21, 2026 21:48
Copilot AI requested review from Copilot and removed request for Copilot May 21, 2026 21:50
Copilot AI changed the title [WIP] Add horarios importação por CSV Adicionar importação de horários (tempos) via CSV no painel admin May 21, 2026
Copilot AI requested a review from marpisco May 21, 2026 21:51
Copy link
Copy Markdown
Owner

@marpisco marpisco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Anula, não estou a falar da importação de tempos na minha issue original, estou a referir me às importações de reservas (script atual admin/reservaemmassa.php)

Agent-Logs-Url: https://github.com/marpisco/ClassLink/sessions/3ff397cc-c4ca-405e-846d-9e183bfa8375

Co-authored-by: marpisco <162377105+marpisco@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 21, 2026 22:00
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 21, 2026

@copilot Anula, não estou a falar da importação de tempos na minha issue original, estou a referir me às importações de reservas (script atual admin/reservaemmassa.php)

Reverti totalmente essas alterações fora de escopo no commit 99cf827 (removida a importação CSV de tempos e o ficheiro de exemplo). Screenshot de verificação da UI revertida: /tmp/playwright-logs/page-2026-05-21T21-59-53-748Z.png.

Se quiseres, avanço já com a implementação correta da importação CSV nas reservas em massa (admin/reservaemmassa.php).

Copilot AI changed the title Adicionar importação de horários (tempos) via CSV no painel admin Reverter importação de horários (tempos) via CSV no painel admin May 21, 2026
Copilot AI requested a review from marpisco May 21, 2026 22:00
@marpisco
Copy link
Copy Markdown
Owner

@copilot Sim, podes avançar com a implementação do correto.

Agent-Logs-Url: https://github.com/marpisco/ClassLink/sessions/4b49d234-7d64-4371-9ed8-1404a6bdde04

Co-authored-by: marpisco <162377105+marpisco@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 21, 2026 22:04
@marpisco
Copy link
Copy Markdown
Owner

@copilot Tenta novamente, ontem atingi um rate limit.

Substitui a pré-carga integral das tabelas salas, cache e tempos por
consultas SELECT 1 ... WHERE id = ? LIMIT 1 com cache por ID durante
o parsing do CSV. Evita carregar tabelas completas para memória.
Copilot AI review requested due to automatic review settings May 22, 2026 08:32
@github-actions
Copy link
Copy Markdown

📝 Manual Update Required? It looks like you've modified core logic. Please check if the user manual needs a refresh! @marpisco

@marpisco
Copy link
Copy Markdown
Owner

@marpisco Implementada a correção do review comment pendente no commit 74f95cc:

A validação de IDs do CSV deixou de carregar as tabelas completas (salas, cache, tempos) para arrays em memória. Agora usa consultas pontuais SELECT 1 ... WHERE id = ? LIMIT 1 com cache por ID durante o parsing, como sugerido pelo reviewer.

Além disso, atualizei a descrição do PR para corrigir a menção ao delimitador: é , (vírgula, compatível com Excel), não ;.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Evita acumular todas as mensagens de erro em memória quando o CSV
contém muitas linhas com erros. Apenas guarda até $maxDisplayedErrors
(10) mensagens, que é o limite efetivo de exibição.
@github-actions
Copy link
Copy Markdown

📝 Manual Update Required? It looks like you've modified core logic. Please check if the user manual needs a refresh! @marpisco

@marpisco
Copy link
Copy Markdown
Owner

Corrigido também o terceiro review comment (memory concern): o array $errors agora é limitado a $maxDisplayedErrors (10) mensagens durante o parsing, em vez de acumular todas as mensagens de erro. Isto evita consumo desnecessário de memória em CSVs grandes com muitos erros.

Commit: c10a15a

Adds 2MB max file size validation to prevent resource exhaustion from
large CSV uploads. Adds mb_detect_encoding + mb_convert_encoding for
UTF-8 conversion (consistent with materiais.php pattern) to handle
non-UTF-8 CSVs (e.g. Windows-1252 from Excel). Uses tmpfile() instead
of direct fopen() on the upload temp path to process converted content.
Copilot AI review requested due to automatic review settings May 22, 2026 09:07
@github-actions
Copy link
Copy Markdown

📝 Manual Update Required? It looks like you've modified core logic. Please check if the user manual needs a refresh! @marpisco

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

The materiais.php CSV import lacked CSRF token verification (unlike the
new reservaemmassa.php import) and had no file size limit. Both issues
could allow abuse: CSRF bypass for automated submissions and resource
exhaustion via large file uploads. Now consistent with reservaemmassa.php
security pattern.
@github-actions
Copy link
Copy Markdown

📝 Manual Update Required? It looks like you've modified core logic. Please check if the user manual needs a refresh! @marpisco

@marpisco
Copy link
Copy Markdown
Owner

🔒 Security Review — PR #152

Review executed against the security standards defined in .github/copilot-instructions.md.

✅ Already correct (no issues found)

Area Status Detail
Admin guard admin/index.php checks $_SESSION['admin'] + session validity ($_SESSION['validity']) before any page content
CSRF protection CSV import uses verify_csrf_token() + csrf_token_field()
Prepared statements All DB queries use $db->prepare() + bind_param() — zero SQL injection risk
XSS protection PHP errors escaped via htmlspecialchars($error, ENT_QUOTES, 'UTF-8'); JS lookup modal uses escapeHtml() for all API output
Audit logging acaoexecutada() internally calls logaction() with IP tracking ($_SESSION['id'], get_client_ip())
LIKE wildcard escaping All 3 lookup endpoints escape % / _ with ESCAPE '\\' clause (stricter than existing salas_search.php / tempos_search.php which don't escape wildcards)
API admin check All 3 new endpoints verify $_SESSION['admin'] with HTTP 403 on failure
Input sanitization Lookup endpoints use sanitize_input() (100 char limit); minimum 2-char filter enforced; result cap of 10 per query
Session guard if (session_status() === PHP_SESSION_NONE) { session_start(); } per project convention
Content-Type header application/json; charset=utf-8 on all API responses
BOM handling preg_replace('/^\xEF\xBB\xBF/', '', ...) strips UTF-8 BOM from CSV first column
Error cap $maxDisplayedErrors = 10 prevents memory exhaustion from error accumulation in large CSVs
Duplicate detection Point-query SELECT 1 FROM reservas WHERE ... LIMIT 1 before insert, avoids duplicate entries
Validation caching salaValidationCache, requisitorValidationCache, tempoValidationCache arrays cache ID existence checks — avoids redundant DB roundtrips

🔴 Issues found & fixed (2 commits pushed to this branch)

# Issue Severity Fix (commit)
1 No file size limit on CSV upload — attacker could upload multi-GB CSV to exhaust server memory/CPU Medium → High if exploitable Added $maxFileSize = 2 MB check ($_FILES['csvfile']['size'] > $maxFileSize) with clear error message. Commit 21f116d.
2 No encoding detection on reservaemmassa CSV import — non-UTF-8 CSVs (Windows-1252 from Excel PT users) would produce corrupted data in DB Medium Added mb_detect_encoding() + mb_convert_encoding() (consistent with materiais.php pattern), uses tmpfile() for converted content. Commit 21f116d.
3 materiais.php CSV import lacked CSRF protection — any admin could be tricked into importing a malicious CSV via cross-site request High Added csrf_token_field() to form + verify_csrf_token() check in handler. Commit 25452cb.
4 materiais.php CSV import had no file size limit — same resource exhaustion risk as issue #1 Medium → High Added identical 2MB limit check. Commit 25452cb.

⚠️ Notes for reviewers (no code fix needed, but worth awareness)

  • validate_uuid() not used for ID inputs: Intentionally skipped because pre_ prefixed IDs and non-v4-format UUIDs in the DB would fail the regex. DB existence check with prepared statements provides equivalent security.
  • All imports set aprovado = 1: Consistent with existing weekly mass reservation behavior — bypasses approval workflow for tipo_sala = 1 rooms. This is an admin action, so intentional.
  • No transaction/rollback on CSV import: Consistent with materiais.php pattern — partial imports are possible on mid-process error. Not a security issue, but a data integrity consideration.
  • Existing salas_search.php / tempos_search.php don't escape LIKE wildcards: Pre-existing issue (not introduced by this PR). The new lookup endpoints are actually stricter in this regard.
  • materiais.php criar_completo / update / apagar actions also lack CSRF: Pre-existing issues outside the scope of this PR's changes, but should be addressed in a follow-up.

@marpisco
Copy link
Copy Markdown
Owner

📋 PR #152 Overview

What this PR covers

This PR implements bulk reservation import via CSV in the admin panel (admin/reservaemmassa.php), along with an ID lookup modal to help admins prepare their CSV files. It also standardizes the CSV delimiter for materials from ; to ,.

What was done

New features:

  • CSV upload form + import handler (action=import_csv) in admin/reservaemmassa.php
    • Parses CSV with , delimiter (Excel-compatible)
    • Validates all references (SalaID, RequisitorID, TempoID) via point-queries with per-ID caching
    • Validates dates (YYYY-MM-DD)
    • Detects and skips existing reservations (duplicate protection)
    • Inserts with aprovado = 1 (admin auto-approve)
    • Aggregated success/warning/error feedback with capped error display (max 10 shown)
  • Lookup modal with 3 tabs (requisitorID, tempoID, salaID) in admin/reservaemmassa.php
  • 3 new API endpoints: admin/api/requisitor_lookup.php, admin/api/sala_lookup.php, admin/api/tempo_lookup.php
    • Admin-only access (session check + 403)
    • sanitize_input() + LIKE wildcard escaping + result cap of 10
  • Sample CSV template: assets/csvsample_reservas.csv
  • JS client-side: escapeHtml() for XSS-safe rendering, skeleton loading states

Bugfix / consistency changes:

  • admin/materiais.php: CSV delimiter changed from ; to , (consistent with Excel)
  • assets/csvsample.csv → deleted, replaced by assets/csvsample_materiais.csv (comma-delimited)

Security hardening (added after review):

  • admin/reservaemmassa.php: Added 2MB file size limit + encoding detection/conversion (commits 21f116d, 25452cb)
  • admin/materiais.php: Added CSRF token protection + 2MB file size limit (commit 25452cb)

Files changed (8)

File Change
admin/reservaemmassa.php Major: CSV import UI + handler + lookup modal + JS
admin/api/requisitor_lookup.php New: API endpoint for requisitor ID search
admin/api/sala_lookup.php New: API endpoint for sala ID search
admin/api/tempo_lookup.php New: API endpoint for tempo ID search
admin/materiais.php Modified: delimiter ;, + CSRF + file size limit
assets/csvsample.csv Deleted (old semicolon-delimited materials template)
assets/csvsample_materiais.csv New (comma-delimited materials template)
assets/csvsample_reservas.csv New (comma-delimited reservations template)

What's needed to adopt into main

  1. Merge this PR — The branch copilot/importar-horarios-por-csv is ready for merge into main. All php -l syntax checks pass on modified files.

  2. No DB schema changes required — The PR uses existing tables (reservas, salas, cache, tempos) with no ALTER TABLE migrations. The aprovado = 1 column already exists.

  3. No new dependencies — All code uses existing project functions (acaoexecutada, logaction, validate_date, sanitize_input, csrf_token_field, uuid4). No new PHP libraries needed.

  4. Post-merge follow-ups (not blocking, but recommended):

    • Add CSRF protection to remaining materiais.php actions (criar, criar_completo, update, apagar) — currently unprotected
    • Fix LIKE wildcard escaping in existing admin/api/salas_search.php and admin/api/tempos_search.php — currently unescaped, inconsistent with the new lookup endpoints
    • Consider adding a transaction wrapper around the CSV import loop for atomicity (partial import rollback on error)
  5. Merge method: Recommend squash merge to keep main history clean — this PR has 22 commits (including Copilot agent iterations + 2 security fix commits). A single squash commit summarizing all changes would be cleaner.

@marpisco marpisco marked this pull request as ready for review May 22, 2026 09:36
Copilot AI review requested due to automatic review settings May 22, 2026 09:36
@marpisco marpisco merged commit 701ce3d into main May 22, 2026
6 of 7 checks passed
@marpisco marpisco deleted the copilot/importar-horarios-por-csv branch May 22, 2026 09:36
Copilot AI review requested due to automatic review settings May 22, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Horários importação por CSV

3 participants