mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
merge banners into covers: remove banner field, migrate files, add covers to search/home/repertoire cards
This commit is contained in:
130
app/migrations/applied/016_merge_banners_into_covers.php
Normal file
130
app/migrations/applied/016_merge_banners_into_covers.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Migration 016 — merge banners into covers
|
||||
*
|
||||
* 1. For every thesis that has a banner_path:
|
||||
* a. Copy the file from storage/banners/<file> to storage/covers/<file>
|
||||
* b. Insert a thesis_files row with file_type='cover'
|
||||
* c. Clear theses.banner_path
|
||||
* 2. Remove the now-empty storage/banners/ directory (best-effort).
|
||||
*
|
||||
* Safe to re-run: if a cover record already exists for a thesis, the banner
|
||||
* migration for that thesis is skipped.
|
||||
*/
|
||||
|
||||
defined('APP_ROOT') || define('APP_ROOT', dirname(__DIR__, 2));
|
||||
defined('STORAGE_ROOT') || define('STORAGE_ROOT', APP_ROOT . '/storage');
|
||||
|
||||
$dbPath = APP_ROOT . '/storage/xamxam.db';
|
||||
if (!file_exists($dbPath)) {
|
||||
echo "ERROR: database not found at $dbPath\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$pdo = new PDO('sqlite:' . $dbPath);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
$pdo->exec('PRAGMA foreign_keys = ON');
|
||||
|
||||
$coverDir = STORAGE_ROOT . '/covers/';
|
||||
$bannerDir = STORAGE_ROOT . '/banners/';
|
||||
|
||||
if (!is_dir($coverDir)) {
|
||||
mkdir($coverDir, 0755, true);
|
||||
echo "Created covers/ directory.\n";
|
||||
}
|
||||
|
||||
// Fetch all theses with a non-null banner_path
|
||||
$stmt = $pdo->query("SELECT id, banner_path FROM theses WHERE banner_path IS NOT NULL");
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
if (empty($rows)) {
|
||||
echo "No banners to migrate.\n";
|
||||
} else {
|
||||
foreach ($rows as $row) {
|
||||
$thesisId = (int)$row['id'];
|
||||
$bannerPath = $row['banner_path']; // e.g. "banners/abc123.png"
|
||||
|
||||
// Skip if a cover record already exists for this thesis
|
||||
$check = $pdo->prepare("SELECT id FROM thesis_files WHERE thesis_id = ? AND file_type = 'cover' LIMIT 1");
|
||||
$check->execute([$thesisId]);
|
||||
if ($check->fetch()) {
|
||||
echo " Thesis $thesisId: cover record already exists — skipping banner migration.\n";
|
||||
// Still clear banner_path so UI stays clean
|
||||
$pdo->prepare("UPDATE theses SET banner_path = NULL WHERE id = ?")->execute([$thesisId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$srcAbs = STORAGE_ROOT . '/' . $bannerPath;
|
||||
$filename = basename($bannerPath);
|
||||
$dstAbs = $coverDir . $filename;
|
||||
$dstRel = 'covers/' . $filename;
|
||||
|
||||
if (!file_exists($srcAbs)) {
|
||||
echo " Thesis $thesisId: source file missing ($srcAbs) — inserting DB record with new path anyway, skipping file copy.\n";
|
||||
} else {
|
||||
if (!copy($srcAbs, $dstAbs)) {
|
||||
echo " ERROR: could not copy $srcAbs → $dstAbs — skipping thesis $thesisId.\n";
|
||||
continue;
|
||||
}
|
||||
chmod($dstAbs, 0644);
|
||||
echo " Thesis $thesisId: copied $bannerPath → $dstRel\n";
|
||||
}
|
||||
|
||||
// Determine MIME from extension
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$mime = match($ext) {
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
default => 'image/jpeg',
|
||||
};
|
||||
|
||||
// Get file size
|
||||
$size = file_exists($dstAbs) ? filesize($dstAbs) : 0;
|
||||
|
||||
// Insert cover record
|
||||
$ins = $pdo->prepare(
|
||||
"INSERT INTO thesis_files (thesis_id, file_type, file_path, file_name, file_size, mime_type, sort_order)
|
||||
VALUES (?, 'cover', ?, ?, ?, ?, 0)"
|
||||
);
|
||||
$ins->execute([$thesisId, $dstRel, $filename, $size, $mime]);
|
||||
echo " Thesis $thesisId: inserted cover record → $dstRel\n";
|
||||
|
||||
// Clear banner_path
|
||||
$pdo->prepare("UPDATE theses SET banner_path = NULL WHERE id = ?")->execute([$thesisId]);
|
||||
echo " Thesis $thesisId: cleared banner_path.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old banner files that were successfully copied
|
||||
$remaining = glob($bannerDir . '*') ?: [];
|
||||
$allClear = true;
|
||||
foreach ($remaining as $f) {
|
||||
$basename = basename($f);
|
||||
if (file_exists($coverDir . $basename)) {
|
||||
@unlink($f);
|
||||
echo "Removed migrated banner file: banners/$basename\n";
|
||||
} else {
|
||||
echo "WARNING: banners/$basename has no corresponding cover — leaving in place.\n";
|
||||
$allClear = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the now-empty banners/ directory (best-effort, ignoring .gitkeep)
|
||||
if ($allClear && is_dir($bannerDir)) {
|
||||
$leftovers = array_diff(scandir($bannerDir), ['.', '..', '.gitkeep']);
|
||||
if (empty($leftovers)) {
|
||||
// Remove .gitkeep if present, then the dir
|
||||
$gitkeep = $bannerDir . '.gitkeep';
|
||||
if (file_exists($gitkeep)) {
|
||||
@unlink($gitkeep);
|
||||
}
|
||||
@rmdir($bannerDir);
|
||||
echo "Removed banners/ directory.\n";
|
||||
} else {
|
||||
echo "WARNING: banners/ directory still has files after migration — leaving in place.\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\nMigration 016 complete.\n";
|
||||
@@ -10,7 +10,7 @@
|
||||
* - Parse and validate GET parameters (`page`, `year`)
|
||||
* - Determine the display mode (default random-latest / year-filtered / paginated all)
|
||||
* - Run the appropriate Database queries
|
||||
* - Batch-load cover images for theses without a banner_path
|
||||
* - Batch-load cover images for displayed theses
|
||||
* - Assemble OG / meta tag array
|
||||
* - Return a flat array of view variables ready for template extraction
|
||||
*
|
||||
@@ -91,18 +91,11 @@ class HomeController
|
||||
$totalItems = $this->db->countPublishedTheses();
|
||||
}
|
||||
|
||||
// Batch-load cover images for theses that have no banner_path
|
||||
// Batch-load cover images for all displayed theses
|
||||
if (!empty($itemsToLoad)) {
|
||||
$needCover = array_column(
|
||||
array_filter(
|
||||
$itemsToLoad,
|
||||
static fn ($t) => empty($t['banner_path']),
|
||||
),
|
||||
'id',
|
||||
$coverMap = $this->db->getCoverPathsForTheses(
|
||||
array_column($itemsToLoad, 'id')
|
||||
);
|
||||
if (!empty($needCover)) {
|
||||
$coverMap = $this->db->getCoverPathsForTheses($needCover);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('HomeController: ' . $e->getMessage());
|
||||
|
||||
@@ -118,6 +118,7 @@ class SearchController
|
||||
'results' => $results,
|
||||
'validationError' => $validationError,
|
||||
'baseParams' => $baseParams,
|
||||
'coverMap' => $coverMap,
|
||||
|
||||
// Filter dropdowns
|
||||
'years' => $years,
|
||||
@@ -225,6 +226,8 @@ class SearchController
|
||||
exit();
|
||||
}
|
||||
|
||||
$coverMap = $this->db->getCoverPathsForTheses(array_column($theses, 'id'));
|
||||
|
||||
header('Cache-Control: public, max-age=300');
|
||||
include APP_ROOT . '/templates/partials/student-preview.php';
|
||||
exit();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Responsibilities:
|
||||
* - Validate the `id` GET parameter and load the thesis record
|
||||
* - Enforce publication visibility (redirect to index on 404)
|
||||
* - Resolve the OG image (banner → first image file)
|
||||
* - Resolve the OG image (cover file → first image file)
|
||||
* - Build the complete OG / Twitter Card tag array
|
||||
* - Assemble the meta description from the synopsis
|
||||
* - Collect WebVTT caption file paths for video pairing
|
||||
@@ -155,16 +155,20 @@ class TfeController
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the OG image URL: banner_path → first image file → empty string.
|
||||
* Resolve the OG image URL: cover file → first image file → empty string.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $files
|
||||
*/
|
||||
private function resolveOgImage(array $files, ?string $bannerPath): string
|
||||
private function resolveOgImage(array $files): string
|
||||
{
|
||||
if (!empty($bannerPath)) {
|
||||
return self::BASE_URL . '/media.php?path=' . rawurlencode($bannerPath);
|
||||
// Prefer the dedicated cover
|
||||
foreach ($files as $file) {
|
||||
if (($file['file_type'] ?? '') === 'cover') {
|
||||
return self::BASE_URL . '/media.php?path=' . rawurlencode($file['file_path']);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to first image file
|
||||
foreach ($files as $file) {
|
||||
$ext = strtolower(pathinfo($file['file_path'] ?? '', PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
|
||||
@@ -183,7 +187,7 @@ class TfeController
|
||||
*/
|
||||
private function buildOgTags(array $data, int $thesisId, string $metaDescription): array
|
||||
{
|
||||
$ogImage = $this->resolveOgImage($data['files'] ?? [], $data['banner_path'] ?? null);
|
||||
$ogImage = $this->resolveOgImage($data['files'] ?? []);
|
||||
$title = $data['title'] . (!empty($data['authors']) ? ' – ' . $data['authors'] : '');
|
||||
$imageAlt = $data['title'] . (!empty($data['authors']) ? ' par ' . $data['authors'] : '');
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class ThesisCreateController
|
||||
* 3. INSERT thesis row + link author (inside transaction)
|
||||
* 4. Link jury, languages, formats, tags (inside transaction)
|
||||
* 5. COMMIT
|
||||
* 6. Handle file uploads: cover, banner, thesis files (outside transaction)
|
||||
* 6. Handle file uploads: cover, thesis files (outside transaction)
|
||||
*
|
||||
* @param array $post Sanitised $_POST array.
|
||||
* @param array $files $_FILES array.
|
||||
@@ -213,7 +213,6 @@ class ThesisCreateController
|
||||
|
||||
// ── 5. File uploads (outside transaction — filesystem ops) ────────────
|
||||
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null);
|
||||
$this->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 ──────────────────────
|
||||
|
||||
@@ -320,20 +320,6 @@ class ThesisEditController
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// ── Banner (outside transaction — filesystem op) ──────────────────────
|
||||
if (isset($post['remove_banner'])) {
|
||||
$currentBannerPath = $this->db->getThesisBannerPath($thesisId);
|
||||
if ($currentBannerPath && defined('STORAGE_ROOT')) {
|
||||
$absPath = STORAGE_ROOT . '/' . $currentBannerPath;
|
||||
if (file_exists($absPath)) {
|
||||
unlink($absPath);
|
||||
}
|
||||
}
|
||||
$this->db->setBannerPath($thesisId, null);
|
||||
} else {
|
||||
$this->db->handleBannerUpload($thesisId, $files['banner'] ?? null);
|
||||
}
|
||||
|
||||
// ── Cover image (outside transaction — filesystem op) ─────────────────
|
||||
if (isset($post['remove_cover'])) {
|
||||
$allFiles = $this->db->getThesisFiles($thesisId);
|
||||
|
||||
@@ -1551,79 +1551,9 @@ class Database
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// BANNER METHODS
|
||||
// COVER METHODS (formerly also BANNER METHODS — banners merged into covers)
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Set (or clear) the banner_path for a thesis.
|
||||
*/
|
||||
public function setBannerPath(int $thesisId, ?string $path): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE theses SET banner_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([$path, $thesisId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a banner image upload for a thesis.
|
||||
*
|
||||
* Validates MIME type, extension, and file size, then saves the file to the
|
||||
* banners/ directory under STORAGE_ROOT and calls setBannerPath().
|
||||
*
|
||||
* Returns the relative path (e.g. "banners/abc123.jpg") on success,
|
||||
* or null if the file array is absent, has an error, fails validation,
|
||||
* or cannot be moved.
|
||||
*
|
||||
* @param int $thesisId Target thesis ID
|
||||
* @param array|null $uploadedFile Entry from $_FILES (e.g. $_FILES['banner'])
|
||||
* @return string|null Relative path stored in the DB, or null
|
||||
*/
|
||||
public function handleBannerUpload(int $thesisId, ?array $uploadedFile): ?string
|
||||
{
|
||||
if (!$uploadedFile || ($uploadedFile['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
$allowedExts = ['jpg', 'jpeg', 'png', 'webp'];
|
||||
$maxBytes = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($uploadedFile['tmp_name']);
|
||||
$ext = strtolower(pathinfo($uploadedFile['name'], PATHINFO_EXTENSION));
|
||||
|
||||
if (!in_array($mimeType, $allowedMimes, true) ||
|
||||
!in_array($ext, $allowedExts, true) ||
|
||||
$uploadedFile['size'] > $maxBytes) {
|
||||
error_log('handleBannerUpload: rejected ' . $uploadedFile['name'] . " ($mimeType, {$uploadedFile['size']} bytes)");
|
||||
return null;
|
||||
}
|
||||
|
||||
$bannerDir = defined('STORAGE_ROOT') ? STORAGE_ROOT . '/banners/' : null;
|
||||
if (!$bannerDir) {
|
||||
error_log('handleBannerUpload: STORAGE_ROOT not defined');
|
||||
return null;
|
||||
}
|
||||
if (!file_exists($bannerDir)) {
|
||||
mkdir($bannerDir, 0755, true);
|
||||
}
|
||||
|
||||
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
|
||||
$targetPath = $bannerDir . $safeName;
|
||||
|
||||
if (!move_uploaded_file($uploadedFile['tmp_name'], $targetPath)) {
|
||||
error_log('handleBannerUpload: move_uploaded_file failed for ' . $uploadedFile['name']);
|
||||
return null;
|
||||
}
|
||||
|
||||
chmod($targetPath, 0644);
|
||||
$relativePath = 'banners/' . $safeName;
|
||||
$this->setBannerPath($thesisId, $relativePath);
|
||||
error_log("handleBannerUpload: saved $relativePath");
|
||||
return $relativePath;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// ENCAPSULATED QUERY HELPERS
|
||||
// ========================================================================
|
||||
@@ -1658,20 +1588,6 @@ class Database
|
||||
return $row !== false ? $row : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the banner_path for a thesis, or null.
|
||||
* Used when we need just the banner path without the full view expansion.
|
||||
*/
|
||||
public function getThesisBannerPath(int $thesisId): ?string
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT banner_path FROM theses WHERE id = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$thesisId]);
|
||||
$val = $stmt->fetchColumn();
|
||||
return ($val !== false && $val !== null) ? (string)$val : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-load cover file paths for a set of thesis IDs.
|
||||
* Returns [thesis_id => file_path] for IDs that have a cover in thesis_files.
|
||||
@@ -1891,19 +1807,10 @@ class Database
|
||||
|
||||
/**
|
||||
* Delete a single thesis and all its related data (cascade via FK).
|
||||
* Also removes the banner file from disk if present.
|
||||
* Removes thesis files from disk (covers are stored in thesis_files and handled here).
|
||||
*/
|
||||
public function deleteThesis(int $thesisId): void
|
||||
{
|
||||
// Clean up banner file
|
||||
$bannerPath = $this->getThesisBannerPath($thesisId);
|
||||
if ($bannerPath !== null) {
|
||||
$fullPath = defined('STORAGE_ROOT') ? STORAGE_ROOT . '/' . $bannerPath : null;
|
||||
if ($fullPath && file_exists($fullPath)) {
|
||||
@unlink($fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up thesis files from disk
|
||||
$files = $this->getThesisFiles($thesisId);
|
||||
foreach ($files as $file) {
|
||||
@@ -1928,13 +1835,6 @@ class Database
|
||||
|
||||
// Clean up files for each thesis
|
||||
foreach ($thesisIds as $id) {
|
||||
$bannerPath = $this->getThesisBannerPath($id);
|
||||
if ($bannerPath !== null) {
|
||||
$fullPath = defined('STORAGE_ROOT') ? STORAGE_ROOT . '/' . $bannerPath : null;
|
||||
if ($fullPath && file_exists($fullPath)) {
|
||||
@unlink($fullPath);
|
||||
}
|
||||
}
|
||||
$files = $this->getThesisFiles($id);
|
||||
foreach ($files as $file) {
|
||||
if (!empty($file['file_path']) && file_exists($file['file_path'])) {
|
||||
@@ -2058,9 +1958,9 @@ class Database
|
||||
return null;
|
||||
}
|
||||
|
||||
$allowedMimes = ['image/jpeg', 'image/png'];
|
||||
$allowedExts = ['jpg', 'jpeg', 'png'];
|
||||
$maxBytes = 10 * 1024 * 1024; // 10 MB
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
$allowedExts = ['jpg', 'jpeg', 'png', 'webp'];
|
||||
$maxBytes = 20 * 1024 * 1024; // 20 MB
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($upload['tmp_name']);
|
||||
|
||||
BIN
app/storage/covers/f2f1ef60698383a1e6a93dc7719dc5c3.png
Normal file
BIN
app/storage/covers/f2f1ef60698383a1e6a93dc7719dc5c3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
@@ -83,9 +83,8 @@
|
||||
|
||||
// Files: edit mode
|
||||
$filesMode = 'edit';
|
||||
$currentCover = $currentCover ?? null;
|
||||
$currentFiles = $currentFiles ?? [];
|
||||
$currentBannerPath = $thesis['banner_path'] ?? null;
|
||||
$currentCover = $currentCover ?? null;
|
||||
$currentFiles = $currentFiles ?? [];
|
||||
$currentContextNote = $currentContextNote ?? null;
|
||||
|
||||
// Website URL from existing files
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Shared partial — "Fichiers" fieldset (add / student submission mode).
|
||||
*
|
||||
* Order per spec:
|
||||
* 1. Image de bannière (optionnel)
|
||||
* 1. Image de couverture (optionnel)
|
||||
* 2. Note d'intention (obligatoire)
|
||||
* 3. TFE (obligatoire)
|
||||
* 4. Annexes éventuelles (optionnel)
|
||||
@@ -15,10 +15,10 @@
|
||||
<legend>Fichiers</legend>
|
||||
|
||||
<?php
|
||||
$name = 'banner';
|
||||
$label = 'Image de bannière (optionnel) :';
|
||||
$name = 'couverture';
|
||||
$label = 'Image de couverture (optionnel) :';
|
||||
$accept = 'image/jpeg,image/png,image/webp';
|
||||
$hint = 'JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 20 MB.';
|
||||
$hint = 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
|
||||
include APP_ROOT . '/templates/partials/form/file-field.php';
|
||||
?>
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
* bool $showContact — Contact checkbox fieldset
|
||||
* bool $showCoverPreview — cover image preview + remove checkbox
|
||||
* bool $showExistingFiles — existing thesis files list (sortable, deletable)
|
||||
* bool $showBannerPreview — banner image preview + remove checkbox
|
||||
* bool $showContextNote — Note contextuelle fieldset
|
||||
* bool $showBackoffice — Backoffice fieldset (jury_points, remarks, contact_interne, exemplaires)
|
||||
* bool $showEmailConfirmation — E-mail de confirmation fieldset
|
||||
@@ -44,7 +43,6 @@
|
||||
* string $filesMode — 'add' | 'edit' (determines which file inputs to show)
|
||||
* ?string $currentCover — existing cover file info for edit mode
|
||||
* array $currentFiles — existing thesis files for edit mode
|
||||
* ?string $currentBannerPath — existing banner path for edit mode
|
||||
* ?string $currentContextNote — existing context note for edit mode
|
||||
* array $currentRaw — raw thesis row for edit mode
|
||||
* ?string $currentAuthorShowContact — author show_contact flag for edit mode
|
||||
@@ -84,7 +82,7 @@ $showFlash = $showFlash ?? false;
|
||||
$showContact = $showContact ?? false;
|
||||
$showCoverPreview = $showCoverPreview ?? false;
|
||||
$showExistingFiles = $showExistingFiles ?? false;
|
||||
$showBannerPreview = $showBannerPreview ?? false;
|
||||
$showBannerPreview = false; // Banners merged into covers — field removed
|
||||
$showContextNote = $showContextNote ?? false;
|
||||
$showBackoffice = $showBackoffice ?? false;
|
||||
$showEmailConfirmation = $showEmailConfirmation ?? false;
|
||||
@@ -249,11 +247,11 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png" data-preview="fp-couverture">
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png,image/webp" data-preview="fp-couverture">
|
||||
<div id="fp-couverture" class="file-preview-list" aria-live="polite"></div>
|
||||
<small><?= empty($currentCover)
|
||||
? "JPG, PNG. Format 4:3 recommandé. Max 20 MB."
|
||||
: "Laisser vide pour conserver la couverture actuelle. JPG, PNG. Max 20 MB." ?></small>
|
||||
? "JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB."
|
||||
: "Laisser vide pour conserver la couverture actuelle. JPG, PNG ou WEBP. Max 20 MB." ?></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -377,27 +375,7 @@ $checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banner image -->
|
||||
<div class="admin-form-group">
|
||||
<label>Image bannière (accueil) :</label>
|
||||
<div class="admin-file-input">
|
||||
<?php if (!empty($currentBannerPath)): ?>
|
||||
<div class="admin-banner-preview">
|
||||
<img src="/media.php?path=<?= urlencode(
|
||||
$currentBannerPath,
|
||||
) ?>" alt="Bannière actuelle">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="remove_banner" value="1"> Supprimer la bannière
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="file" name="banner" id="banner" accept="image/jpeg,image/png,image/webp" data-preview="fp-banner">
|
||||
<div id="fp-banner" class="file-preview-list" aria-live="polite"></div>
|
||||
<small><?= empty($currentBannerPath)
|
||||
? "JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 20 MB."
|
||||
: "Laisser vide pour conserver la bannière actuelle. JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 20 MB." ?></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* Partial: student popover preview card(s).
|
||||
*
|
||||
* Expected variables:
|
||||
* $theses array rows from Database::getThesesByAuthorName()
|
||||
* $name string student name
|
||||
* $theses array rows from Database::getThesesByAuthorName()
|
||||
* $name string student name
|
||||
* $coverMap array<int,string> thesis_id => cover file_path
|
||||
*/
|
||||
|
||||
foreach ($theses as $t):
|
||||
@@ -20,8 +21,9 @@ foreach ($theses as $t):
|
||||
]);
|
||||
?>
|
||||
<a href="/tfe?id=<?= (int)$t['id'] ?>" class="student-card">
|
||||
<?php if (!empty($t['banner_path'])): ?>
|
||||
<div class="student-card__banner" style="background-image:url('<?= htmlspecialchars($t['banner_path']) ?>')"></div>
|
||||
<?php $cover = $coverMap[$t['id']] ?? null; ?>
|
||||
<?php if ($cover): ?>
|
||||
<div class="student-card__banner" style="background-image:url('/media?path=<?= urlencode($cover) ?>')"></div>
|
||||
<?php else: ?>
|
||||
<div class="student-card__banner student-card__banner--gradient">
|
||||
<span class="student-card__gradient-author"><?= htmlspecialchars($t['authors'] ?? '') ?></span>
|
||||
|
||||
@@ -14,13 +14,7 @@
|
||||
<li class="card">
|
||||
<a href="/tfe?id=<?= (int)$item["id"] ?>">
|
||||
<?php
|
||||
$thumb = null;
|
||||
if (!empty($item['banner_path'])) {
|
||||
$thumb = $item['banner_path'];
|
||||
}
|
||||
if (!$thumb && isset($coverMap[$item['id']])) {
|
||||
$thumb = $coverMap[$item['id']];
|
||||
}
|
||||
$thumb = $coverMap[$item['id']] ?? null;
|
||||
?>
|
||||
<?php if ($thumb): ?>
|
||||
<figure>
|
||||
|
||||
@@ -51,7 +51,15 @@
|
||||
<?php if (!empty($results)): ?>
|
||||
<ul class="results-grid">
|
||||
<?php foreach ($results as $item): ?>
|
||||
<li><a href="/tfe?id=<?= (int)$item['id'] ?>" class="result-card">
|
||||
<?php $thumb = $coverMap[$item['id']] ?? null; ?>
|
||||
<li><a href="/tfe?id=<?= (int)$item['id'] ?>" class="result-card<?= $thumb ? ' result-card--has-cover' : '' ?>">
|
||||
<?php if ($thumb): ?>
|
||||
<figure class="result-card__cover">
|
||||
<img src="/media?path=<?= urlencode($thumb) ?>"
|
||||
alt="Couverture — <?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy">
|
||||
</figure>
|
||||
<?php endif; ?>
|
||||
<span class="result-card__authors"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<span class="result-card__title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
<small class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></small>
|
||||
|
||||
Reference in New Issue
Block a user