Files
xamxam/app/templates/admin/acces.php
Pontoporeia dfde88eaa5 Migrate all <img>-based icons to inline SVG via PHP helper
Replace every <img src="/assets/icons/..."> with <?= icon('name') ?>
across 26 template files. The PHP helper inlines the SVG markup into the
DOM so CSS color cascades naturally through fill="currentColor".

- Add src/icon.php helper: reads SVG file, sets width/height to 1em,
  injects aria-hidden, supports optional CSS class
- Fix 12 icon SVGs that had hardcoded fill="#000000" or missing fill attr
- Replace search.svg with Phosphor fill-based magnifying glass
- Add explicit SVG sizes for admin header nav icons (16px/20px)
- Scope public search icon CSS to form[role=search]:not(.header-search-form)
  to avoid breaking admin header layout; change stroke to fill
- Remove <img> filter: brightness(0) invert(1) hacks from admin.css
2026-06-21 17:52:27 +02:00

693 lines
40 KiB
PHP
Raw 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.
<main id="main-content" class="admin-main--toc">
<?php include APP_ROOT . '/templates/admin/partials/admin-toc.php'; ?>
<article>
<h1>Accès</h1>
<!-- ══════════════════════════════════════════════════════════════
LIENS ÉTUDIANT·E
══════════════════════════════════════════════════════════════ -->
<section aria-labelledby="acces-liens-title">
<div class="admin-list-toolbar">
<h2 id="acces-liens-title">Liens étudiant·e</h2>
<div class="admin-list-toolbar__right">
<button type="button" class="btn btn--primary btn--sm" id="open-create-dialog">
+ Créer un lien
</button>
</div>
</div>
<?php if (empty($links)): ?>
<p class="admin-empty">Aucun lien créé. Cliquez sur « Créer un lien » pour générer un lien partageable.</p>
<?php else: ?>
<table>
<thead>
<tr>
<th scope="col">Nom</th>
<th scope="col">Lien</th>
<th scope="col">Objet</th>
<th scope="col">Année</th>
<th scope="col">Statut</th>
<th scope="col">Mot de passe</th>
<th scope="col">Utilisations</th>
<th scope="col">Expiration</th>
<th scope="col">Créé le</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($links as $link): ?>
<?php
$isExpired = $link['expires_at'] !== null && strtotime($link['expires_at']) < time();
$isActive = (bool)$link['is_active'] && !$isExpired;
$statusLabel = $isExpired ? 'Expiré' : ($link['is_active'] ? 'Actif' : 'Désactivé');
$fullUrl = $baseUrl . '/partage/' . htmlspecialchars($link['slug']);
$created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y H:i', strtotime($link['expires_at'])) : '-';
$hasLinkPassword = !empty($link['password_hash']);
$linkName = $link['name'] ?? '';
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
$linkLockedYear = $link['locked_year'] ?? null;
?>
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
<td><?= htmlspecialchars($linkName ?: '—') ?></td>
<td>
<code style="font-size:var(--step--2);color:var(--text-secondary);"><?= htmlspecialchars($link['slug']) ?></code>
<input type="hidden" id="url-<?= $link['id'] ?>" value="<?= $fullUrl ?>">
</td>
<td>
<?php if ($link['objet_restriction']): ?>
<span class="status-badge"><?= htmlspecialchars($link['objet_restriction']) ?></span>
<?php else: ?>
<span style="color:var(--text-secondary);font-size:var(--step--2);">Tous</span>
<?php endif; ?>
</td>
<td>
<?php if ($link['locked_year']): ?>
<span class="status-badge" style="background:var(--accent-green-muted-bg, #e6f7ec);color:var(--accent-green, #1a7f4b);border:1px solid var(--accent-green, #1a7f4b);">🔒 <?= htmlspecialchars((string)$link['locked_year']) ?></span>
<?php else: ?>
<span style="color:var(--text-secondary);font-size:var(--step--2);">Libre</span>
<?php endif; ?>
</td>
<td>
<?php if ($isExpired): ?>
<span class="status-badge status-pending"><?= $statusLabel ?></span>
<?php elseif ($link['is_active']): ?>
<span class="status-badge status-published"><?= $statusLabel ?></span>
<?php else: ?>
<span style="display:inline-block;padding:var(--space-3xs) var(--space-2xs);border-radius:3px;font-size:var(--step--2);font-weight:500;letter-spacing:0.04em;background:var(--error-muted-bg);color:var(--error);"><?= $statusLabel ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($hasLinkPassword): ?>
<div style="display:flex;align-items:center;gap:var(--space-2xs);">
<span>Oui</span>
<input type="hidden" id="pwd-<?= $link['id'] ?>" value="<?= htmlspecialchars($link['_decrypted_password'] ?? '') ?>">
<button type="button" class="admin-icon-btn" title="Copier le mot de passe"
onclick="event.stopPropagation(); copyTextToClipboard(document.getElementById('pwd-<?= $link['id'] ?>').value)"
style="width:24px;height:24px;">
<?= icon('copy-duplicate') ?>
</button>
</div>
<?php else: ?>
Non
<?php endif; ?>
</td>
<td style="text-align:center;"><?= intval($link['usage_count']) ?></td>
<td><?= $expires ?></td>
<td><?= $created ?></td>
<td class="admin-actions-col">
<div class="admin-actions">
<button type="button" class="admin-icon-btn admin-icon-btn--edit" title="Éditer"
onclick="event.stopPropagation(); openEditDialog(<?= $link['id'] ?>, <?= htmlspecialchars(json_encode($linkName), ENT_QUOTES) ?>, <?= $hasLinkPassword ? 'true' : 'false' ?>, <?= htmlspecialchars(json_encode($linkExpiresVal), ENT_QUOTES) ?>)">
<?= icon('pencil-note') ?>
</button>
<button type="button" class="admin-icon-btn admin-icon-btn--copy" title="Copier l'URL"
onclick="event.stopPropagation(); copyUrl(<?= $link['id'] ?>)">
<?= icon('copy-duplicate') ?>
</button>
<form method="post" action="actions/acces-etudiante.php" class="publish-form" onclick="event.stopPropagation()">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit"
class="admin-icon-btn <?= $link['is_active'] ? 'admin-icon-btn--unpublish' : 'admin-icon-btn--publish' ?>"
title="<?= $link['is_active'] ? 'Désactiver' : 'Activer' ?>">
<?php if ($link['is_active']): ?>
<?= icon('columns') ?>
<?php else: ?>
<?= icon('play-triangle') ?>
<?php endif; ?>
</button>
</form>
<form method="post" action="actions/acces-etudiante.php" class="publish-form"
id="archive-link-form-<?= $link['id'] ?>" onclick="event.stopPropagation()">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="archive">
<input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="button" class="admin-icon-btn admin-icon-btn--archive" title="Archiver"
onclick="openArchiveLinkDialog(<?= $link['id'] ?>)">
<?= icon('monitor') ?>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($archivedLinks)): ?>
<details style="margin-top:var(--space-m);">
<summary style="cursor:pointer;font-weight:600;color:var(--text-secondary);font-size:var(--step--1);">
Liens archivés (<?= count($archivedLinks) ?>)
</summary>
<table style="margin-top:var(--space-s);opacity:0.75;">
<thead>
<tr>
<th scope="col">Nom</th>
<th scope="col">Lien</th>
<th scope="col">Objet</th>
<th scope="col">Utilisations</th>
<th scope="col">Expiration</th>
<th scope="col">Créé le</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($archivedLinks as $link): ?>
<?php
$created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y H:i', strtotime($link['expires_at'])) : '-';
?>
<tr>
<td><?= htmlspecialchars($link['name'] ?? '—') ?></td>
<td>
<code style="font-size:var(--step--2);color:var(--text-secondary);text-decoration:line-through;"><?= htmlspecialchars($link['slug']) ?></code>
</td>
<td>
<?php if ($link['objet_restriction']): ?>
<span class="status-badge"><?= htmlspecialchars($link['objet_restriction']) ?></span>
<?php else: ?>
<span style="color:var(--text-secondary);font-size:var(--step--2);">Tous</span>
<?php endif; ?>
</td>
<td style="text-align:center;"><?= intval($link['usage_count']) ?></td>
<td><?= $expires ?></td>
<td><?= $created ?></td>
<td class="admin-actions-col">
<button type="button" class="admin-icon-btn admin-icon-btn--delete" title="Supprimer"
onclick="openDeleteArchivedLinkDialog(<?= $link['id'] ?>)">
<?= icon('trash') ?>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</details>
<?php endif; ?>
</section>
<!-- ══════════════════════════════════════════════════════════════
DEMANDES D'ACCÈS AUX FICHIERS
══════════════════════════════════════════════════════════════ -->
<section aria-labelledby="acces-fichiers-title">
<h2 id="acces-fichiers-title">Demandes d'accès aux fichiers</h2>
<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="btn btn--primary access-req-btn access-req-btn--approve"
onclick="openApproveDialog(<?= $req['id'] ?>)">
Approuver
</button>
<button type="button"
class="btn btn--danger 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">
&larr; 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 &rarr;
</a>
<?php endif; ?>
</nav>
<?php endif; ?>
<?php endif; ?>
</section>
<!-- ═══════════════════════ FILE ACCESS RESTRICTIONS ═══════════════════════ -->
<section aria-labelledby="acces-fichiers-restrictions-title" style="margin-top:var(--space-l);">
<h3 id="acces-fichiers-restrictions-title">Restrictions d'accès aux fichiers</h3>
<div class="param-form">
<fieldset id="fieldset-restrictions">
<legend>Paramètre global</legend>
<label class="param-checkbox">
<input type="checkbox" name="restricted_files_enabled" value="1"
<?= ($siteSettings['restricted_files_enabled'] ?? '0') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#restrictions-response"
hx-swap="innerHTML"
hx-include="#fieldset-restrictions">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="formulaire_restrictions">
<strong>Activer la restriction d'accès</strong><br>
<small style="max-width:42ch;">Pour les TFE de type "Interne", masquer les fichiers et exiger une demande d'accès par email. Les métadonnées et résumés restent publics.</small>
</label>
</fieldset>
<div id="restrictions-response" aria-live="polite"></div>
</div>
</section>
<!-- ═══════════════════════ TFE MESSAGES ═══════════════════════ -->
<section aria-labelledby="acces-fichiers-messages-title" style="margin-top:var(--space-xl);">
<h3 id="acces-fichiers-messages-title">Messages des pages TFE</h3>
<p style="color:var(--text-secondary);font-size:var(--step--1);">
Personnalisez les textes affichés sur les pages TFE pour les accès restreints et interdits.
</p>
<fieldset id="fieldset-tfe-messages" style="margin-top:var(--space-m);">
<legend>Message pour les TFE internes (accès restreint)</legend>
<div>
<label for="tfe_restricted_message">Texte affiché quand les fichiers sont masqués :</label>
<textarea id="tfe_restricted_message" name="tfe_restricted_message" rows="3"
hx-post="/admin/actions/settings.php"
hx-trigger="change delay:500ms"
hx-target="#tfe-messages-response"
hx-swap="innerHTML"
hx-include="#fieldset-tfe-messages"
style="width:100%;max-width:60ch;"><?= htmlspecialchars($siteSettings['tfe_restricted_message'] ?? 'Les fichiers attachés à ce TFE sont réservés aux utilisateur·ices autorisé·es.') ?></textarea>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="tfe_messages">
</div>
</fieldset>
<fieldset id="fieldset-tfe-forbidden" style="margin-top:var(--space-m);">
<legend>Message pour les TFE interdits (non disponible)</legend>
<div>
<label for="tfe_forbidden_message">Texte affiché quand le TFE n'est pas disponible :</label>
<textarea id="tfe_forbidden_message" name="tfe_forbidden_message" rows="2"
hx-post="/admin/actions/settings.php"
hx-trigger="change delay:500ms"
hx-target="#tfe-messages-response"
hx-swap="innerHTML"
hx-include="#fieldset-tfe-forbidden"
style="width:100%;max-width:60ch;"><?= htmlspecialchars($siteSettings['tfe_forbidden_message'] ?? "Ce TFE n'est pas disponible en ligne.") ?></textarea>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="tfe_messages">
</div>
</fieldset>
<div id="tfe-messages-response" aria-live="polite"></div>
</section>
</article>
</main>
<!-- ═══════════════════════ CREATE DIALOG ═══════════════════════ -->
<dialog id="create-dialog" class="admin-dialog" aria-labelledby="create-dialog-title">
<div class="admin-dialog__header">
<h2 id="create-dialog-title">Créer un lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="document.getElementById('create-dialog').close()">&#x2715;</button>
</div>
<form method="post" action="actions/acces-etudiante.php" class="admin-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="create">
<fieldset>
<legend>Nom (optionnel)</legend>
<input type="text" name="name" placeholder="Ex: Lien promo 2025">
<small>Donnez un nom pour identifier facilement ce lien.</small>
</fieldset>
<fieldset>
<legend>Type d'objet</legend>
<label style="display:flex;align-items:center;gap:var(--space-2xs);font-weight:400;">
<input type="checkbox" name="objet_restriction[]" value="tfe" checked> TFE (par défaut)
</label>
<label style="display:flex;align-items:center;gap:var(--space-2xs);font-weight:400;">
<input type="checkbox" name="objet_restriction[]" value="thèse"> Thèse
</label>
<label style="display:flex;align-items:center;gap:var(--space-2xs);font-weight:400;">
<input type="checkbox" name="objet_restriction[]" value="frart"> Frart
</label>
<small>Le lien sera restreint aux types cochés. Par défaut : TFE uniquement.</small>
</fieldset>
<fieldset>
<legend>Cadre académique</legend>
<div>
<label for="create-locked-year">Année académique verrouillée (optionnel)</label>
<input type="number" id="create-locked-year" name="locked_year"
min="2000" max="<?= date('Y') + 3 ?>" placeholder="<?= date('Y') ?>">
<small>Si renseignée, le formulaire étudiant masquera le champ Année et utilisera cette valeur.
Cela garantit que les identifiants TFE (ex: <strong><?= date('Y') ?>-001</strong>) correspondent
à la bonne année académique. Laissez vide pour laisser l'étudiant·e choisir.</small>
</div>
</fieldset>
<fieldset>
<legend>Mot de passe</legend>
<div>
<label>Mot de passe</label>
<input type="password" value="••••••••" disabled
style="width:100%;padding:var(--space-2xs);border:1px solid var(--border-color);border-radius:3px;background:var(--bg-secondary);">
<small>Le mot de passe est généré automatiquement et ne peut pas être modifié.</small>
</div>
<div>
<label for="create-expires">Expiration (optionnel)</label>
<input type="datetime-local" id="create-expires" name="expires_at">
</div>
</fieldset>
<div class="admin-form-footer">
<button type="submit" class="btn btn--primary">Créer le lien</button>
<button type="button" class="btn btn--secondary"
onclick="document.getElementById('create-dialog').close()">Annuler</button>
</div>
</form>
</dialog>
<!-- ═══════════════════════ CREATE RESULT DIALOG (shown after creation) ═══════════════════════ -->
<dialog id="create-result-dialog" class="admin-dialog" aria-labelledby="create-result-dialog-title">
<div class="admin-dialog__header">
<h2 id="create-result-dialog-title">Lien créé</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="document.getElementById('create-result-dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__body">
<p style="font-weight:600;margin-bottom:var(--space-s);color:var(--accent-green);">✅ Lien créé avec succès</p>
<p style="font-size:var(--step--2);color:var(--text-secondary);">
Communiquez ce lien et ce mot de passe à l'étudiant·e. Le mot de passe ne sera plus affiché ensuite.
</p>
<fieldset style="margin-bottom:var(--space-s);">
<legend>Lien d'accès</legend>
<div style="display:flex;gap:var(--space-2xs);align-items:center;">
<input type="text" id="create-result-url" readonly
style="flex:1;font-family:monospace;font-size:var(--step--1);padding:var(--space-xs);border:1px solid var(--border-color);border-radius:3px;background:var(--bg-secondary);">
<button type="button" class="btn btn--primary"
onclick="copyUrlFrom(document.getElementById('create-result-url'))">Copier</button>
</div>
</fieldset>
<fieldset style="margin-bottom:var(--space-s);">
<legend>Mot de passe</legend>
<div style="display:flex;gap:var(--space-2xs);align-items:center;">
<input type="text" id="create-result-password" readonly
style="flex:1;font-family:monospace;font-size:var(--step--1);padding:var(--space-xs);border:1px solid var(--border-color);border-radius:3px;background:var(--bg-secondary);">
<button type="button" class="btn btn--primary"
onclick="copyTextToClipboard(document.getElementById('create-result-password').value)">Copier</button>
</div>
<small>Un mot de passe sera généré automatiquement.</small>
</fieldset>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--secondary"
onclick="document.getElementById('create-result-dialog').close()">Fermer</button>
</div>
</dialog>
<!-- ═══════════════════════ EDIT DIALOG ═══════════════════════ -->
<dialog id="edit-dialog" class="admin-dialog" aria-labelledby="edit-dialog-title">
<div class="admin-dialog__header">
<h2 id="edit-dialog-title">Modifier le lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="document.getElementById('edit-dialog').close()">&#x2715;</button>
</div>
<form method="post" action="actions/acces-etudiante.php" class="admin-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" id="edit-link-id" value="">
<div>
<label for="edit-name">Nom</label>
<input type="text" id="edit-name" name="name" placeholder="Ex: Lien promo 2025">
</div>
<div>
<label>Mot de passe</label>
<input type="password" value="••••••••" disabled
style="width:100%;padding:var(--space-2xs);border:1px solid var(--border-color);border-radius:3px;background:var(--bg-secondary);">
<small>Le mot de passe est généré automatiquement et ne peut pas être modifié.</small>
</div>
<fieldset>
<legend>Cadre académique</legend>
<div>
<label for="edit-locked-year">Année académique verrouillée (optionnel)</label>
<input type="number" id="edit-locked-year" name="locked_year"
min="2000" max="<?= date('Y') + 3 ?>" placeholder="<?= date('Y') ?>">
<small>Si renseignée, le formulaire étudiant masquera le champ Année et utilisera cette valeur.</small>
</div>
</fieldset>
<div>
<label for="edit-expires">Expiration</label>
<input type="datetime-local" id="edit-expires" name="expires_at">
<small>Laissez vide pour qu'il n'expire jamais.</small>
</div>
<div class="admin-form-footer">
<button type="submit" class="btn btn--primary">Enregistrer</button>
<button type="button" class="btn btn--secondary"
onclick="document.getElementById('edit-dialog').close()">Annuler</button>
</div>
</form>
</dialog>
<!-- ═══════════════════════ 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()">&times;</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="btn btn--primary">Approuver et envoyer email</button>
<button type="button" class="btn 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()">&times;</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="btn btn--danger">Rejeter</button>
<button type="button" class="btn btn--secondary"
onclick="document.getElementById('reject-dialog').close()">Annuler</button>
</div>
</form>
</dialog>
<script>
// ── Forward PHP flash data to JS globals ──────────────────────────────────
const BASE_URL = <?= json_encode($baseUrl) ?>;
const _newLinkPassword = <?= json_encode($newLinkPassword ?? '') ?>;
const _newLinkSlug = <?= json_encode($newLinkSlug ?? '') ?>;
// ── Show result dialogs after redirect ────────────────────────────────────
if (_newLinkSlug && _newLinkPassword) {
document.getElementById('create-result-password').value = _newLinkPassword;
document.getElementById('create-result-url').value = BASE_URL + '/partage/' + _newLinkSlug;
document.getElementById('create-result-dialog').showModal();
}
document.getElementById('open-create-dialog').addEventListener('click', () => {
document.getElementById('create-dialog').showModal();
});
function copyUrl(id) {
const input = document.getElementById('url-' + id);
navigator.clipboard.writeText(input.value).then(() => {
const btn = event.target.closest('button');
if (btn) { const orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); setTimeout(() => btn.setAttribute('title', orig), 1200); }
});
}
function copyUrlFrom(el) {
navigator.clipboard.writeText(el.value).then(() => {
const btn = el.nextElementSibling;
if (btn) { const orig = btn.textContent; btn.textContent = '✓ Copié'; setTimeout(() => { btn.textContent = orig; }, 1200); }
});
}
function copyTextToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
const btn = event?.target?.closest('button');
if (btn) { const orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); setTimeout(() => btn.setAttribute('title', orig), 1200); }
}).catch(() => {});
}
function openEditDialog(id, name, hasPassword, expiresVal) {
document.getElementById('edit-link-id').value = id;
document.getElementById('edit-name').value = name || '';
document.getElementById('edit-expires').value = expiresVal || '';
document.getElementById('edit-dialog').showModal();
}
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();
}
let _pendingArchiveLinkId = null;
function openArchiveLinkDialog(id) {
_pendingArchiveLinkId = id;
document.getElementById('archive-link-dialog').showModal();
}
function _executeArchiveLink() {
const form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId);
if (form) form.submit();
}
function openDeleteArchivedLinkDialog(id) {
document.getElementById('delete-archived-link-id').value = id;
document.getElementById('delete-archived-link-dialog').showModal();
}
</script>
<!-- Archive link confirm -->
<dialog id="archive-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="archive-link-title">
<div class="admin-dialog__header">
<h2 id="archive-link-title">Archiver ce lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Archiver ce lien ? Il ne sera plus accessible, mais les statistiques seront conservées.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--warning" onclick="this.closest('dialog').close(); _executeArchiveLink()">Archiver</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- ═══════════════════════ DELETE ARCHIVED LINK DIALOG ═══════════════════════ -->
<dialog id="delete-archived-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-archived-link-title">
<div class="admin-dialog__header">
<h2 id="delete-archived-link-title">Supprimer le lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer définitivement ce lien archivé ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<form method="post" action="actions/acces-etudiante.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="delete-archived-link-id" value="">
<button type="submit" class="btn btn--danger">Supprimer</button>
</form>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>