Files
xamxam/app/public/admin/actions/access-request.php
Pontoporeia ca5983075d feat: admin audit logging across all admin actions
- AdminLogger: JSON-lines → /var/log/xamxam.log (prod) / storage/logs/admin.log (dev)
  + best-effort DB mirror to admin_audit_log table
- DB: admin_audit_log table, share_links.is_archived column
- ShareLink: archive() replaces delete(), toggleActive() returns new state,
  listActive()/listArchived() split, validateLink blocks archived slugs
- All action handlers wired: publish, unpublish, visibility, delete, csv/db export,
  tfe add/edit, tags, pages, apropos, form-help, access-request, maintenance,
  settings (formulaire toggles, objet types, smtp update), smtp-test
- acces.php: archive button replaces delete; collapsible archived links section
- setup-server.sh: provision /var/log/xamxam.log (www-data:xamxam 640)
2026-05-05 11:04:52 +02:00

135 lines
4.9 KiB
PHP

<?php
/**
* Access Request Action Handler
*
* Approve or reject file access requests.
*/
require_once __DIR__ . '/../../../bootstrap.php';
require_once __DIR__ . '/../../../src/AdminAuth.php';
AdminAuth::requireLogin();
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Token de sécurité invalide']);
exit;
}
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/SmtpRelay.php';
require_once APP_ROOT . '/src/AdminLogger.php';
$db = Database::getInstance();
$logger = AdminLogger::make();
$requestId = isset($_POST['request_id']) ? (int)$_POST['request_id'] : 0;
$action = $_POST['action'] ?? '';
$notes = $_POST['admin_notes'] ?? null;
if ($requestId <= 0 || !in_array($action, ['approve', 'reject'], true)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Données invalides']);
exit;
}
try {
$request = $db->getAccessRequestById($requestId);
if (!$request) {
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Demande non trouvée']);
exit;
}
if ($action === 'approve') {
// Generate token
$token = $db->approveAccessRequest($requestId);
// Send access email to user
$thesisTitle = $request['title'];
$thesisAuthors = $request['authors'] ?? '';
$accessUrl = "https://{$_SERVER['HTTP_HOST']}/validate-access?token={$token}&thesis={$request['thesis_id']}";
$subject = "Accès accordé - TFE: {$thesisTitle}";
$body = buildApprovalEmail($thesisTitle, $thesisAuthors, $accessUrl, $notes);
$plain = strip_tags($body);
try {
SmtpRelay::send($db, $request['email'], $subject, $body, $plain);
$logger->logAccessRequest($requestId, 'approve', $request['email'], $request['title']);
App::flash('success', "Demande approuvée. Email envoyé à {$request['email']}.");
} catch (SmtpSendException $e) {
error_log('[access-request] Email delivery failed after approval: ' . $e->getMessage());
$logger->logAccessRequest($requestId, 'approve', $request['email'], $request['title']);
$smtpMsg = $e->isRecipientRejected()
? "Demande approuvée, mais l'email n'a pas pu être délivré : adresse inconnue ({$request['email']})."
: "Demande approuvée, mais l'envoi de l'email a échoué (erreur SMTP). L'utilisateur devra relancer une demande.";
App::flash('warning', $smtpMsg);
}
} elseif ($action === 'reject') {
$db->rejectAccessRequest($requestId, $notes);
$logger->logAccessRequest($requestId, 'reject', $request['email'], $request['title']);
App::flash('success', "Demande rejetée.");
}
header('Location: /admin/acces.php');
exit;
} catch (Exception $e) {
error_log('Access request action failed: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Erreur lors du traitement']);
}
/**
* Build approval notification email HTML
*/
function buildApprovalEmail(string $title, string $authors, string $accessUrl, ?string $adminNotes): string {
$notesHtml = '';
if (!empty($adminNotes)) {
$notesHtml = "<p><strong>Note de l'administrateur :</strong><br>" . htmlspecialchars($adminNotes) . "</p>";
}
return <<<HTML
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h2 style="color: #2c5282;">Accès accordé au TFE</h2>
<p>Bonjour,</p>
<p>Votre demande d'accès au TFE suivant a été approuvée par un administrateur :</p>
<div style="background: #f7fafc; padding: 15px; border-left: 4px solid #2c5282; margin: 20px 0;">
<strong>Titre :</strong> {$title}<br>
<strong>Auteur(s) :</strong> {$authors}
</div>
{$notesHtml}
<p>Pour accéder aux fichiers attachés, veuillez cliquer sur le lien ci-dessous :</p>
<div style="text-align: center; margin: 30px 0;">
<a href="{$accessUrl}"
style="display: inline-block; padding: 12px 30px; background-color: #2c5282;
color: white; text-decoration: none; border-radius: 5px; font-weight: bold;">
Accéder au TFE
</a>
</div>
<p><strong>Ce lien est valable 24 heures et à usage unique.</strong> L'accès sur votre appareil sera ensuite conservé 30 jours.</p>
<p style="margin-top: 30px; color: #666; font-size: 0.9em;">
Cordialement,<br>
L'équipe XAMXAM - ERG
</p>
</div>
</body>
</html>
HTML;
}