Add Open Graph and Twitter Card meta tags to all public pages

- templates/public/head.php: add centralised OG/Twitter tag rendering via $ogTags array;
  supports type, title, description, url, image, image_alt, site_name, article_author,
  article_published_time; twitter:card switches between summary_large_image / summary
  based on presence of og:image

- public/tfe.php: populate full article OG tags — og:type=article, canonical URL,
  og:image resolved from banner_path → first image file in thesis_files → omitted,
  og:image:alt, article:author, article:published_time (year-01-01); twitter:card
  summary_large_image when image present

- public/index.php, search.php, apropos.php, licence.php: add basic og:type=website
  tags (title, description, canonical url, site_name)

Sharing a thesis link on Slack, WhatsApp, iMessage, or any social platform will now
render a rich preview card with the thesis title, synopsis excerpt, and cover/banner image.
This commit is contained in:
Pontoporeia
2026-03-29 15:43:21 +02:00
parent 1dee1ea73f
commit 3a8ffa6afe
7 changed files with 105 additions and 3 deletions

View File

@@ -539,9 +539,11 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis
site blurb for `index.php`, synopsis excerpt for `tfe.php`, page content intro for
`apropos.php`/`licence.php`. Necessary for search indexing and link preview cards.
- [ ] **No Open Graph tags** - `tfe.php` is the ideal candidate for `og:title`, `og:description`
(synopsis), `og:image` (banner or cover path through `/media.php`), `og:type=article`.
Without them, sharing a thesis link on social media or messaging apps shows a blank preview.
- [x] **No Open Graph tags** - `tfe.php` now emits `og:type=article`, `og:title`, `og:description`,
`og:url`, `og:image` (banner → first image file → none), `og:image:alt`, `og:site_name`,
`article:author`, `article:published_time`, plus `twitter:card`/`twitter:title`/`twitter:description`/
`twitter:image`. All other public pages (`index`, `search`, `apropos`, `licence`) emit basic
`og:type=website` tags. OG rendering is centralised in `templates/public/head.php` via `$ogTags` array.
### H - Minor / low-hanging fruit

View File

@@ -26,6 +26,13 @@ $pd->setSafeMode(true);
$aboutHtml = $pd->text($rawContent);
$pageTitle = 'À Propos Posterg';
$metaDescription = 'À propos de Posterg, le répertoire des mémoires de fin d\'études de l\'erg École de Recherches Graphiques de Bruxelles.';
$ogTags = [
'type' => 'website',
'title' => $pageTitle,
'description' => $metaDescription,
'url' => 'https://posterg.erg.be/apropos.php',
'site_name' => 'Posterg ERG',
];
$extraCss = ['assets/apropos.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>

View File

@@ -52,6 +52,13 @@ try {
$currentNav = '';
$pageTitle = 'Posterg Mémoires de l\'ERG';
$metaDescription = 'Posterg répertorie et valorise les mémoires de fin d\'études (TFE) de l\'erg École de Recherches Graphiques de Bruxelles.';
$ogTags = [
'type' => 'website',
'title' => $pageTitle,
'description' => $metaDescription,
'url' => 'https://posterg.erg.be/',
'site_name' => 'Posterg ERG',
];
$extraCss = ['assets/main.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>

View File

@@ -22,6 +22,13 @@ $html = $pd->text($content);
$pageTitle = $licencePageTitle . ' Posterg';
$metaDescription = 'Informations sur les licences d\'utilisation des mémoires publiés sur Posterg, le répertoire des TFE de l\'erg.';
$ogTags = [
'type' => 'website',
'title' => $pageTitle,
'description' => $metaDescription,
'url' => 'https://posterg.erg.be/licence.php',
'site_name' => 'Posterg ERG',
];
$extraCss = ['assets/apropos.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>

View File

@@ -63,6 +63,13 @@ $currentNav = 'repertoire';
$searchBarValue = $_GET['query'] ?? '';
$pageTitle = 'Répertoire Posterg';
$metaDescription = 'Parcourez le répertoire des mémoires de fin d\'études (TFE) de l\'erg École de Recherches Graphiques de Bruxelles. Recherche par année, orientation, atelier et mots-clés.';
$ogTags = [
'type' => 'website',
'title' => $pageTitle,
'description' => $metaDescription,
'url' => 'https://posterg.erg.be/search.php',
'site_name' => 'Posterg ERG',
];
$extraCss = ['assets/search.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>

View File

@@ -25,6 +25,37 @@ $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/tfe.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>

View File

@@ -7,6 +7,47 @@
<?php if (!empty($metaDescription)): ?>
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
<?php endif; ?>
<?php
// Open Graph / Twitter Card tags — populated per-page via $ogTags array.
// Keys: type, title, description, url, image, image_alt, site_name, article_author, article_published_time
if (!empty($ogTags)):
$ogType = $ogTags['type'] ?? 'website';
$ogTitle = $ogTags['title'] ?? ($pageTitle ?? 'Posterg');
$ogDescription = $ogTags['description'] ?? ($metaDescription ?? '');
$ogUrl = $ogTags['url'] ?? '';
$ogImage = $ogTags['image'] ?? '';
$ogImageAlt = $ogTags['image_alt'] ?? $ogTitle;
$ogSiteName = $ogTags['site_name'] ?? 'Posterg ERG';
?>
<meta property="og:type" content="<?= htmlspecialchars($ogType) ?>">
<meta property="og:site_name" content="<?= htmlspecialchars($ogSiteName) ?>">
<meta property="og:title" content="<?= htmlspecialchars($ogTitle) ?>">
<?php if (!empty($ogDescription)): ?>
<meta property="og:description" content="<?= htmlspecialchars($ogDescription) ?>">
<?php endif; ?>
<?php if (!empty($ogUrl)): ?>
<meta property="og:url" content="<?= htmlspecialchars($ogUrl) ?>">
<?php endif; ?>
<?php if (!empty($ogImage)): ?>
<meta property="og:image" content="<?= htmlspecialchars($ogImage) ?>">
<meta property="og:image:alt" content="<?= htmlspecialchars($ogImageAlt) ?>">
<?php endif; ?>
<?php if (!empty($ogTags['article_author'])): ?>
<meta property="article:author" content="<?= htmlspecialchars($ogTags['article_author']) ?>">
<?php endif; ?>
<?php if (!empty($ogTags['article_published_time'])): ?>
<meta property="article:published_time" content="<?= htmlspecialchars($ogTags['article_published_time']) ?>">
<?php endif; ?>
<meta name="twitter:card" content="<?= !empty($ogImage) ? 'summary_large_image' : 'summary' ?>">
<meta name="twitter:title" content="<?= htmlspecialchars($ogTitle) ?>">
<?php if (!empty($ogDescription)): ?>
<meta name="twitter:description" content="<?= htmlspecialchars($ogDescription) ?>">
<?php endif; ?>
<?php if (!empty($ogImage)): ?>
<meta name="twitter:image" content="<?= htmlspecialchars($ogImage) ?>">
<meta name="twitter:image:alt" content="<?= htmlspecialchars($ogImageAlt) ?>">
<?php endif; ?>
<?php endif; ?>
<link rel="icon" type="image/svg+xml" href="/assets/admin_favicon.svg">
<link rel="stylesheet" href="assets/modern-normalize.min.css">
<link rel="stylesheet" href="assets/common.css">