formulaire: default interne, unpublished, contact toggle, settings section

This commit is contained in:
Pontoporeia
2026-04-15 11:57:55 +02:00
parent 67a4aaac26
commit 0cb4451218
13 changed files with 490 additions and 44 deletions

11
TODO.md
View File

@@ -12,3 +12,14 @@
- [x] Fix nginx/SETUP.md manual step to use just manage-admin-users instead of raw htpasswd
- [x] Fix root README.md dead reference to docs/TODO.SECURITY.md
- [x] Update root README.md project structure (remove nginx/scripts/ entry)
- [x] Form default visibility: "Interne" (access_type_id=2) set at DB insert level
- [x] New TFE always created unpublished (is_published=0 hardcoded in createThesis)
- [x] Contact checkbox: `show_contact` column on authors; checkbox in add/edit forms; tfe.php shows contact only if enabled
- [x] Migration 008: site_settings table + show_contact column + rebuilt views with author_email/author_show_contact/access_type_id
- [x] Formulaire section in parametres.php: toggle switches for Interdit/Interne/Libre access types
- [x] Libre option disabled by default (access_type_libre_enabled=0)
- [x] Add visibility select in add.php, filtered by enabled access types, defaulting to Interne
- [x] Edit.php: pre-populate contact email from DB; show contact_public checkbox with current state
- [x] tfe.php: contact shown from author_email+show_contact; baiu_link relabeled as "Lien"
- [x] actions/settings.php: handler for formulaire settings form
- [x] CSS: admin-toggle pill switches + admin-settings-toggles layout + admin-form-group

View File

