From ac0008df6cc5c650e16725999ae43ae55713e885 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Thu, 7 May 2026 21:24:46 +0200 Subject: [PATCH] Add website-type TFE support: URLs stored as thesis_files rows, HTMX-toggle on Site web format --- TODO.md | 14 ++++ app/public/assets/css/form.css | 5 ++ .../partage/format-website-fragment.php | 66 +++++++++++++++ app/public/partage/index.php | 64 +++++++++++++-- .../Controllers/ThesisCreateController.php | 40 +++++++++ app/src/Controllers/ThesisEditController.php | 58 ++++++++++++- app/templates/admin/add.php | 56 +++++++++++-- app/templates/admin/edit.php | 82 ++++++++++++++++--- app/templates/partials/form/checkbox-list.php | 22 +++-- app/templates/public/tfe.php | 31 +++++-- 10 files changed, 399 insertions(+), 39 deletions(-) create mode 100644 app/public/partage/format-website-fragment.php diff --git a/TODO.md b/TODO.md index f36a2fe..48a9c7d 100644 --- a/TODO.md +++ b/TODO.md @@ -102,6 +102,20 @@ - [x] Migration `013_fix_csv_column_shift.sql`: move orientation from synopsisβ†’orientation_id, finality from context_noteβ†’finality_id for already-imported theses - [x] Migration `013_fix_remarks_keywords.php`: move keywords from remarksβ†’tags+thesis_tags for already-imported theses +## Support website-type TFE (URL instead of uploaded files) +- [x] Add `file_type = 'website'` support to `thesis_files` β€” URL stored in `file_path`, no filesystem upload (no schema change needed) +- [x] Admin add form: "Site web (URL)" field dynamically shown via HTMX when "Site web" format checked +- [x] Admin edit form: website URL field via HTMX toggle + recognize website (🌐 icon) in existing-files list +- [x] Student partage form: website URL field via HTMX toggle + HTMX script added +- [x] TFE detail page (`tfe.php`): render `website` type as iframe with sandbox in media section +- [x] `ThesisCreateController`: handle website URL in submit β†’ `handleWebsiteUrl()` stores as thesis_files row +- [x] `ThesisEditController`: handle website URL in save β†’ `handleWebsiteUrl()` replaces existing website row; delete-files skips unlink for URLs +- [x] Edit page: website rows deletable via same delete_files checkbox mechanism +- [x] File size 0 for website rows β€” hidden in edit list to avoid showing "0.00 MB" +- [x] HTMX fragment endpoint: `/admin/actions/format-website-fragment.php` + `/partage/format-website-fragment` +- [x] `checkbox-list.php` partial: optional `hxPost`/`hxTarget` for HTMX live update +- [x] Server-side initial render: pre-populate `#website-url-section` if "Site web" already checked + ## Standardise rΓ©pertoire filter column rendering - [x] Centralise filter column rendering into a shared `repFilterEntry()` function - [x] Define `$filterColumns` config array as single source of truth for the 5 filter columns diff --git a/app/public/assets/css/form.css b/app/public/assets/css/form.css index 5ca8d06..4b34aa3 100644 --- a/app/public/assets/css/form.css +++ b/app/public/assets/css/form.css @@ -204,6 +204,11 @@ } /* ── Jury fieldset ──────────────────────────────────────────────────────── */ + +/* ── Website-URL inline fieldset (shown/hidden via HTMX) ────────────────── */ +/* The fieldset is shown/hidden via outerHTML swap β€” no CSS needed */ + +/* ── Jury fieldset (continued) ──────────────────────────────────────────── */ .admin-body fieldset fieldset.admin-jury-lecteurs, .student-body fieldset fieldset.admin-jury-lecteurs { border: none; diff --git a/app/public/partage/format-website-fragment.php b/app/public/partage/format-website-fragment.php new file mode 100644 index 0000000..d47cb0d --- /dev/null +++ b/app/public/partage/format-website-fragment.php @@ -0,0 +1,66 @@ +getConnection()->prepare( + 'SELECT id FROM format_types WHERE name = ? LIMIT 1' +); +$stmt->execute(['Site web']); +$websiteFormatId = $stmt->fetchColumn(); + +if (!$websiteFormatId) { + echo ''; + exit; +} + +$selectedFormats = isset($_POST['formats']) && is_array($_POST['formats']) + ? array_map('intval', $_POST['formats']) + : []; + +if (!in_array((int)$websiteFormatId, $selectedFormats, true)) { + echo ''; + exit; +} + +$websiteUrl = htmlspecialchars($_POST['website_url'] ?? ''); +$websiteLabel = htmlspecialchars($_POST['website_label'] ?? ''); +?> +
+ Site web + +
+ +
+ + Si le TFE est un site web, entrez son URL ici. Il sera affichΓ© comme un site embarquΓ© sur la page du TFE. +
+
+ +
+ + +
+
diff --git a/app/public/partage/index.php b/app/public/partage/index.php index 49005cf..95f20d1 100644 --- a/app/public/partage/index.php +++ b/app/public/partage/index.php @@ -21,6 +21,13 @@ $parts = explode('/', $path); $slug = $parts[0] ?? ''; $action = $parts[1] ?? ''; +// Special route: /partage/format-website-fragment (HTMX fragment, no auth needed) +if ($slug === 'format-website-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') { + App::boot(); + require_once __DIR__ . '/format-website-fragment.php'; + exit; +} + // Special route: /partage/recapitulatif?id=N if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') { App::boot(); @@ -265,6 +272,7 @@ function renderShareLinkForm(string $slug, array $link): void +
@@ -319,12 +327,6 @@ function renderShareLinkForm(string $slug, array $link): void - -
- Format(s) - -
-
Mots-clΓ©s @@ -368,6 +370,19 @@ function renderShareLinkForm(string $slug, array $link): void require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; ?> + +
+ Format(s) + +
+ + + + getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1'); + $_stmt->execute(['Site web']); + $_siteWebId = $_stmt->fetchColumn(); + if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) { + echo ''; + } + ?> + db->handleBannerUpload($thesisId, $files['banner'] ?? null); $this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post); + // ── 6. Website URL β€” stored as thesis_files row ────────────────────── + $this->handleWebsiteUrl($thesisId, $post); + return $thesisId; } @@ -813,4 +816,41 @@ class ThesisCreateController } return $candidate; } + + /** + * Store a website URL as a thesis_files row (file_type = 'website'). + * + * The URL is stored in file_path; no filesystem operation is performed. + * label and sort_order from the POST are preserved. + */ + private function handleWebsiteUrl(int $thesisId, array $post): void + { + $websiteUrl = trim($post['website_url'] ?? ''); + if ($websiteUrl === '') { + return; + } + + // Validate URL + $websiteUrl = filter_var($websiteUrl, FILTER_VALIDATE_URL); + if ($websiteUrl === false) { + error_log('ThesisCreateController: invalid website URL, skipping'); + return; + } + + $label = trim($post['website_label'] ?? ''); + $sortOrder = isset($post['website_order']) ? (int)$post['website_order'] : null; + $fileName = rtrim(preg_replace('#^https?://#i', '', $websiteUrl), '/'); + + $this->db->insertThesisFile( + $thesisId, + 'website', + $websiteUrl, + $fileName, + 0, + 'text/html', + $label !== '' ? $label : null, + $sortOrder + ); + error_log("ThesisCreateController: website stored β†’ $websiteUrl"); + } } diff --git a/app/src/Controllers/ThesisEditController.php b/app/src/Controllers/ThesisEditController.php index a226624..44eb34a 100644 --- a/app/src/Controllers/ThesisEditController.php +++ b/app/src/Controllers/ThesisEditController.php @@ -331,9 +331,12 @@ class ThesisEditController } $filePath = $this->db->deleteThesisFile($fileId, $thesisId); if ($filePath && defined('STORAGE_ROOT')) { - $abs = STORAGE_ROOT . '/' . $filePath; - if (file_exists($abs)) { - @unlink($abs); + // Skip filesystem deletion for website URLs (not real files) + if (!str_starts_with($filePath, 'http://') && !str_starts_with($filePath, 'https://')) { + $abs = STORAGE_ROOT . '/' . $filePath; + if (file_exists($abs)) { + @unlink($abs); + } } } } @@ -358,6 +361,9 @@ class ThesisEditController if (!empty($files['files']['name'][0])) { $this->handleThesisFiles($thesisId, $post, $files['files']); } + + // ── Website URL β€” add or update ────────────────────────────────────── + $this->handleWebsiteUrl($thesisId, $post); } // ── Private: file uploads ───────────────────────────────────────────────── @@ -703,4 +709,50 @@ class ThesisEditController } return $info; } + + /** + * Add or update a website URL thesis_file row. + * + * If a website row already exists for this thesis, it is replaced. + * Otherwise a new row is inserted. + */ + private function handleWebsiteUrl(int $thesisId, array $post): void + { + $websiteUrl = trim($post['website_url'] ?? ''); + + // Remove existing website rows + $existingFiles = $this->db->getThesisFiles($thesisId); + foreach ($existingFiles as $f) { + if ($f['file_type'] === 'website') { + $this->db->deleteThesisFile((int)$f['id'], $thesisId); + } + } + + if ($websiteUrl === '') { + return; + } + + // Validate URL + $websiteUrl = filter_var($websiteUrl, FILTER_VALIDATE_URL); + if ($websiteUrl === false) { + error_log('ThesisEditController: invalid website URL, skipping'); + return; + } + + $label = trim($post['website_label'] ?? ''); + $sortOrder = isset($post['website_order']) ? (int)$post['website_order'] : null; + $fileName = rtrim(preg_replace('#^https?://#i', '', $websiteUrl), '/'); + + $this->db->insertThesisFile( + $thesisId, + 'website', + $websiteUrl, + $fileName, + 0, + 'text/html', + $label !== '' ? $label : null, + $sortOrder + ); + error_log("ThesisEditController: website stored β†’ $websiteUrl"); + } } diff --git a/app/templates/admin/add.php b/app/templates/admin/add.php index 0ef4451..4eb31ed 100644 --- a/app/templates/admin/add.php +++ b/app/templates/admin/add.php @@ -36,12 +36,6 @@
- -
- Format(s) - -
-
Mots-clΓ©s @@ -74,9 +68,59 @@ require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; ?> + +
+ Format(s) + +
+ + + + getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1'); + $_stmt->execute(['Site web']); + $_siteWebId = $_stmt->fetchColumn(); + if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) { + echo ''; + } + ?> +
- -
- Format(s) - -
-
Mots-clΓ©s @@ -132,6 +122,20 @@ require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; ?> + +
+ Format(s) + +
+
Fichiers @@ -173,20 +177,25 @@ $fType === 'video' || in_array($fExt, ['mp4','webm','mov','ogv']) => '🎬', $fType === 'audio' || in_array($fExt, ['mp3','ogg','wav','flac','aac','m4a']) => 'πŸ”Š', $fType === 'caption' || $fExt === 'vtt' => 'πŸ’¬', + $fType === 'website' => '🌐', default => 'πŸ“Ž', }; + $isExternalUrl = str_starts_with($f['file_path'] ?? '', 'http://') || str_starts_with($f['file_path'] ?? '', 'https://'); + $fLinkHref = $isExternalUrl + ? htmlspecialchars($f['file_path']) + : ('/media.php?path=' . urlencode($f['file_path'])); ?>
  • β Ώ - + - + 0): ?> MB @@ -239,6 +248,55 @@
  • + + + + getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1'); + $_stmt->execute(['Site web']); + $_siteWebId = $_stmt->fetchColumn(); + if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) { + echo ''; + } + ?> +
    *' : '' ?> -
    > +
    + + hx-post="" + hx-target="" + hx-trigger="change" + hx-include="this, #website-url-fieldset" + hx-swap="outerHTML" + >
    @@ -464,6 +467,20 @@

    TΓ©lΓ©charger le PDF

    + + +

    + + Ouvrir le site dans un nouvel onglet + (ouvre dans un nouvel onglet) + +

    <?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' β€” ' . ($data['authors'] ?? '')) ?>