feat: dual upload system — direct file storage + PeerTube API integration

Adds a parallel PeerTube upload system behind a feature flag (disabled by default
until upload quota is granted). When disabled, the existing direct file upload
path works unchanged.

Files:
- src/PeerTubeService.php — credential storage (encrypted), OAuth2 token
  retrieval, multipart upload to /api/v1/videos/upload
- migrations/021_peertube_settings.sql — peertube_settings singleton table
  + peertube_upload_enabled site_setting (default 0)
- admin/actions/settings.php — peertube section handler
- admin/parametres.php / templates/admin/parametres.php — PeerTube UI section
- partage/fichiers-fragment.php — shows file inputs when enabled, TODO notice otherwise
- ThesisCreateController / ThesisEditController — handlePeerTubeUpload()
- tfe.php — PeerTube iframe embed detection
- AdminLogger — logPeerTubeUpdate()
This commit is contained in:
Pontoporeia
2026-05-08 16:48:34 +02:00
parent 11e61226e2
commit 03c5fd217e
12 changed files with 658 additions and 4 deletions

View File

@@ -313,6 +313,92 @@
</fieldset>
</section>
<!-- ══════════════════════════════════════════════════════════════
PEERTUBE
══════════════════════════════════════════════════════════════ -->
<section aria-labelledby="settings-peertube-title">
<h2 id="settings-peertube-title">PeerTube</h2>
<p>
Intégration avec une instance PeerTube pour l'hébergement des vidéos et fichiers audio.
Les fichiers sont uploadés via l'API PeerTube et intégrés comme lecteurs embarqués sur la page du TFE.
</p>
<p class="param-note">
⚠ L'activation nécessite un quota d'upload suffisant sur l'instance PeerTube.
Laissez désactivé jusqu'à obtention du quota.
</p>
<div class="param-smtp-status">
<?php if ($peerTubeConfigured): ?>
<span class="param-badge-ok">✓ Configuré</span>
<span><?= htmlspecialchars($peerTubeSettings['instance_url']) ?></span>
<?php else: ?>
<span class="param-badge-warn">✗ Non configuré</span>
<?php endif; ?>
</div>
<form method="post" action="actions/settings.php" class="param-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="peertube">
<fieldset>
<legend>Activation</legend>
<label class="param-checkbox">
<input type="checkbox" name="peertube_upload_enabled" value="1"
<?= $peerTubeEnabled ? 'checked' : '' ?>>
<span>
<strong>Activer l'upload PeerTube</strong><br>
<small>Lorsqu'activé, les fichiers vidéo et audio soumis via le formulaire sont uploadés
sur l'instance PeerTube plutôt que stockés localement sur le serveur.</small>
</span>
</label>
</fieldset>
<div class="param-grid">
<div>
<label for="peertube_instance_url">URL de l'instance PeerTube</label>
<input type="url" id="peertube_instance_url" name="peertube_instance_url"
value="<?= htmlspecialchars($peerTubeSettings['instance_url']) ?>"
placeholder="https://peertube.example.com">
<small>Sans slash final. Ex : <code>https://peertube.erg.be</code></small>
</div>
<div>
<label for="peertube_username">Nom d'utilisateur</label>
<input type="text" id="peertube_username" name="peertube_username"
value="<?= htmlspecialchars($peerTubeSettings['username']) ?>"
autocomplete="username">
</div>
<div>
<label for="peertube_password">Mot de passe</label>
<input type="password" id="peertube_password" name="peertube_password"
value=""
autocomplete="new-password"
placeholder="Laissez vide pour ne pas modifier">
</div>
<div>
<label for="peertube_channel_id">ID de la chaîne</label>
<input type="number" id="peertube_channel_id" name="peertube_channel_id"
value="<?= (int)$peerTubeSettings['channel_id'] ?>"
min="1">
<small>Identifiant numérique de la chaîne PeerTube cible.</small>
</div>
<div>
<label for="peertube_privacy">Visibilité des vidéos</label>
<select id="peertube_privacy" name="peertube_privacy">
<option value="1" <?= (int)$peerTubeSettings['privacy'] === 1 ? 'selected' : '' ?>>Publique</option>
<option value="2" <?= (int)$peerTubeSettings['privacy'] === 2 ? 'selected' : '' ?>>Non listée</option>
<option value="3" <?= (int)$peerTubeSettings['privacy'] === 3 ? 'selected' : '' ?>>Privée</option>
</select>
</div>
</div>
<button type="submit" class="btn btn--primary">Enregistrer</button>
</form>
</section>
<!-- ══════════════════════════════════════════════════════════════
COMPTE ADMINISTRATEUR
══════════════════════════════════════════════════════════════ -->

View File

@@ -443,6 +443,7 @@
$isAudio = in_array($ext, ['mp3','ogg','oga','wav','flac','aac','m4a'], true) || $fileType === 'audio';
$isPdf = ($ext === 'pdf') || $fileType === 'main';
$isWebsite = ($fileType === 'website');
$isPeerTube = ($isExternalUrl && str_contains($filePath, '/videos/watch/'));
$isOther = !($isImage || $isVideo || $isAudio || $isPdf || $isWebsite);
$_vttPath = null;
@@ -485,6 +486,22 @@
<img src="<?= $mediaUrl ?>"
alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>">
<?php elseif ($isVideo): ?>
<?php if ($isPeerTube): ?>
<iframe src="<?= $mediaUrl ?>embed"
width="100%" height="400px"
style="border:none"
title="<?= $fileName ?>"
sandbox="allow-same-origin allow-scripts"
allowfullscreen
loading="lazy">
</iframe>
<p class="tfe-pdf-fallback">
<a href="<?= $mediaUrl ?>" target="_blank" rel="noopener">
Ouvrir dans PeerTube
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
</p>
<?php else: ?>
<video width="100%" controls>
<source src="<?= $mediaUrl ?>" type="video/<?= htmlspecialchars($ext === 'mov' ? 'mp4' : $ext) ?>">
<?php if ($_vttPath): ?>
@@ -493,7 +510,23 @@
srclang="fr" label="Sous-titres" default>
<?php endif; ?>
</video>
<?php endif; ?>
<?php elseif ($isAudio): ?>
<?php if ($isPeerTube): ?>
<iframe src="<?= $mediaUrl ?>embed"
width="100%" height="170px"
style="border:none"
title="<?= $fileName ?>"
sandbox="allow-same-origin allow-scripts"
loading="lazy">
</iframe>
<p class="tfe-pdf-fallback">
<a href="<?= $mediaUrl ?>" target="_blank" rel="noopener">
Ouvrir dans PeerTube
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
</p>
<?php else: ?>
<audio controls class="tfe-audio">
<source src="<?= $mediaUrl ?>" type="audio/<?= htmlspecialchars(match($ext) {
'mp3' => 'mpeg',
@@ -506,6 +539,7 @@
}) ?>">
Votre navigateur ne supporte pas la lecture audio.
</audio>
<?php endif; ?>
<?php else: /* other — download only */ ?>
<div class="tfe-download-file">
<a href="<?= $mediaUrl ?>&download=1" class="tfe-download-link">