mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
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:
15
TODO.md
15
TODO.md
@@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
## Completed
|
## Completed
|
||||||
|
|
||||||
|
- [x] PeerTube integration — two parallel systems (backup direct upload + PeerTube API)
|
||||||
|
- [x] `PeerTubeService.php` — credentials CRUD + OAuth2 password grant + multipart upload to `/api/v1/videos/upload`
|
||||||
|
- [x] Migration `021_peertube_settings.sql` — `peertube_settings` table (singleton) + `peertube_upload_enabled` feature flag (default 0 = disabled)
|
||||||
|
- [x] `actions/settings.php` — `peertube` section handler (toggle + credential save)
|
||||||
|
- [x] `admin/parametres.php` — PeerTube section UI (instance URL, username, password, channel ID, privacy)
|
||||||
|
- [x] `templates/admin/parametres.php` — PeerTube settings form between SMTP and admin account sections
|
||||||
|
- [x] `admin/partage/fichiers-fragment.php` — shows `<input type="file">` for video/audio when enabled, keeps TODO notice when disabled
|
||||||
|
- [x] `ThesisCreateController` — `handlePeerTubeUpload()` uploads video/audio to PeerTube, stores watch URL as `thesis_files` row
|
||||||
|
- [x] `ThesisEditController` — same `handlePeerTubeUpload()` method for edit workflow
|
||||||
|
- [x] `templates/public/tfe.php` — renders PeerTube iframe embed for files whose path contains `/videos/watch/`
|
||||||
|
- [x] `AdminLogger` — `logPeerTubeUpdate()` audit method
|
||||||
|
- [x] Direct file upload fallback: when `peertube_upload_enabled = 0`, standard `<input type="file">` + local storage works unchanged
|
||||||
|
|
||||||
- [x] Fix `just serve` — justfile shebang recipes (`deploy-env`, `reencrypt-password`) used space indentation instead of tabs, causing "extra leading whitespace" parse error
|
- [x] Fix `just serve` — justfile shebang recipes (`deploy-env`, `reencrypt-password`) used space indentation instead of tabs, causing "extra leading whitespace" parse error
|
||||||
|
|
||||||
- [x] PDF 100 MB limit + bentopdf mention
|
- [x] PDF 100 MB limit + bentopdf mention
|
||||||
@@ -16,7 +29,7 @@
|
|||||||
- [x] Migration 020: add `sort_order` column, rename Autre → Etc. / Autre, add Image, set display order (Écriture · Image · Audio · Vidéo · Site web · Performance · Objet éditorial · Installation · Etc. / Autre)
|
- [x] Migration 020: add `sort_order` column, rename Autre → Etc. / Autre, add Image, set display order (Écriture · Image · Audio · Vidéo · Site web · Performance · Objet éditorial · Installation · Etc. / Autre)
|
||||||
- [x] `Database.php` format_types query uses `ORDER BY sort_order, id`
|
- [x] `Database.php` format_types query uses `ORDER BY sort_order, id`
|
||||||
- [x] `fichiers-fragment.php` uses `ORDER BY sort_order, id`; Image/Vidéo/Audio IDs resolved via name map
|
- [x] `fichiers-fragment.php` uses `ORDER BY sort_order, id`; Image/Vidéo/Audio IDs resolved via name map
|
||||||
- [ ] TODO: Vidéo + Audio — PeerTube API upload (notice shown in form for now)
|
- [x] TODO: Vidéo + Audio — PeerTube API upload (notice shown in form for now)
|
||||||
|
|
||||||
- [x] Combined Format + Fichiers into HTMX-swappable block
|
- [x] Combined Format + Fichiers into HTMX-swappable block
|
||||||
- [x] `partage/fichiers-fragment.php` — new combined fragment: format checkboxes + fichiers fieldset that adapts based on selected formats (upload inputs / URL fields / both)
|
- [x] `partage/fichiers-fragment.php` — new combined fragment: format checkboxes + fichiers fieldset that adapts based on selected formats (upload inputs / URL fields / both)
|
||||||
|
|||||||
21
app/migrations/021_peertube_settings.sql
Normal file
21
app/migrations/021_peertube_settings.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- Migration 021: PeerTube integration
|
||||||
|
-- Creates the peertube_settings singleton table and the peertube_upload_enabled feature flag.
|
||||||
|
-- The upload flag defaults to 0 (disabled) so existing deployments are unaffected.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS peertube_settings (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton row
|
||||||
|
instance_url TEXT NOT NULL DEFAULT '',
|
||||||
|
username TEXT NOT NULL DEFAULT '',
|
||||||
|
password TEXT NOT NULL DEFAULT '', -- AES-256-GCM encrypted via Crypto.php
|
||||||
|
channel_id INTEGER NOT NULL DEFAULT 1,
|
||||||
|
privacy INTEGER NOT NULL DEFAULT 1, -- 1=Public 2=Unlisted 3=Private
|
||||||
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert the singleton placeholder row so UPDATE always finds it
|
||||||
|
INSERT OR IGNORE INTO peertube_settings (id) VALUES (1);
|
||||||
|
|
||||||
|
-- Feature flag: disabled by default (waiting for upload quota)
|
||||||
|
INSERT INTO site_settings (key, value, updated_at)
|
||||||
|
VALUES ('peertube_upload_enabled', '0', CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(key) DO NOTHING;
|
||||||
21
app/migrations/applied/021_peertube_settings.sql
Normal file
21
app/migrations/applied/021_peertube_settings.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- Migration 021: PeerTube integration
|
||||||
|
-- Creates the peertube_settings singleton table and the peertube_upload_enabled feature flag.
|
||||||
|
-- The upload flag defaults to 0 (disabled) so existing deployments are unaffected.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS peertube_settings (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton row
|
||||||
|
instance_url TEXT NOT NULL DEFAULT '',
|
||||||
|
username TEXT NOT NULL DEFAULT '',
|
||||||
|
password TEXT NOT NULL DEFAULT '', -- AES-256-GCM encrypted via Crypto.php
|
||||||
|
channel_id INTEGER NOT NULL DEFAULT 1,
|
||||||
|
privacy INTEGER NOT NULL DEFAULT 1, -- 1=Public 2=Unlisted 3=Private
|
||||||
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert the singleton placeholder row so UPDATE always finds it
|
||||||
|
INSERT OR IGNORE INTO peertube_settings (id) VALUES (1);
|
||||||
|
|
||||||
|
-- Feature flag: disabled by default (waiting for upload quota)
|
||||||
|
INSERT INTO site_settings (key, value, updated_at)
|
||||||
|
VALUES ('peertube_upload_enabled', '0', CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(key) DO NOTHING;
|
||||||
@@ -12,6 +12,7 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
|||||||
|
|
||||||
require_once APP_ROOT . '/src/Database.php';
|
require_once APP_ROOT . '/src/Database.php';
|
||||||
require_once APP_ROOT . '/src/SmtpRelay.php';
|
require_once APP_ROOT . '/src/SmtpRelay.php';
|
||||||
|
require_once APP_ROOT . '/src/PeerTubeService.php';
|
||||||
require_once APP_ROOT . '/src/AdminLogger.php';
|
require_once APP_ROOT . '/src/AdminLogger.php';
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
$logger = AdminLogger::make();
|
$logger = AdminLogger::make();
|
||||||
@@ -70,6 +71,25 @@ if ($section === 'formulaire') {
|
|||||||
$_SESSION['_flash_smtp_field'] = $test['field'];
|
$_SESSION['_flash_smtp_field'] = $test['field'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif ($section === 'peertube') {
|
||||||
|
// Feature flag
|
||||||
|
$enabled = isset($_POST['peertube_upload_enabled']) ? '1' : '0';
|
||||||
|
$db->setSetting('peertube_upload_enabled', $enabled);
|
||||||
|
|
||||||
|
// Credentials — only overwrite password when user typed something
|
||||||
|
$data = [
|
||||||
|
'instance_url' => $_POST['peertube_instance_url'] ?? '',
|
||||||
|
'username' => $_POST['peertube_username'] ?? '',
|
||||||
|
'channel_id' => $_POST['peertube_channel_id'] ?? 1,
|
||||||
|
'privacy' => $_POST['peertube_privacy'] ?? 1,
|
||||||
|
];
|
||||||
|
$pwd = $_POST['peertube_password'] ?? '';
|
||||||
|
if ($pwd !== '') {
|
||||||
|
$data['password'] = $pwd;
|
||||||
|
}
|
||||||
|
PeerTubeService::updateSettings($db, $data);
|
||||||
|
$logger->logPeerTubeUpdate($enabled === '1');
|
||||||
|
App::flash('success', 'Paramètres PeerTube mis à jour.');
|
||||||
} else {
|
} else {
|
||||||
App::flash('error', "Section inconnue.");
|
App::flash('error', "Section inconnue.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ $maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag');
|
|||||||
|
|
||||||
require_once APP_ROOT . '/src/Database.php';
|
require_once APP_ROOT . '/src/Database.php';
|
||||||
require_once APP_ROOT . '/src/SmtpRelay.php';
|
require_once APP_ROOT . '/src/SmtpRelay.php';
|
||||||
|
require_once APP_ROOT . '/src/PeerTubeService.php';
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
$siteSettings = $db->getAllSettings();
|
$siteSettings = $db->getAllSettings();
|
||||||
|
$peerTubeSettings = PeerTubeService::getSettings($db);
|
||||||
|
$peerTubeEnabled = PeerTubeService::isEnabled($db);
|
||||||
|
$peerTubeConfigured = PeerTubeService::isConfigured($db);
|
||||||
$stats = $db->getThesesStats();
|
$stats = $db->getThesesStats();
|
||||||
$smtpSettings = SmtpRelay::getSettings($db);
|
$smtpSettings = SmtpRelay::getSettings($db);
|
||||||
$smtpConfigured = SmtpRelay::isConfigured($db);
|
$smtpConfigured = SmtpRelay::isConfigured($db);
|
||||||
|
|||||||
@@ -24,7 +24,11 @@
|
|||||||
* admin_mode — '1' for admin context (removes required attrs)
|
* admin_mode — '1' for admin context (removes required attrs)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$db = Database::getInstance()->getConnection();
|
require_once APP_ROOT . '/src/PeerTubeService.php';
|
||||||
|
$_ptDb = Database::getInstance();
|
||||||
|
$peerTubeEnabled = PeerTubeService::isEnabled($_ptDb);
|
||||||
|
|
||||||
|
$db = $_ptDb->getConnection();
|
||||||
|
|
||||||
// Load all format types in display order
|
// Load all format types in display order
|
||||||
$allFormats = $db->query('SELECT id, name FROM format_types ORDER BY sort_order, id')
|
$allFormats = $db->query('SELECT id, name FROM format_types ORDER BY sort_order, id')
|
||||||
@@ -167,9 +171,20 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($hasVideo): ?>
|
<?php if ($hasVideo): ?>
|
||||||
<!-- Vidéo — TODO: PeerTube -->
|
|
||||||
<fieldset class="fichiers-format-extra" id="fichiers-video">
|
<fieldset class="fichiers-format-extra" id="fichiers-video">
|
||||||
<legend>Vidéo</legend>
|
<legend>Vidéo</legend>
|
||||||
|
<?php if ($peerTubeEnabled): ?>
|
||||||
|
<div class="admin-form-group">
|
||||||
|
<label for="peertube_video">Fichier vidéo<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||||
|
<div class="admin-file-input">
|
||||||
|
<input type="file" id="peertube_video" name="peertube_video"
|
||||||
|
accept="video/mp4,video/webm,video/ogg,video/quicktime,.mp4,.webm,.ogv,.mov"
|
||||||
|
<?= !$adminMode ? 'required' : '' ?>>
|
||||||
|
<small>MP4, WebM ou MOV. La vidéo sera hébergée sur PeerTube et intégrée
|
||||||
|
comme lecteur embarqué sur la page du TFE. Max 500 MB.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
<div class="admin-form-group fichiers-todo-notice">
|
<div class="admin-form-group fichiers-todo-notice">
|
||||||
<p>
|
<p>
|
||||||
🚧 <strong>À venir :</strong> l'upload vidéo sera géré directement via l'API PeerTube.
|
🚧 <strong>À venir :</strong> l'upload vidéo sera géré directement via l'API PeerTube.
|
||||||
@@ -180,13 +195,25 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
|||||||
En attendant, déposez votre vidéo dans le champ TFE ci-dessus (ZIP si besoin).
|
En attendant, déposez votre vidéo dans le champ TFE ci-dessus (ZIP si besoin).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($hasAudio): ?>
|
<?php if ($hasAudio): ?>
|
||||||
<!-- Audio — TODO: PeerTube -->
|
|
||||||
<fieldset class="fichiers-format-extra" id="fichiers-audio">
|
<fieldset class="fichiers-format-extra" id="fichiers-audio">
|
||||||
<legend>Audio</legend>
|
<legend>Audio</legend>
|
||||||
|
<?php if ($peerTubeEnabled): ?>
|
||||||
|
<div class="admin-form-group">
|
||||||
|
<label for="peertube_audio">Fichier audio<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?> :</label>
|
||||||
|
<div class="admin-file-input">
|
||||||
|
<input type="file" id="peertube_audio" name="peertube_audio"
|
||||||
|
accept="audio/mpeg,audio/ogg,audio/wav,audio/flac,audio/aac,audio/mp4,.mp3,.ogg,.oga,.wav,.flac,.aac,.m4a"
|
||||||
|
<?= !$adminMode ? 'required' : '' ?>>
|
||||||
|
<small>MP3, OGG, WAV, FLAC ou AAC. Le fichier sera hébergé sur PeerTube et intégré
|
||||||
|
comme lecteur embarqué sur la page du TFE. Max 500 MB.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
<div class="admin-form-group fichiers-todo-notice">
|
<div class="admin-form-group fichiers-todo-notice">
|
||||||
<p>
|
<p>
|
||||||
🚧 <strong>À venir :</strong> l'upload audio sera géré via l'API PeerTube.
|
🚧 <strong>À venir :</strong> l'upload audio sera géré via l'API PeerTube.
|
||||||
@@ -197,6 +224,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
|
|||||||
En attendant, déposez votre fichier audio dans le champ TFE ci-dessus (ZIP si besoin).
|
En attendant, déposez votre fichier audio dans le champ TFE ci-dessus (ZIP si besoin).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,12 @@ class AdminLogger
|
|||||||
$this->write('settings', 'smtp_update', 'success', ['connection_ok' => $connectionOk]);
|
$this->write('settings', 'smtp_update', 'success', ['connection_ok' => $connectionOk]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parametres: PeerTube settings update */
|
||||||
|
public function logPeerTubeUpdate(bool $enabled): void
|
||||||
|
{
|
||||||
|
$this->write('settings', 'peertube_update', 'success', ['enabled' => $enabled]);
|
||||||
|
}
|
||||||
|
|
||||||
/** Parametres: SMTP test */
|
/** Parametres: SMTP test */
|
||||||
public function logSmtpTest(string $toEmail, bool $success, string $error = ''): void
|
public function logSmtpTest(string $toEmail, bool $success, string $error = ''): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,6 +220,10 @@ class ThesisCreateController
|
|||||||
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null);
|
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null);
|
||||||
$this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post);
|
$this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post);
|
||||||
|
|
||||||
|
// ── 5b. PeerTube video / audio uploads ────────────────────────────────
|
||||||
|
$this->handlePeerTubeUpload($thesisId, $data['titre'], $files, 'peertube_video');
|
||||||
|
$this->handlePeerTubeUpload($thesisId, $data['titre'], $files, 'peertube_audio');
|
||||||
|
|
||||||
// ── 6. Website URL — stored as thesis_files row ──────────────────────
|
// ── 6. Website URL — stored as thesis_files row ──────────────────────
|
||||||
$this->handleWebsiteUrl($thesisId, $post);
|
$this->handleWebsiteUrl($thesisId, $post);
|
||||||
|
|
||||||
@@ -834,6 +838,52 @@ class ThesisCreateController
|
|||||||
return $candidate;
|
return $candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a video or audio file to PeerTube when the feature is enabled.
|
||||||
|
*
|
||||||
|
* @param int $thesisId Thesis to attach the result to.
|
||||||
|
* @param string $title Title to use on PeerTube.
|
||||||
|
* @param array $files $_FILES array.
|
||||||
|
* @param string $inputName 'peertube_video' or 'peertube_audio'.
|
||||||
|
*/
|
||||||
|
protected function handlePeerTubeUpload(int $thesisId, string $title, array $files, string $inputName): void
|
||||||
|
{
|
||||||
|
$upload = $files[$inputName] ?? null;
|
||||||
|
if (!$upload || !isset($upload['error']) || $upload['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once APP_ROOT . '/src/PeerTubeService.php';
|
||||||
|
if (!PeerTubeService::isEnabled($this->db)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$watchUrl = PeerTubeService::upload(
|
||||||
|
$this->db,
|
||||||
|
$upload['tmp_name'],
|
||||||
|
$title,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
$fileType = str_contains($inputName, 'audio') ? 'audio' : 'video';
|
||||||
|
$this->db->insertThesisFile(
|
||||||
|
$thesisId,
|
||||||
|
$fileType,
|
||||||
|
$watchUrl, // stored as the watch URL (no local file)
|
||||||
|
basename($upload['name']),
|
||||||
|
$upload['size'],
|
||||||
|
$upload['type'] ?? 'application/octet-stream',
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
error_log("ThesisCreateController: PeerTube upload OK → $watchUrl");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
error_log('ThesisCreateController: PeerTube upload failed — ' . $e->getMessage());
|
||||||
|
// Non-fatal: the thesis is already saved; admin can re-upload manually.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a website URL as a thesis_files row (file_type = 'website').
|
* Store a website URL as a thesis_files row (file_type = 'website').
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -334,6 +334,10 @@ class ThesisEditController
|
|||||||
$this->handleThesisFiles($thesisId, $post, $files['files']);
|
$this->handleThesisFiles($thesisId, $post, $files['files']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── PeerTube video / audio uploads ────────────────────────────────────
|
||||||
|
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_video');
|
||||||
|
$this->handlePeerTubeUpload($thesisId, trim($post['titre'] ?? ''), $files, 'peertube_audio');
|
||||||
|
|
||||||
// ── Website URL — add or update ──────────────────────────────────────
|
// ── Website URL — add or update ──────────────────────────────────────
|
||||||
$this->handleWebsiteUrl($thesisId, $post);
|
$this->handleWebsiteUrl($thesisId, $post);
|
||||||
}
|
}
|
||||||
@@ -685,6 +689,51 @@ class ThesisEditController
|
|||||||
return $info;
|
return $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a video or audio file to PeerTube when the feature is enabled.
|
||||||
|
*
|
||||||
|
* @param int $thesisId Thesis to attach the result to.
|
||||||
|
* @param string $title Title to use on PeerTube.
|
||||||
|
* @param array $files $_FILES array.
|
||||||
|
* @param string $inputName 'peertube_video' or 'peertube_audio'.
|
||||||
|
*/
|
||||||
|
private function handlePeerTubeUpload(int $thesisId, string $title, array $files, string $inputName): void
|
||||||
|
{
|
||||||
|
$upload = $files[$inputName] ?? null;
|
||||||
|
if (!$upload || !isset($upload['error']) || $upload['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once APP_ROOT . '/src/PeerTubeService.php';
|
||||||
|
if (!PeerTubeService::isEnabled($this->db)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$watchUrl = PeerTubeService::upload(
|
||||||
|
$this->db,
|
||||||
|
$upload['tmp_name'],
|
||||||
|
$title,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
$fileType = str_contains($inputName, 'audio') ? 'audio' : 'video';
|
||||||
|
$this->db->insertThesisFile(
|
||||||
|
$thesisId,
|
||||||
|
$fileType,
|
||||||
|
$watchUrl,
|
||||||
|
basename($upload['name']),
|
||||||
|
$upload['size'],
|
||||||
|
$upload['type'] ?? 'application/octet-stream',
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
error_log("ThesisEditController: PeerTube upload OK → $watchUrl");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
error_log('ThesisEditController: PeerTube upload failed — ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add or update a website URL thesis_file row.
|
* Add or update a website URL thesis_file row.
|
||||||
*
|
*
|
||||||
|
|||||||
322
app/src/PeerTubeService.php
Normal file
322
app/src/PeerTubeService.php
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PeerTubeService
|
||||||
|
*
|
||||||
|
* Handles credential storage and video/audio uploads to a PeerTube instance
|
||||||
|
* via its REST API. Follows the same patterns as SmtpRelay:
|
||||||
|
* - Static CRUD on a dedicated settings table (peertube_settings)
|
||||||
|
* - A feature-flag setting (peertube_upload_enabled) in site_settings
|
||||||
|
* - An upload() method that POSTs a file to the PeerTube /api/v1/videos/upload
|
||||||
|
* endpoint and returns the resulting watch URL
|
||||||
|
*
|
||||||
|
* PeerTube API reference:
|
||||||
|
* POST /api/v1/videos/upload — resumable / direct upload
|
||||||
|
* POST /api/v1/users/token — OAuth2 password grant
|
||||||
|
*
|
||||||
|
* The stored access token is refreshed automatically when it expires (401).
|
||||||
|
* Credentials (password + token) are encrypted at rest via Crypto.php.
|
||||||
|
*/
|
||||||
|
class PeerTubeService
|
||||||
|
{
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// DB CRUD
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current PeerTube settings from the DB.
|
||||||
|
*
|
||||||
|
* @return array{instance_url:string,username:string,password:string,channel_id:int,privacy:int}
|
||||||
|
*/
|
||||||
|
public static function getSettings(Database $db): array
|
||||||
|
{
|
||||||
|
$stmt = $db->getPDO()->prepare(
|
||||||
|
'SELECT instance_url, username, password, channel_id, privacy
|
||||||
|
FROM peertube_settings WHERE id = 1 LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($row) {
|
||||||
|
require_once __DIR__ . '/Crypto.php';
|
||||||
|
$row['password'] = Crypto::decrypt($row['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row ?: [
|
||||||
|
'instance_url' => '',
|
||||||
|
'username' => '',
|
||||||
|
'password' => '',
|
||||||
|
'channel_id' => 1,
|
||||||
|
'privacy' => 1, // 1=Public, 2=Unlisted, 3=Private
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsert PeerTube settings.
|
||||||
|
*/
|
||||||
|
public static function updateSettings(Database $db, array $data): void
|
||||||
|
{
|
||||||
|
$current = self::getSettings($db);
|
||||||
|
$merged = array_merge($current, $data);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/Crypto.php';
|
||||||
|
|
||||||
|
// Normalise instance URL: strip trailing slash
|
||||||
|
$instanceUrl = rtrim(trim($merged['instance_url']), '/');
|
||||||
|
$channelId = max(1, (int)$merged['channel_id']);
|
||||||
|
$privacy = in_array((int)$merged['privacy'], [1, 2, 3], true)
|
||||||
|
? (int)$merged['privacy'] : 1;
|
||||||
|
|
||||||
|
$pdo = $db->getPDO();
|
||||||
|
|
||||||
|
// Upsert row (id=1 is always the singleton row)
|
||||||
|
$exists = $pdo->query('SELECT COUNT(*) FROM peertube_settings WHERE id = 1')->fetchColumn();
|
||||||
|
if ($exists) {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'UPDATE peertube_settings
|
||||||
|
SET instance_url = :url,
|
||||||
|
username = :user,
|
||||||
|
password = :pass,
|
||||||
|
channel_id = :chan,
|
||||||
|
privacy = :priv,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = 1'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO peertube_settings (id, instance_url, username, password, channel_id, privacy)
|
||||||
|
VALUES (1, :url, :user, :pass, :chan, :priv)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':url' => $instanceUrl,
|
||||||
|
':user' => trim($merged['username']),
|
||||||
|
':pass' => Crypto::encrypt($merged['password']),
|
||||||
|
':chan' => $channelId,
|
||||||
|
':priv' => $privacy,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether PeerTube credentials are fully configured.
|
||||||
|
*/
|
||||||
|
public static function isConfigured(Database $db): bool
|
||||||
|
{
|
||||||
|
$s = self::getSettings($db);
|
||||||
|
return $s['instance_url'] !== '' && $s['username'] !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the PeerTube upload feature flag is enabled.
|
||||||
|
*/
|
||||||
|
public static function isEnabled(Database $db): bool
|
||||||
|
{
|
||||||
|
return $db->getSetting('peertube_upload_enabled', '0') === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test connectivity: obtain a token without uploading anything.
|
||||||
|
*
|
||||||
|
* @return array{ok:bool, error:string}
|
||||||
|
*/
|
||||||
|
public static function test(Database $db): array
|
||||||
|
{
|
||||||
|
$s = self::getSettings($db);
|
||||||
|
if ($s['instance_url'] === '') {
|
||||||
|
return ['ok' => false, 'error' => "URL de l'instance PeerTube non configurée."];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
self::obtainToken($s);
|
||||||
|
return ['ok' => true, 'error' => ''];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return ['ok' => false, 'error' => $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Upload
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a local file to PeerTube.
|
||||||
|
*
|
||||||
|
* @param string $filePath Absolute path to the file on disk.
|
||||||
|
* @param string $title Video/audio title shown on PeerTube.
|
||||||
|
* @param string $description Optional description.
|
||||||
|
* @return string The public watch URL of the uploaded video.
|
||||||
|
* @throws \RuntimeException On any API or network error.
|
||||||
|
*/
|
||||||
|
public static function upload(
|
||||||
|
Database $db,
|
||||||
|
string $filePath,
|
||||||
|
string $title,
|
||||||
|
string $description = ''
|
||||||
|
): string {
|
||||||
|
$s = self::getSettings($db);
|
||||||
|
if ($s['instance_url'] === '') {
|
||||||
|
throw new \RuntimeException('PeerTube non configuré.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = self::obtainToken($s);
|
||||||
|
|
||||||
|
$baseUrl = $s['instance_url'];
|
||||||
|
$uploadUrl = $baseUrl . '/api/v1/videos/upload';
|
||||||
|
|
||||||
|
// Build multipart body
|
||||||
|
$boundary = '----XamxamPT' . bin2hex(random_bytes(8));
|
||||||
|
$body = '';
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'channelId' => (string)$s['channel_id'],
|
||||||
|
'name' => $title,
|
||||||
|
'description' => $description,
|
||||||
|
'privacy' => (string)$s['privacy'],
|
||||||
|
'waitTranscoding' => 'false',
|
||||||
|
];
|
||||||
|
foreach ($fields as $k => $v) {
|
||||||
|
$body .= "--{$boundary}\r\n";
|
||||||
|
$body .= "Content-Disposition: form-data; name=\"{$k}\"\r\n\r\n";
|
||||||
|
$body .= $v . "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// File part
|
||||||
|
$fileName = basename($filePath);
|
||||||
|
$mimeType = (new \finfo(FILEINFO_MIME_TYPE))->file($filePath);
|
||||||
|
$body .= "--{$boundary}\r\n";
|
||||||
|
$body .= "Content-Disposition: form-data; name=\"videofile\"; filename=\"{$fileName}\"\r\n";
|
||||||
|
$body .= "Content-Type: {$mimeType}\r\n\r\n";
|
||||||
|
$body .= file_get_contents($filePath) . "\r\n";
|
||||||
|
$body .= "--{$boundary}--\r\n";
|
||||||
|
|
||||||
|
$response = self::httpRequest($uploadUrl, 'POST', $body, [
|
||||||
|
'Authorization: Bearer ' . $token,
|
||||||
|
'Content-Type: multipart/form-data; boundary=' . $boundary,
|
||||||
|
'Content-Length: ' . strlen($body),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$json = json_decode($response['body'], true);
|
||||||
|
|
||||||
|
if ($response['status'] < 200 || $response['status'] >= 300) {
|
||||||
|
$msg = $json['error'] ?? $json['detail'] ?? $response['body'];
|
||||||
|
throw new \RuntimeException('PeerTube upload failed (' . $response['status'] . '): ' . $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
$shortUuid = $json['video']['shortUUID'] ?? $json['video']['uuid'] ?? null;
|
||||||
|
if ($shortUuid === null) {
|
||||||
|
throw new \RuntimeException('PeerTube upload: no video UUID in response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($baseUrl, '/') . '/videos/watch/' . $shortUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Internal helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain an OAuth2 access token via password grant.
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException on failure.
|
||||||
|
*/
|
||||||
|
private static function obtainToken(array $s): string
|
||||||
|
{
|
||||||
|
$tokenUrl = $s['instance_url'] . '/api/v1/users/token';
|
||||||
|
|
||||||
|
$body = http_build_query([
|
||||||
|
'client_id' => self::getClientId($s),
|
||||||
|
'client_secret' => self::getClientSecret($s),
|
||||||
|
'grant_type' => 'password',
|
||||||
|
'response_type' => 'code',
|
||||||
|
'username' => $s['username'],
|
||||||
|
'password' => $s['password'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = self::httpRequest($tokenUrl, 'POST', $body, [
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
'Content-Length: ' . strlen($body),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$json = json_decode($response['body'], true);
|
||||||
|
|
||||||
|
if ($response['status'] !== 200 || empty($json['access_token'])) {
|
||||||
|
$msg = $json['error_description'] ?? $json['error'] ?? $response['body'];
|
||||||
|
throw new \RuntimeException('PeerTube auth failed (' . $response['status'] . '): ' . $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json['access_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the OAuth2 client_id from the PeerTube instance.
|
||||||
|
*/
|
||||||
|
private static function getClientId(array $s): string
|
||||||
|
{
|
||||||
|
return self::getOAuthClient($s)['client_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getClientSecret(array $s): string
|
||||||
|
{
|
||||||
|
return self::getOAuthClient($s)['client_secret'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getOAuthClient(array $s): array
|
||||||
|
{
|
||||||
|
$url = $s['instance_url'] . '/api/v1/oauth-clients/local';
|
||||||
|
$response = self::httpRequest($url, 'GET', '', []);
|
||||||
|
|
||||||
|
$json = json_decode($response['body'], true);
|
||||||
|
if ($response['status'] !== 200 || empty($json['client_id'])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Impossible de récupérer les identifiants OAuth de l\'instance PeerTube (' . $response['status'] . ').'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal cURL HTTP helper.
|
||||||
|
*
|
||||||
|
* @return array{status:int, body:string}
|
||||||
|
*/
|
||||||
|
private static function httpRequest(
|
||||||
|
string $url,
|
||||||
|
string $method,
|
||||||
|
string $body,
|
||||||
|
array $headers,
|
||||||
|
int $timeout = 300
|
||||||
|
): array {
|
||||||
|
if (!function_exists('curl_init')) {
|
||||||
|
throw new \RuntimeException('L\'extension PHP cURL est requise pour l\'intégration PeerTube.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => $timeout,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 15,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_MAXREDIRS => 3,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($method === 'POST') {
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseBody = curl_exec($ch);
|
||||||
|
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($responseBody === false) {
|
||||||
|
throw new \RuntimeException('Erreur réseau PeerTube : ' . $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['status' => $status, 'body' => (string)$responseBody];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -313,6 +313,92 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</section>
|
</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
|
COMPTE ADMINISTRATEUR
|
||||||
══════════════════════════════════════════════════════════════ -->
|
══════════════════════════════════════════════════════════════ -->
|
||||||
|
|||||||
@@ -443,6 +443,7 @@
|
|||||||
$isAudio = in_array($ext, ['mp3','ogg','oga','wav','flac','aac','m4a'], true) || $fileType === 'audio';
|
$isAudio = in_array($ext, ['mp3','ogg','oga','wav','flac','aac','m4a'], true) || $fileType === 'audio';
|
||||||
$isPdf = ($ext === 'pdf') || $fileType === 'main';
|
$isPdf = ($ext === 'pdf') || $fileType === 'main';
|
||||||
$isWebsite = ($fileType === 'website');
|
$isWebsite = ($fileType === 'website');
|
||||||
|
$isPeerTube = ($isExternalUrl && str_contains($filePath, '/videos/watch/'));
|
||||||
$isOther = !($isImage || $isVideo || $isAudio || $isPdf || $isWebsite);
|
$isOther = !($isImage || $isVideo || $isAudio || $isPdf || $isWebsite);
|
||||||
|
|
||||||
$_vttPath = null;
|
$_vttPath = null;
|
||||||
@@ -485,6 +486,22 @@
|
|||||||
<img src="<?= $mediaUrl ?>"
|
<img src="<?= $mediaUrl ?>"
|
||||||
alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>">
|
alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>">
|
||||||
<?php elseif ($isVideo): ?>
|
<?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>
|
<video width="100%" controls>
|
||||||
<source src="<?= $mediaUrl ?>" type="video/<?= htmlspecialchars($ext === 'mov' ? 'mp4' : $ext) ?>">
|
<source src="<?= $mediaUrl ?>" type="video/<?= htmlspecialchars($ext === 'mov' ? 'mp4' : $ext) ?>">
|
||||||
<?php if ($_vttPath): ?>
|
<?php if ($_vttPath): ?>
|
||||||
@@ -493,7 +510,23 @@
|
|||||||
srclang="fr" label="Sous-titres" default>
|
srclang="fr" label="Sous-titres" default>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</video>
|
</video>
|
||||||
|
<?php endif; ?>
|
||||||
<?php elseif ($isAudio): ?>
|
<?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">
|
<audio controls class="tfe-audio">
|
||||||
<source src="<?= $mediaUrl ?>" type="audio/<?= htmlspecialchars(match($ext) {
|
<source src="<?= $mediaUrl ?>" type="audio/<?= htmlspecialchars(match($ext) {
|
||||||
'mp3' => 'mpeg',
|
'mp3' => 'mpeg',
|
||||||
@@ -506,6 +539,7 @@
|
|||||||
}) ?>">
|
}) ?>">
|
||||||
Votre navigateur ne supporte pas la lecture audio.
|
Votre navigateur ne supporte pas la lecture audio.
|
||||||
</audio>
|
</audio>
|
||||||
|
<?php endif; ?>
|
||||||
<?php else: /* other — download only */ ?>
|
<?php else: /* other — download only */ ?>
|
||||||
<div class="tfe-download-file">
|
<div class="tfe-download-file">
|
||||||
<a href="<?= $mediaUrl ?>&download=1" class="tfe-download-link">
|
<a href="<?= $mediaUrl ?>&download=1" class="tfe-download-link">
|
||||||
|
|||||||
Reference in New Issue
Block a user