Extract TfeController from public/tfe.php

src/TfeController.php (new, 195 lines):
- Dedicated controller for the public TFE detail page
- create(): Database singleton injection, ready-to-use factory
- handle(): validates id param (redirect to index.php on missing/invalid/404),
  loads thesis via getThesisById(), fetches access type via getThesisAccessTypeId()
- buildMetaDescription(): strip_tags + 160-char mb_substr truncation
- resolveOgImage(): banner_path → first image file → empty string resolution
- buildOgTags(): full og:type/title/description/url/image/image_alt/site_name +
  article:author / article:published_time assembly
- collectCaptionPaths(): ordered list of VTT paths for N-th-video pairing
- returns flat array of all view variables including ogTags, captionFiles,
  pageTitle, metaDescription, isInterdit, bodyClass, extraCss, currentNav

public/tfe.php (271 → 206 lines):
- Reduced to 9-line dispatcher: require TfeController, create(), handle(), extract()
- $db reference removed from view layer entirely
- Inline OG tag block (~20 lines) removed
- Inline meta-description block (~5 lines) removed
- Inline caption-collection loop (~10 lines) removed
- $captionFiles replaces $_captionFiles in the video pairing section

todo/02-php-components.md:
- TfeController extraction marked done
- 'Move OG tag construction into controller logic' marked done
- Remaining item narrowed to public/index.php home-page controller
This commit is contained in:
Pontoporeia
2026-04-06 14:25:54 +02:00
parent 41629398d3
commit 89067a521f
4 changed files with 217 additions and 73 deletions

View File

@@ -1,63 +1,12 @@
<?php
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/TfeController.php';
if (isset($_GET['id'])) {
$thesisId = intval($_GET['id']);
try {
$db = Database::getInstance();
$data = $db->getThesisById($thesisId);
if (!$data) { header('Location: index.php'); exit; }
} catch (Exception $e) {
error_log("Error loading thesis: " . $e->getMessage());
header('Location: index.php'); exit;
}
} else {
header('Location: index.php'); exit;
}
// Build controller (loads thesis, enforces visibility, builds OG tags; redirects on 404)
$ctrl = TfeController::create();
$currentNav = '';
$_tfeAuthor = $data['authors'] ?? '';
$pageTitle = $data['title'] . (!empty($_tfeAuthor) ? ' ' . $_tfeAuthor : '') . ' Posterg';
// Build meta description: strip markdown/HTML from synopsis, truncate to ~160 chars
$_synopsisRaw = strip_tags($data['synopsis'] ?? '');
$metaDescription = mb_strlen($_synopsisRaw) > 160
? mb_substr($_synopsisRaw, 0, 157) . '…'
: (empty($_synopsisRaw) ? 'Mémoire de fin d\'études Posterg, répertoire des TFE de l\'erg.' : $_synopsisRaw);
unset($_tfeAuthor, $_synopsisRaw);
// --- Open Graph / Twitter Card tags ------------------------------------------
$_ogBaseUrl = 'https://posterg.erg.be';
// Resolve OG image: banner → first image file → none
$_ogImage = '';
if (!empty($data['banner_path'])) {
$_ogImage = $_ogBaseUrl . '/media.php?path=' . rawurlencode($data['banner_path']);
} elseif (!empty($data['files'])) {
foreach ($data['files'] as $_f) {
$_ext = strtolower(pathinfo($_f['file_path'], PATHINFO_EXTENSION));
if (in_array($_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
$_ogImage = $_ogBaseUrl . '/media.php?path=' . rawurlencode($_f['file_path']);
break;
}
}
unset($_f, $_ext);
}
$ogTags = [
'type' => 'article',
'title' => $data['title'] . (!empty($data['authors']) ? ' ' . $data['authors'] : ''),
'description' => $metaDescription,
'url' => $_ogBaseUrl . '/tfe.php?id=' . $thesisId,
'image' => $_ogImage,
'image_alt' => $data['title'] . (!empty($data['authors']) ? ' par ' . $data['authors'] : ''),
'site_name' => 'Posterg ERG',
'article_author' => $data['authors'] ?? '',
'article_published_time' => !empty($data['year']) ? $data['year'] . '-01-01' : '',
];
unset($_ogBaseUrl, $_ogImage);
// --- End Open Graph ----------------------------------------------------------
$extraCss = ['/assets/css/tfe.css'];
$bodyClass = 'tfe-body';
// Collect all view variables
extract($ctrl->handle());
?>
<?php include APP_ROOT . '/templates/head.php'; ?>
<?php include APP_ROOT . '/templates/header.php'; ?>
@@ -194,21 +143,8 @@ $bodyClass = 'tfe-body';
<!-- RIGHT: media — supplementary aside -->
<aside class="tfe-right">
<?php
$accessTypeId = $db->getThesisAccessTypeId($thesisId) ?? 1;
$isInterdit = ($accessTypeId === 3);
// Collect any WebVTT caption files uploaded for this thesis.
// The N-th VTT file is paired with the N-th video in document order.
$_captionFiles = [];
foreach ($data['files'] ?? [] as $_cf) {
$__mime = $_cf['mime_type'] ?? '';
$__ext2 = strtolower(pathinfo($_cf['file_path'], PATHINFO_EXTENSION));
if ($__mime === 'text/vtt' || $__ext2 === 'vtt') {
$_captionFiles[] = $_cf['file_path'];
}
}
// $isInterdit and $captionFiles are resolved by TfeController::handle()
$_videoIndex = 0;
unset($_cf, $__mime, $__ext2);
?>
<?php if ($isInterdit): ?>
<p class="tfe-restricted">
@@ -240,7 +176,7 @@ $bodyClass = 'tfe-body';
<?php elseif ($ext === 'mp4'): ?>
<?php
// Pair this video with the N-th VTT file (if one was uploaded).
$_vttPath = $_captionFiles[$_videoIndex] ?? null;
$_vttPath = $captionFiles[$_videoIndex] ?? null;
$_videoIndex++;
?>
<video width="100%" controls>