@@ -0,0 +1,32 @@
<?php
require_once __DIR__ . '/../../../config/bootstrap.php';
require_once __DIR__ . '/../../../src/AdminAuth.php';
AdminAuth::requireLogin();
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
App::flash('error', "Erreur de sécurité : token invalide.");
header('Location: /admin/parametres.php');
exit;
}
require_once APP_ROOT . '/src/Database.php';
$db = new Database();
$section = $_POST['section'] ?? '';
if ($section === 'formulaire') {
// Save access-type toggle settings
$allowed = ['access_type_libre_enabled', 'access_type_interne_enabled', 'access_type_interdit_enabled'];
foreach ($allowed as $key) {
$value = isset($_POST[$key]) ? '1' : '0';
$db->setSetting($key, $value);
}
App::flash('success', "Paramètres du formulaire mis à jour.");
} else {
App::flash('error', "Section inconnue.");
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
header('Location: /admin/parametres.php');
exit;

View File

@@ -63,6 +63,16 @@ function wasSelected($key, $value) {
<?php $name = 'auteurice'; $label = 'Auteur·ice(s) :'; $value = old('auteurice'); $required = true; $attrs = withAutofocus('auteurice', ['autocomplete' => 'name']); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
<?php $name = 'mail'; $label = 'Contact(s) (optionnel) [mail/site/insta/etc.] :'; $value = old('mail'); $attrs = ['autocomplete' => 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
<!-- Contact visibility -->
<div class="admin-form-group">
<label class="admin-checkbox-label">
<input type="checkbox" name="contact_public" value="1"
<?= isset($formData['contact_public']) ? 'checked' : '' ?>>
Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
</label>
<small>Si cette case est cochée, votre contact apparaîtra sur la page publique de votre TFE.</small>
</div>
<?php require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; ?>
<?php
@@ -105,6 +115,26 @@ function wasSelected($key, $value) {
<?php $name = 'files'; $label = 'Fichiers du TFE :'; $accept = '.pdf,.jpg,.jpeg,.png,.mp4,.zip,.vtt'; $hint = 'PDF, JPG, PNG, MP4, ZIP. Max 50 MB par fichier. Pour les vidéos, un fichier .vtt de sous-titres peut être joint (il sera associé automatiquement à la vidéo correspondante).'; $multiple = true; include APP_ROOT . '/templates/partials/form/file-field.php'; ?>
<?php
// Visibility select — only show options enabled in settings
$accessOptions = array_map(function($at) {
return ['id' => $at['id'], 'name' => $at['name']];
}, $enabledAccessTypes);
// Default: Interne (id=2)
$defaultAccessType = 2;
$selectedAccessType = isset($formData['access_type_id'])
? (int)$formData['access_type_id']
: $defaultAccessType;
$name = 'access_type_id';
$label = 'Visibilité / Accès :';
$options = $accessOptions;
$selected = $selectedAccessType;
$placeholder = null;
$required = true;
$attrs = [];
include APP_ROOT . '/templates/partials/form/select-field.php';
?>
<div class="admin-form-footer">
<button type="submit" name="go" class="admin-btn">Soumettre</button>
</div>

View File

@@ -45,7 +45,17 @@ try {
<input type="hidden" name="thesis_id" value="<?= $thesisId ?>">
<?php $name = 'auteurice'; $label = 'Auteur·ice(s) :'; $value = htmlspecialchars($thesis['authors']); $required = true; $attrs = array_merge(['autocomplete' => 'name'], $autofocusField === 'auteurice' ? ['autofocus' => true] : []); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
<?php $name = 'mail'; $label = 'Contact :'; $value = ''; $attrs = ['autocomplete' => 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
<?php $name = 'mail'; $label = 'Contact :'; $value = htmlspecialchars($currentAuthorEmail ?? ''); $attrs = ['autocomplete' => 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
<!-- Contact visibility -->
<div class="admin-form-group">
<label class="admin-checkbox-label">
<input type="checkbox" name="contact_public" value="1"
<?= !empty($currentAuthorShowContact) ? 'checked' : '' ?>>
Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
</label>
<small>Si cette case est cochée, le contact apparaît sur la page publique du TFE.</small>
</div>
<?php
$name = 'année'; $label = 'Année :'; $value = htmlspecialchars((string)$thesis['year']); $required = true;

View File

@@ -9,6 +9,10 @@ $credentialsFile = APP_ROOT . '/config/admin_credentials.php';
$hasPassword = defined('ADMIN_PASSWORD_HASH');
$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag');
require_once APP_ROOT . '/src/Database.php';
$db = new Database();
$siteSettings = $db->getAllSettings();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
@@ -56,7 +60,58 @@ if (empty($_SESSION['csrf_token'])) {
</section>
<!-- ══════════════════════════════════════════════════════════════
SECTION 2 — Compte administrateur
SECTION 2 — Formulaire
══════════════════════════════════════════════════════════════ -->
<section class="admin-settings-section" aria-labelledby="settings-formulaire-title">
<h2 class="admin-settings-section__title" id="settings-formulaire-title">Formulaire</h2>
<p>Options de visibilité disponibles dans le formulaire d'ajout de TFE.</p>
<p><small>L'option <strong>Libre</strong> ne sera activée qu'à partir de l'année académique prochaine.</small></p>
<form method="post" action="actions/settings.php" class="admin-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="formulaire">
<div class="admin-settings-toggles">
<label class="admin-toggle-row">
<span class="admin-toggle-label">
<strong>Interdit</strong>
<small>TFE non disponible en physique ni sur le site</small>
</span>
<input type="checkbox" name="access_type_interdit_enabled" value="1"
class="admin-toggle"
<?= ($siteSettings['access_type_interdit_enabled'] ?? '1') === '1' ? 'checked' : '' ?>>
</label>
<label class="admin-toggle-row">
<span class="admin-toggle-label">
<strong>Interne</strong>
<small>TFE accessible uniquement sur place en physique</small>
</span>
<input type="checkbox" name="access_type_interne_enabled" value="1"
class="admin-toggle"
<?= ($siteSettings['access_type_interne_enabled'] ?? '1') === '1' ? 'checked' : '' ?>>
</label>
<label class="admin-toggle-row admin-toggle-row--disabled">
<span class="admin-toggle-label">
<strong>Libre</strong>
<small>Libre accès — disponible à partir de l'année académique prochaine</small>
</span>
<input type="checkbox" name="access_type_libre_enabled" value="1"
class="admin-toggle"
<?= ($siteSettings['access_type_libre_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
</label>
</div>
<div class="admin-form-footer">
<button type="submit" class="admin-btn">Enregistrer</button>
</div>
</form>
</section>
<!-- ══════════════════════════════════════════════════════════════
SECTION 3 — Compte administrateur
══════════════════════════════════════════════════════════════ -->
<section class="admin-settings-section" aria-labelledby="settings-account-title">
<h2 class="admin-settings-section__title" id="settings-account-title">Compte administrateur</h2>

View File

@@ -1164,3 +1164,83 @@
height: 50vh;
border: 1px solid var(--border-primary);
}
/* ── Settings: formulaire toggles ──────────────────────────────────────────── */
.admin-settings-toggles {
display: flex;
flex-direction: column;
gap: var(--space-xs);
margin-bottom: var(--space-m);
}
.admin-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-m);
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
border-radius: 4px;
padding: var(--space-xs) var(--space-m);
cursor: pointer;
}
.admin-toggle-row--disabled {
opacity: 0.6;
}
.admin-toggle-label {
display: flex;
flex-direction: column;
gap: 2px;
}
.admin-toggle-label strong {
font-size: var(--step-0);
}
.admin-toggle-label small {
color: var(--text-secondary);
font-size: var(--step--2);
}
/* Native checkbox styled as toggle pill */
.admin-toggle {
appearance: none;
-webkit-appearance: none;
width: 40px;
height: 22px;
background: var(--border-primary);
border-radius: 11px;
position: relative;
cursor: pointer;
flex-shrink: 0;
transition: background 0.2s;
}
.admin-toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
transition: transform 0.2s;
}
.admin-toggle:checked {
background: var(--accent-primary);
}
.admin-toggle:checked::after {
transform: translateX(18px);
}
/* ── Form group (for checkbox inside .admin-form) ──────────────────────────── */
.admin-form-group {
display: flex;
flex-direction: column;
gap: var(--space-3xs);
}

View File

@@ -149,18 +149,42 @@ extract($ctrl->handle());
</div>
<?php endif; ?>
<?php if (!empty($data['baiu_link'])): ?>
<?php
$_contactHref = htmlspecialchars($data['baiu_link']);
$_contactLabel = preg_replace('#^https?://#i', '', rtrim($data['baiu_link'], '/'));
?>
<?php if (!empty($data['author_email']) && !empty($data['author_show_contact'])): ?>
<div>
<dt>Contact :</dt>
<dd>
<a href="<?= $_contactHref ?>" target="_blank" rel="noopener">
<?= htmlspecialchars($_contactLabel) ?>
<?php
$_contact = $data['author_email'];
$_isUrl = filter_var($_contact, FILTER_VALIDATE_URL) !== false;
$_isEmail = !$_isUrl && str_contains($_contact, '@');
if ($_isUrl):
?>
<a href="<?= htmlspecialchars($_contact) ?>" target="_blank" rel="noopener">
<?= htmlspecialchars(preg_replace('#^https?://#i', '', rtrim($_contact, '/'))) ?>
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
<?php elseif ($_isEmail): ?>
<a href="mailto:<?= htmlspecialchars($_contact) ?>"><?= htmlspecialchars($_contact) ?></a>
<?php else: ?>
<?= htmlspecialchars($_contact) ?>
<?php endif; ?>
</dd>
</div>
<?php endif; ?>
<?php if (!empty($data['baiu_link'])): ?>
<?php
$_baiuHref = htmlspecialchars($data['baiu_link']);
$_baiuLabel = preg_replace('#^https?://#i', '', rtrim($data['baiu_link'], '/'));
?>
<div>
<dt>Lien :</dt>
<dd>
<a href="<?= $_baiuHref ?>" target="_blank" rel="noopener">
<?= htmlspecialchars($_baiuLabel) ?>
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
</dd>
</div>
<?php endif; ?>

View File

@@ -809,21 +809,24 @@ class Database {
/**
* Find or create an author
*/
public function findOrCreateAuthor($name, $email = null) {
public function findOrCreateAuthor($name, $email = null, bool $showContact = false) {
$stmt = $this->pdo->prepare("SELECT id FROM authors WHERE name = ?");
$stmt->execute([$name]);
$author = $stmt->fetch();
if ($author) {
if ($email && $email !== '') {
$updateStmt = $this->pdo->prepare("UPDATE authors SET email = ? WHERE id = ?");
$updateStmt->execute([$email, $author['id']]);
$updateStmt = $this->pdo->prepare("UPDATE authors SET email = ?, show_contact = ? WHERE id = ?");
$updateStmt->execute([$email, $showContact ? 1 : 0, $author['id']]);
} else {
$updateStmt = $this->pdo->prepare("UPDATE authors SET show_contact = ? WHERE id = ?");
$updateStmt->execute([$showContact ? 1 : 0, $author['id']]);
}
return $author['id'];
}
$stmt = $this->pdo->prepare("INSERT INTO authors (name, email) VALUES (?, ?)");
$stmt->execute([$name, $email]);
$stmt = $this->pdo->prepare("INSERT INTO authors (name, email, show_contact) VALUES (?, ?, ?)");
$stmt->execute([$name, $email, $showContact ? 1 : 0]);
return $this->pdo->lastInsertId();
}
@@ -1048,6 +1051,77 @@ class Database {
return $stmt->fetchAll();
}
// ========================================================================
// SITE SETTINGS
// ========================================================================
/**
* Get a single site setting value by key. Returns $default if not found.
*/
public function getSetting(string $key, string $default = ''): string {
$stmt = $this->pdo->prepare("SELECT value FROM site_settings WHERE key = ? LIMIT 1");
$stmt->execute([$key]);
$row = $stmt->fetch();
return $row ? (string) $row['value'] : $default;
}
/**
* Get all site settings as an associative array [ key => value ].
*/
public function getAllSettings(): array {
$stmt = $this->pdo->query("SELECT key, value FROM site_settings");
$rows = $stmt->fetchAll();
$out = [];
foreach ($rows as $r) {
$out[$r['key']] = $r['value'];
}
return $out;
}
/**
* Upsert a site setting.
*/
public function setSetting(string $key, string $value): void {
$this->pdo->prepare(
"INSERT INTO site_settings (key, value, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP"
)->execute([$key, $value]);
}
/**
* Return access types that are enabled in the add-thesis form,
* filtered by site_settings toggles.
* 'Libre' (id=1) is excluded unless access_type_libre_enabled = '1'.
* 'Interne' (id=2) is excluded unless access_type_interne_enabled = '1'.
* 'Interdit' (id=3) is excluded unless access_type_interdit_enabled = '1'.
*/
public function getEnabledFormAccessTypes(): array {
$settings = $this->getAllSettings();
$all = $this->getAccessTypes();
$map = [
'Libre' => $settings['access_type_libre_enabled'] ?? '0',
'Interne' => $settings['access_type_interne_enabled'] ?? '1',
'Interdit' => $settings['access_type_interdit_enabled'] ?? '1',
];
return array_values(array_filter($all, fn($at) => ($map[$at['name']] ?? '0') === '1'));
}
/**
* Update the show_contact flag for the first author of a thesis.
*/
public function setAuthorShowContact(int $thesisId, bool $show): void {
$stmt = $this->pdo->prepare(
"UPDATE authors SET show_contact = ?
WHERE id = (
SELECT author_id FROM thesis_authors
WHERE thesis_id = ?
ORDER BY author_order LIMIT 1
)"
);
$stmt->execute([$show ? 1 : 0, $thesisId]);
}
// ========================================================================
// JURY METHODS
// ========================================================================
@@ -1439,7 +1513,8 @@ class Database {
foreach ($authors as $index => $author) {
$name = trim($author['name'] ?? '');
if ($name === '') continue;
$authorId = $this->findOrCreateAuthor($name, $author['email'] ?? null);
$showContact = !empty($author['show_contact']);
$authorId = $this->findOrCreateAuthor($name, $author['email'] ?? null, $showContact);
$stmt->execute([$thesisId, $authorId, $index + 1]);
}
}
@@ -1453,8 +1528,10 @@ class Database {
orientation_id, ap_program_id, finality_id,
synopsis, file_size_info,
baiu_link, license_id,
access_type_id,
is_published,
submitted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP)
");
$stmt->execute([
@@ -1469,6 +1546,7 @@ class Database {
!empty($data['file_size_info']) ? $data['file_size_info'] : null,
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
isset($data['license_id']) ? $data['license_id'] : null,
isset($data['access_type_id']) ? (int)$data['access_type_id'] : 2, // default: Interne
]);
$thesisId = (int)$this->pdo->lastInsertId();

View File

@@ -70,12 +70,13 @@ class ThesisCreateController
public function loadFormData(): array
{
return [
'orientations' => $this->db->getAllOrientations(),
'apPrograms' => $this->db->getAllAPPrograms(),
'finalityTypes' => $this->db->getAllFinalityTypes(),
'languages' => $this->db->getAllLanguages(),
'formatTypes' => $this->db->getAllFormatTypes(),
'licenseTypes' => $this->db->getAllLicenseTypes(),
'orientations' => $this->db->getAllOrientations(),
'apPrograms' => $this->db->getAllAPPrograms(),
'finalityTypes' => $this->db->getAllFinalityTypes(),
'languages' => $this->db->getAllLanguages(),
'formatTypes' => $this->db->getAllFormatTypes(),
'licenseTypes' => $this->db->getAllLicenseTypes(),
'enabledAccessTypes' => $this->db->getEnabledFormAccessTypes(),
];
}
@@ -107,7 +108,7 @@ class ThesisCreateController
$data = $this->validateAndSanitise($post);
// ── 2. Find / create author ───────────────────────────────────────────
$authorId = $this->db->findOrCreateAuthor($data['auteurName'], $data['mail'] ?: null);
$authorId = $this->db->findOrCreateAuthor($data['auteurName'], $data['mail'] ?: null, $data['showContact']);
error_log("ThesisCreateController: author ID $authorId");
// ── 34. DB writes in a transaction ───────────────────────────────────
@@ -125,6 +126,7 @@ class ThesisCreateController
'file_size_info' => $data['durationInfo'],
'baiu_link' => $data['lien'],
'license_id' => $data['licenseId'],
'access_type_id' => $data['accessTypeId'],
'author_id' => $authorId,
]);
@@ -192,7 +194,8 @@ class ThesisCreateController
'Nom/Prénom/Pseudo'
);
$mail = !empty($post['mail']) ? $this->sanitiseString($post['mail']) : '';
$mail = !empty($post['mail']) ? $this->sanitiseString($post['mail']) : '';
$showContact = !empty($post['contact_public']) ? true : false;
$annee = filter_var($post['année'] ?? '', FILTER_VALIDATE_INT);
if ($annee === false || $annee < 2000 || $annee > ((int) date('Y') + 1)) {
@@ -265,6 +268,12 @@ class ThesisCreateController
$licenseId = filter_var($post['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
// Access type — must be one of the enabled types; default 2 (Interne)
$accessTypeId = filter_var($post['access_type_id'] ?? '', FILTER_VALIDATE_INT);
if ($accessTypeId === false || $accessTypeId <= 0) {
$accessTypeId = 2; // Interne
}
// External link (optional)
$lien = '';
if (!empty($post['lien'])) {
@@ -275,10 +284,10 @@ class ThesisCreateController
}
return compact(
'auteurName', 'mail', 'annee', 'orientationId', 'apProgramId',
'auteurName', 'mail', 'showContact', 'annee', 'orientationId', 'apProgramId',
'finalityId', 'titre', 'subtitle', 'synopsis', 'durationInfo',
'juryMembers', 'keywords', 'languageIds', 'formatIds',
'licenseId', 'lien'
'licenseId', 'lien', 'accessTypeId'
);
}

View File

@@ -91,22 +91,28 @@ class ThesisEditController
$currentAccessTypeId = $rawRow['access_type_id'] ?? null;
$currentContextNote = $rawRow['context_note'] ?? '';
// Author contact info (from view)
$currentAuthorEmail = $thesis['author_email'] ?? '';
$currentAuthorShowContact = (bool)($thesis['author_show_contact'] ?? false);
return [
'thesis' => $thesis,
'currentLanguages' => $currentLanguages,
'currentFormats' => $currentFormats,
'jury' => $jury,
'orientations' => $orientations,
'apPrograms' => $apPrograms,
'finalityTypes' => $finalityTypes,
'languages' => $languages,
'formatTypes' => $formatTypes,
'licenseTypes' => $licenseTypes,
'accessTypes' => $accessTypes,
'currentLicenseId' => $currentLicenseId,
'currentAccessTypeId' => $currentAccessTypeId,
'currentContextNote' => $currentContextNote,
'pageTitle' => 'Éditer TFE - ' . htmlspecialchars($thesis['title']),
'thesis' => $thesis,
'currentLanguages' => $currentLanguages,
'currentFormats' => $currentFormats,
'jury' => $jury,
'orientations' => $orientations,
'apPrograms' => $apPrograms,
'finalityTypes' => $finalityTypes,
'languages' => $languages,
'formatTypes' => $formatTypes,
'licenseTypes' => $licenseTypes,
'accessTypes' => $accessTypes,
'currentLicenseId' => $currentLicenseId,
'currentAccessTypeId' => $currentAccessTypeId,
'currentContextNote' => $currentContextNote,
'currentAuthorEmail' => $currentAuthorEmail,
'currentAuthorShowContact' => $currentAuthorShowContact,
'pageTitle' => 'Éditer TFE - ' . htmlspecialchars($thesis['title']),
];
}
@@ -162,13 +168,15 @@ class ThesisEditController
// ── 2. Authors ────────────────────────────────────────────────────
$authorsRaw = trim($post['auteurice'] ?? '');
$showContact = !empty($post['contact_public']);
$authorEntries = [];
if ($authorsRaw !== '') {
foreach (array_map('trim', explode(',', $authorsRaw)) as $i => $name) {
if ($name !== '') {
$authorEntries[] = [
'name' => $name,
'email' => $i === 0 ? ($post['mail'] ?? null) : null,
'name' => $name,
'email' => $i === 0 ? ($post['mail'] ?? null) : null,
'show_contact' => $i === 0 ? $showContact : false,
];
}
}

View File

@@ -0,0 +1,89 @@
-- Migration 008: Formulaire settings + contact visibility
-- Adds site_settings key-value table for admin-configurable options
-- Adds show_contact column to authors table
-- Adds author_email + author_show_contact to views
-- ── 1. site_settings ─────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS site_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT '',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Default formulaire settings:
-- access_type_interdit_enabled = 1 (Interdit is available in the add form)
-- access_type_interne_enabled = 1 (Interne is available in the add form)
-- access_type_libre_enabled = 0 (Libre is NOT yet available — next academic year)
INSERT OR IGNORE INTO site_settings (key, value) VALUES
('access_type_interdit_enabled', '1'),
('access_type_interne_enabled', '1'),
('access_type_libre_enabled', '0');
-- ── 2. show_contact on authors ────────────────────────────────────────────────
-- NOTE: SQLite has no IF NOT EXISTS for ALTER TABLE.
-- The migrate.sh script guards against re-running; ignore errors on existing DBs.
ALTER TABLE authors ADD COLUMN show_contact INTEGER NOT NULL DEFAULT 0;
-- ── 3. Rebuild views to expose author_email and author_show_contact ───────────
DROP VIEW IF EXISTS v_theses_public;
DROP VIEW IF EXISTS v_theses_full;
CREATE VIEW IF NOT EXISTS v_theses_full AS
SELECT
t.id,
t.identifier,
t.title,
t.subtitle,
t.year,
t.is_doctoral,
o.name as orientation,
ap.name as ap_program,
ft.name as finality_type,
t.synopsis,
t.context_note,
t.duration_minutes,
t.duration_pages,
t.file_size_info,
at.name as access_type,
lt.name as license_type,
t.license_id,
t.jury_points,
t.submitted_at,
t.defense_date,
t.published_at,
t.is_published,
t.baiu_link,
t.banner_path,
t.access_type_id,
GROUP_CONCAT(DISTINCT a.name) as authors,
GROUP_CONCAT(DISTINCT s.name) as supervisors,
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
GROUP_CONCAT(DISTINCT l.name) as languages,
GROUP_CONCAT(DISTINCT fmt.name) as formats,
GROUP_CONCAT(DISTINCT tg.name) as keywords,
-- First author's email and contact-visibility flag
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_email,
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_show_contact
FROM theses t
LEFT JOIN orientations o ON t.orientation_id = o.id
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
LEFT JOIN finality_types ft ON t.finality_id = ft.id
LEFT JOIN access_types at ON t.access_type_id = at.id
LEFT JOIN license_types lt ON t.license_id = lt.id
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
LEFT JOIN authors a ON ta.author_id = a.id
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
LEFT JOIN languages l ON tl.language_id = l.id
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
LEFT JOIN tags tg ON tt.tag_id = tg.id
GROUP BY t.id;
CREATE VIEW IF NOT EXISTS v_theses_public AS
SELECT * FROM v_theses_full
WHERE is_published = 1;

Binary file not shown.

View File

@@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS authors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
show_contact INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
@@ -278,6 +279,21 @@ CREATE TABLE IF NOT EXISTS thesis_files (
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
);
-- ============================================================================
-- SITE SETTINGS
-- ============================================================================
CREATE TABLE IF NOT EXISTS site_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT '',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT OR IGNORE INTO site_settings (key, value) VALUES
('access_type_interdit_enabled', '1'),
('access_type_interne_enabled', '1'),
('access_type_libre_enabled', '0');
-- ============================================================================
-- STATIC PAGES / CONTENT MANAGEMENT
-- ============================================================================
@@ -375,6 +391,7 @@ SELECT
at.name as access_type,
lt.name as license_type,
t.license_id,
t.access_type_id,
t.jury_points,
t.submitted_at,
t.defense_date,
@@ -389,7 +406,10 @@ SELECT
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
GROUP_CONCAT(DISTINCT l.name) as languages,
GROUP_CONCAT(DISTINCT fmt.name) as formats,
GROUP_CONCAT(DISTINCT tg.name) as keywords
GROUP_CONCAT(DISTINCT tg.name) as keywords,
-- First author's email and contact-visibility flag
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_email,
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as author_show_contact
FROM theses t
LEFT JOIN orientations o ON t.orientation_id = o.id
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id