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)
This commit is contained in:
Pontoporeia
2026-05-04 17:34:26 +02:00
parent 5f24dcae7e
commit ca5983075d
24 changed files with 521 additions and 33 deletions

View File

@@ -98,9 +98,32 @@ class ShareLink
}
/**
* List all share links, ordered by creation date descending.
* List active (non-archived) share links, ordered by creation date descending.
*/
public function listActive(): array
{
$stmt = $this->db->getConnection()->query(
'SELECT * FROM share_links WHERE is_archived = 0 ORDER BY created_at DESC'
);
return $stmt->fetchAll();
}
/**
* List archived share links, ordered by creation date descending.
*/
public function listArchived(): array
{
$stmt = $this->db->getConnection()->query(
'SELECT * FROM share_links WHERE is_archived = 1 ORDER BY created_at DESC'
);
return $stmt->fetchAll();
}
/**
* List all share links (active + archived), ordered by creation date descending.
*
* @return array
* @deprecated Use listActive() / listArchived() instead.
*/
public function listAll(): array
{
@@ -112,12 +135,17 @@ class ShareLink
/**
* Toggle the active status of a share link.
* Returns the new is_active value.
*/
public function toggleActive(int $id): void
public function toggleActive(int $id): bool
{
$this->db->getConnection()->prepare(
$pdo = $this->db->getConnection();
$pdo->prepare(
'UPDATE share_links SET is_active = NOT is_active WHERE id = ?'
)->execute([$id]);
$row = $pdo->prepare('SELECT is_active FROM share_links WHERE id = ?');
$row->execute([$id]);
return (bool)($row->fetchColumn() ?? false);
}
/**
@@ -134,12 +162,13 @@ class ShareLink
}
/**
* Delete a share link.
* Archive a share link: marks it inaccessible while preserving usage stats.
* Sets is_archived = 1 and is_active = 0 so the form rejects the slug.
*/
public function delete(int $id): void
public function archive(int $id): void
{
$this->db->getConnection()->prepare(
'DELETE FROM share_links WHERE id = ?'
'UPDATE share_links SET is_archived = 1, is_active = 0 WHERE id = ?'
)->execute([$id]);
}
@@ -176,6 +205,10 @@ class ShareLink
return ['valid' => false, 'reason' => 'not_found'];
}
if (!empty($link['is_archived'])) {
return ['valid' => false, 'reason' => 'archived', 'link' => $link];
}
if (!$link['is_active']) {
return ['valid' => false, 'reason' => 'disabled', 'link' => $link];
}