mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
Requirements: - parametres.php toggle: 'restricted_files_enabled' enables/disables the feature - Public TFE page: when enabled + access_type=Interne, hides files, shows French restriction message + access request form (metadata/synopsis still visible) - ERG emails (@erg.school / @erg.be): auto-approve, send 24h access link immediately - External emails: show justification textarea, create pending request, notify admin - Admin panel /admin/file-access.php: approve/reject requests with optional notes, sends access email on approval (linked from admin nav with pending count badge) Security: - One-time 24h email tokens (used_at + is_valid=0 on first click) - Token redeemed via POST /validate-access (GET shows confirmation page only) - Long-lived 30-day browser session in file_access_sessions table - Cookie: HttpOnly + Secure + SameSite=Strict - CSRF on all mutations, rate limiting on request submission - Audit trail: IP, UA, event, timestamp in file_access_audit Bug fixes: - admin/file-access.php: $vars never extract()ed → page was blank - Template had self-contained head/footer includes (double-include) - Admin approval URL used $requestId instead of $request['thesis_id'] - App::boot() now starts session so CSRF token works on public pages - Dispatcher routes /validate-access and /request-access through front controller
200 lines
9.2 KiB
PHP
200 lines
9.2 KiB
PHP
<main id="main-content">
|
|
<h1>Demandes d'accès aux fichiers</h1>
|
|
|
|
<div class="access-req-stats">
|
|
<div class="access-req-stat-card">
|
|
<span class="access-req-stat-number"><?= $pendingCount ?></span>
|
|
<span class="access-req-stat-label">En attente</span>
|
|
</div>
|
|
<div class="access-req-stat-card">
|
|
<span class="access-req-stat-number"><?= $approvedCount ?></span>
|
|
<span class="access-req-stat-label">Approuvées</span>
|
|
</div>
|
|
<div class="access-req-stat-card">
|
|
<span class="access-req-stat-number"><?= $rejectedCount ?></span>
|
|
<span class="access-req-stat-label">Rejetées</span>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="access-req-tabs">
|
|
<a href="?status=pending" class="access-req-tab <?= $status === 'pending' ? 'active' : '' ?>">
|
|
En attente <?= $pendingCount > 0 ? "({$pendingCount})" : '' ?>
|
|
</a>
|
|
<a href="?status=approved" class="access-req-tab <?= $status === 'approved' ? 'active' : '' ?>">
|
|
Approuvées
|
|
</a>
|
|
<a href="?status=rejected" class="access-req-tab <?= $status === 'rejected' ? 'active' : '' ?>">
|
|
Rejetées
|
|
</a>
|
|
</nav>
|
|
|
|
<?php if (empty($requests)): ?>
|
|
<div class="access-req-empty">
|
|
<p>Aucune demande <?= $status === 'pending' ? 'en attente' : ($status === 'approved' ? 'approuvée' : 'rejetée') ?>.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="access-req-list">
|
|
<?php foreach ($requests as $req): ?>
|
|
<div class="access-req-card">
|
|
<div class="access-req-card__header">
|
|
<div class="access-req-card__thesis">
|
|
<h3><?= htmlspecialchars($req['title']) ?></h3>
|
|
<p class="access-req-card__authors">
|
|
<?php if (!empty($req['authors'])): ?>
|
|
par <?= htmlspecialchars($req['authors']) ?>
|
|
<?php endif; ?>
|
|
<?php if (!empty($req['year'])): ?>
|
|
— <?= htmlspecialchars($req['year']) ?>
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
<div class="access-req-card__meta">
|
|
<span class="access-req-badge access-req-badge--<?= $status ?>">
|
|
<?= $status === 'pending' ? 'En attente' : ($status === 'approved' ? 'Approuvée' : 'Rejetée') ?>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="access-req-card__body">
|
|
<div class="access-req-card__info">
|
|
<div>
|
|
<strong>Email :</strong>
|
|
<a href="mailto:<?= htmlspecialchars($req['email']) ?>">
|
|
<?= htmlspecialchars($req['email']) ?>
|
|
</a>
|
|
</div>
|
|
<div>
|
|
<strong>Date :</strong>
|
|
<?= date('d/m/Y à H:i', strtotime($req['created_at'])) ?>
|
|
</div>
|
|
<?php if ($status === 'approved' && !empty($req['approved_at'])): ?>
|
|
<div>
|
|
<strong>Approuvée le :</strong>
|
|
<?= date('d/m/Y à H:i', strtotime($req['approved_at'])) ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if ($status === 'rejected' && !empty($req['approved_at'])): ?>
|
|
<div>
|
|
<strong>Rejetée le :</strong>
|
|
<?= date('d/m/Y à H:i', strtotime($req['approved_at'])) ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if (!empty($req['justification'])): ?>
|
|
<div class="access-req-card__justification">
|
|
<strong>Justification :</strong>
|
|
<p><?= nl2br(htmlspecialchars($req['justification'])) ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($status === 'rejected' && !empty($req['admin_notes'])): ?>
|
|
<div class="access-req-card__admin-notes">
|
|
<strong>Note de l'administrateur :</strong>
|
|
<p><?= nl2br(htmlspecialchars($req['admin_notes'])) ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($status === 'pending'): ?>
|
|
<div class="access-req-card__actions">
|
|
<button type="button"
|
|
class="access-req-btn access-req-btn--approve"
|
|
onclick="openApproveDialog(<?= $req['id'] ?>)">
|
|
Approuver
|
|
</button>
|
|
<button type="button"
|
|
class="access-req-btn access-req-btn--reject"
|
|
onclick="openRejectDialog(<?= $req['id'] ?>)">
|
|
Rejeter
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php if ($totalPages > 1): ?>
|
|
<nav class="access-req-pagination">
|
|
<?php if ($page > 1): ?>
|
|
<a href="?status=<?= $status ?>&page=<?= $page - 1 ?>" class="access-req-pagination__link">
|
|
← Précédent
|
|
</a>
|
|
<?php endif; ?>
|
|
|
|
<span class="access-req-pagination__info">
|
|
Page <?= $page ?> sur <?= $totalPages ?>
|
|
</span>
|
|
|
|
<?php if ($page < $totalPages): ?>
|
|
<a href="?status=<?= $status ?>&page=<?= $page + 1 ?>" class="access-req-pagination__link">
|
|
Suivant →
|
|
</a>
|
|
<?php endif; ?>
|
|
</nav>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</main>
|
|
|
|
<!-- Approve Dialog -->
|
|
<dialog id="approve-dialog" class="admin-dialog">
|
|
<div class="admin-dialog__header">
|
|
<h2>Approuver la demande</h2>
|
|
<button type="button" class="admin-dialog__close"
|
|
onclick="document.getElementById('approve-dialog').close()">×</button>
|
|
</div>
|
|
<form method="post" action="/admin/actions/access-request.php">
|
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
|
<input type="hidden" name="request_id" id="approve-request-id">
|
|
<input type="hidden" name="action" value="approve">
|
|
|
|
<label for="approve-notes">Note optionnelle (inclus dans l'email) :</label>
|
|
<textarea name="admin_notes" id="approve-notes" rows="3"
|
|
placeholder="Message personnalisé pour le demandeur..."></textarea>
|
|
|
|
<div class="admin-form-footer">
|
|
<button type="submit" class="admin-btn">Approuver et envoyer email</button>
|
|
<button type="button" class="admin-btn-secondary"
|
|
onclick="document.getElementById('approve-dialog').close()">Annuler</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
|
|
<!-- Reject Dialog -->
|
|
<dialog id="reject-dialog" class="admin-dialog">
|
|
<div class="admin-dialog__header">
|
|
<h2>Rejeter la demande</h2>
|
|
<button type="button" class="admin-dialog__close"
|
|
onclick="document.getElementById('reject-dialog').close()">×</button>
|
|
</div>
|
|
<form method="post" action="/admin/actions/access-request.php">
|
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
|
<input type="hidden" name="request_id" id="reject-request-id">
|
|
<input type="hidden" name="action" value="reject">
|
|
|
|
<label for="reject-notes">Raison du rejet (optionnel) :</label>
|
|
<textarea name="admin_notes" id="reject-notes" rows="3"
|
|
placeholder="Raison du rejet..."></textarea>
|
|
|
|
<div class="admin-form-footer">
|
|
<button type="submit" class="admin-btn admin-btn--danger">Rejeter</button>
|
|
<button type="button" class="admin-btn-secondary"
|
|
onclick="document.getElementById('reject-dialog').close()">Annuler</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
|
|
<script>
|
|
function openApproveDialog(requestId) {
|
|
document.getElementById('approve-request-id').value = requestId;
|
|
document.getElementById('approve-dialog').showModal();
|
|
}
|
|
|
|
function openRejectDialog(requestId) {
|
|
document.getElementById('reject-request-id').value = requestId;
|
|
document.getElementById('reject-dialog').showModal();
|
|
}
|
|
</script>
|
|
|
|
|