mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Fix edit form: is_published reset, contact decoupling, note label, author name case
- Fix #1: Add is_published to getThesisRawFields() SELECT so the publish checkbox stays checked when editing an already-published TFE. - Fix #2: Rename 'Note contextuelle' → 'Note contextuelle relative à soutenance' in all templates and StudentEmail. - Fix #3: Update findOrCreateAuthor to also UPDATE the author name when a record is found by name (fixes inability to capitalise names). - Fix #4/#5: Decouple contact_interne (private author email) from contact_visible (public contact on TFE page). Add migration 037 to add contact_visible TEXT column to theses table and rebuild v_theses_full view. Update all controllers, templates, and DB methods to treat them independently. - Fix #6: Investigated libre→interne restriction — no code barrier found; likely resolved by is_published fix.
This commit is contained in:
17
TODO.md
17
TODO.md
@@ -1,12 +1,9 @@
|
||||
# TODO
|
||||
|
||||
- [x] Ajouter `PeerTubeService::deleteVideo()` pour supprimer une vidéo via l'API PeerTube
|
||||
- [x] Modifier `deleteThesisFileToTrash()` pour appeler `deleteVideo()` quand `file_path` commence par `peertube_ids:`
|
||||
- [x] Modifier `hardDeleteThesis()` pour supprimer les vidéos PeerTube associées
|
||||
- [x] Commit + jj new
|
||||
- [x] Ajouter l'affichage de la finalité sur la page publique TFE (tfe.php)
|
||||
- [x] Fix "ATELIERS PLURIDISCIPLINAIRES" mid-word break in repertoire column headers
|
||||
- [x] Mise à jour auto de l'identifiant quand l'année change en back-office
|
||||
- [x] Améliorer les hints du champ contact dans le formulaire étudiant
|
||||
- [x] Rendre le fichier TFE optionnel pour Site web / Performance / Installation (note d'intention reste obligatoire)
|
||||
- [x] Augmenter les limites d'upload : vidéo/audio 8 GB, images/archives 1 GB, nginx 8 GB
|
||||
- [x] Fix #1: TFE publié se dépublie quand on modifie ses données (is_published missing from getThesisRawFields SELECT)
|
||||
- [x] Fix #2: Renommer "Note contextuelle" → "Note contextuelle relative à soutenance"
|
||||
- [x] Fix #3: Impossible de mettre une majuscule au nom d'étudiant·e (findOrCreateAuthor n'update pas le name)
|
||||
- [x] Fix #4: Décorréler contact interne et contact visible (ajouter colonne contact_visible sur theses)
|
||||
- [x] Fix #5: "Contact public : non" partout, non modifiable, sans impact
|
||||
- [x] Fix #6: Investiguer "libre → interne" impossible — aucune restriction trouvée dans le code admin, probablement causé par Fix #1 (is_published reset)
|
||||
- [ ] Commit + jj new
|
||||
|
||||
80
app/migrations/applied/037_contact_visible.sql
Normal file
80
app/migrations/applied/037_contact_visible.sql
Normal file
@@ -0,0 +1,80 @@
|
||||
-- Migration 037: add contact_visible to theses, update v_theses_full.
|
||||
--
|
||||
-- contact_visible: the public-facing contact shown on the TFE page
|
||||
-- (email, URL, social handle, etc.). Decoupled from the author's
|
||||
-- internal email (authors.email), which remains the private contact
|
||||
-- used for confirmation emails.
|
||||
--
|
||||
-- Also ensures is_published is exposed via the view.
|
||||
|
||||
ALTER TABLE theses ADD COLUMN contact_visible TEXT DEFAULT NULL;
|
||||
|
||||
-- Rebuild v_theses_full to include contact_visible
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
t.objet,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.license_custom,
|
||||
t.access_type_id,
|
||||
t.is_published,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.baiu_link,
|
||||
t.exemplaire_baiu,
|
||||
t.exemplaire_erg,
|
||||
t.cc2r,
|
||||
t.remarks,
|
||||
t.jury_note_added,
|
||||
t.contact_visible,
|
||||
GROUP_CONCAT(DISTINCT a.name ORDER BY a.name ASC) 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' AND ts.is_ulb = 0 THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' AND ts.is_ulb = 1 THEN s.name END) as jury_promoteurs_ulb,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' AND ts.is_external = 0 THEN s.name END) as jury_lecteurs_internes,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' AND ts.is_external = 1 THEN s.name END) as jury_lecteurs_externes,
|
||||
GROUP_CONCAT(DISTINCT UPPER(SUBSTR(l.name,1,1)) || SUBSTR(l.name,2)) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords,
|
||||
(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 contact_interne,
|
||||
(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 contact_public
|
||||
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 AND l.deleted_at IS NULL
|
||||
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 AND tg.deleted_at IS NULL
|
||||
WHERE t.deleted_at IS NULL
|
||||
GROUP BY t.id;
|
||||
|
||||
CREATE VIEW v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
@@ -410,6 +410,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
$contactInterne = null;
|
||||
$contactPublic = false;
|
||||
$currentContextNote = null;
|
||||
$currentContactVisible = null;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -148,6 +148,7 @@ class ThesisCreateController
|
||||
'subtitle' => $data['subtitle'],
|
||||
'synopsis' => $data['synopsis'],
|
||||
'context_note' => $data['contextNote'],
|
||||
'contact_visible' => $data['contactVisible'] ?? '',
|
||||
'baiu_link' => $data['lien'],
|
||||
'license_id' => $data['licenseId'],
|
||||
'license_custom' => $data['licenseCustom'],
|
||||
@@ -310,12 +311,17 @@ class ThesisCreateController
|
||||
if ($contactInterne !== '') {
|
||||
$mail = $contactInterne;
|
||||
}
|
||||
// contact_public: respected if present (admin form); defaults to true for student forms
|
||||
// where the spec says contact is always visible when provided.
|
||||
// contact_visible: what appears publicly on the TFE page
|
||||
// In admin mode: from contact_visible field. In student mode: from mail field.
|
||||
$contactVisible = trim($post['contact_visible'] ?? '');
|
||||
if ($contactVisible === '' && $mail !== '') {
|
||||
$contactVisible = $mail;
|
||||
}
|
||||
// showContact for backwards compat
|
||||
if (array_key_exists('contact_public', $post)) {
|
||||
$showContact = !empty($post['contact_public']);
|
||||
} else {
|
||||
$showContact = $mail !== '';
|
||||
$showContact = $contactVisible !== '';
|
||||
}
|
||||
|
||||
$annee = filter_var($post['année'] ?? '', FILTER_VALIDATE_INT);
|
||||
@@ -528,6 +534,7 @@ class ThesisCreateController
|
||||
return compact(
|
||||
'authorNames',
|
||||
'mail',
|
||||
'contactVisible',
|
||||
'showContact',
|
||||
'annee',
|
||||
'orientationId',
|
||||
|
||||
@@ -108,6 +108,7 @@ class ThesisEditController
|
||||
$currentLicenseId = $rawRow['license_id'] ?? null;
|
||||
$currentAccessTypeId = $rawRow['access_type_id'] ?? null;
|
||||
$currentContextNote = $rawRow['context_note'] ?? '';
|
||||
$currentContactVisible = $rawRow['contact_visible'] ?? '';
|
||||
|
||||
// Author contact info (from view)
|
||||
$contactInterne = $thesis['contact_interne'] ?? '';
|
||||
@@ -130,6 +131,7 @@ class ThesisEditController
|
||||
'currentLicenseId' => $currentLicenseId,
|
||||
'currentAccessTypeId' => $currentAccessTypeId,
|
||||
'currentContextNote' => $currentContextNote,
|
||||
'currentContactVisible' => $currentContactVisible,
|
||||
'contactInterne' => $contactInterne,
|
||||
'contactPublic' => $contactPublic,
|
||||
'currentRaw' => $rawRow,
|
||||
@@ -206,6 +208,7 @@ class ThesisEditController
|
||||
'finality_id' => ($v = intval($post['finality'] ?? 0)) > 0 ? $v : null,
|
||||
'synopsis' => trim($post['synopsis'] ?? ''),
|
||||
'context_note' => trim($post['context_note'] ?? ''),
|
||||
'contact_visible' => trim($post['contact_visible'] ?? ''),
|
||||
'baiu_link' => trim($post['lien'] ?? ''),
|
||||
'license_id' => filter_var($post['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null,
|
||||
'access_type_id' => filter_var($post['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null,
|
||||
@@ -232,10 +235,9 @@ class ThesisEditController
|
||||
|
||||
// ── 2. Authors (alphabetically sorted) ─────────────────────────────
|
||||
$authorsRaw = trim($post['auteurice'] ?? '');
|
||||
$showContact = !empty($post['contact_public']);
|
||||
// contact_interne (backoffice) takes precedence over mail (tfe-info fieldset)
|
||||
// contact_interne = private email of the first author (backoffice field)
|
||||
$contactInterne = trim($post['contact_interne'] ?? '');
|
||||
$firstAuthorEmail = $contactInterne !== '' ? $contactInterne : ($post['mail'] ?? null);
|
||||
$firstAuthorEmail = $contactInterne !== '' ? $contactInterne : null;
|
||||
$authorNames = [];
|
||||
if ($authorsRaw !== '') {
|
||||
$authorNames = array_values(array_filter(array_map('trim', explode(',', $authorsRaw)), fn ($n) => $n !== ''));
|
||||
@@ -246,7 +248,7 @@ class ThesisEditController
|
||||
$authorEntries[] = [
|
||||
'name' => $name,
|
||||
'email' => $i === 0 ? $firstAuthorEmail : null,
|
||||
'show_contact' => $i === 0 ? $showContact : false,
|
||||
'show_contact' => $i === 0,
|
||||
];
|
||||
}
|
||||
$this->db->setThesisAuthors($thesisId, $authorEntries);
|
||||
|
||||
@@ -1037,8 +1037,8 @@ class Database
|
||||
$cleanEmail = null; // don't steal another author's email
|
||||
}
|
||||
}
|
||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET email = ?, show_contact = ? WHERE id = ?');
|
||||
$updateStmt->execute([$cleanEmail, $showContact ? 1 : 0, $author['id']]);
|
||||
$updateStmt = $this->pdo->prepare('UPDATE authors SET name = ?, email = ?, show_contact = ? WHERE id = ?');
|
||||
$updateStmt->execute([$name, $cleanEmail, $showContact ? 1 : 0, $author['id']]);
|
||||
return $author['id'];
|
||||
}
|
||||
|
||||
@@ -2007,7 +2007,7 @@ class Database
|
||||
public function getThesisRawFields(int $thesisId): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT license_id, license_custom, access_type_id, context_note, remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc2r FROM theses WHERE id = ? LIMIT 1'
|
||||
'SELECT license_id, license_custom, access_type_id, context_note, contact_visible, remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc2r, is_published FROM theses WHERE id = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$thesisId]);
|
||||
$row = $stmt->fetch();
|
||||
@@ -2140,6 +2140,7 @@ class Database
|
||||
finality_id = ?,
|
||||
synopsis = ?,
|
||||
context_note = ?,
|
||||
contact_visible = ?,
|
||||
baiu_link = ?,
|
||||
license_id = ?,
|
||||
license_custom = ?,
|
||||
@@ -2170,6 +2171,7 @@ class Database
|
||||
$finality,
|
||||
$data['synopsis'],
|
||||
!empty($data['context_note']) ? $data['context_note'] : null,
|
||||
!empty($data['contact_visible']) ? $data['contact_visible'] : null,
|
||||
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
|
||||
$license,
|
||||
!empty($data['license_custom']) ? $data['license_custom'] : null,
|
||||
@@ -2215,7 +2217,7 @@ class Database
|
||||
INSERT INTO theses (
|
||||
identifier, title, subtitle, year,
|
||||
orientation_id, ap_program_id, finality_id,
|
||||
synopsis, context_note,
|
||||
synopsis, context_note, contact_visible,
|
||||
baiu_link, license_id, license_custom,
|
||||
access_type_id,
|
||||
objet,
|
||||
@@ -2224,7 +2226,7 @@ class Database
|
||||
exemplaire_baiu, exemplaire_erg,
|
||||
cc2r,
|
||||
submitted_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
');
|
||||
|
||||
$validObjet = ['tfe', 'thèse', 'frart'];
|
||||
@@ -2246,6 +2248,7 @@ class Database
|
||||
$finality ? (int)$finality : null,
|
||||
$data['synopsis'],
|
||||
!empty($data['context_note']) ? $data['context_note'] : null,
|
||||
!empty($data['contact_visible']) ? $data['contact_visible'] : null,
|
||||
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
|
||||
$license ? (int)$license : null,
|
||||
!empty($data['license_custom']) ? $data['license_custom'] : null,
|
||||
|
||||
@@ -28,7 +28,7 @@ class StudentEmail
|
||||
'Atelier pluridisciplinaire' => $thesis['ap_program'] ?? '',
|
||||
'Finalité' => $thesis['finality_type'] ?? '',
|
||||
'Synopsis' => $thesis['synopsis'] ?? '',
|
||||
'Note contextuelle' => $thesis['context_note'] ?? '',
|
||||
'Note contextuelle relative à soutenance' => $thesis['context_note'] ?? '',
|
||||
'Langue(s)' => $thesis['languages'] ?? '',
|
||||
'Format(s)' => $thesis['formats'] ?? '',
|
||||
'Mots-clés' => $thesis['keywords'] ?? '',
|
||||
|
||||
2
app/storage/logs/audit-2026-06-09.log
Normal file
2
app/storage/logs/audit-2026-06-09.log
Normal file
@@ -0,0 +1,2 @@
|
||||
{"timestamp":"2026-06-09T10:12:58+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","actor":"127.0.0.1","action":"UPDATE","table":"theses","record_id":143,"old_data":{"id":143,"identifier":"2025-072","title":"Pourquoi les artistes sont-ils encore sur Instagram alors que j’ai vu une story disant qu’il fallait quitter META","subtitle":null,"year":2025,"is_doctoral":0,"objet":"tfe","orientation_id":6,"ap_program_id":2,"finality_id":3,"synopsis":"Depuis une quinzaine d’années, Instagram s’est imposé comme un acteur central du monde de l’art visuel. Ce qui était un réseau social destiné au partage d’images personnelles est devenu, pour toute une génération d’artistes, un lieu incontournable de visibilité, de circulation symbolique, de reconnaissance professionnelle. En soi, un dispositif de légitimation culturelle. Il ne s’agit plus simplement d’un outil de diffusion parmi d’autres, mais d’un environnement structurant, un écosystème dans lequel les artistes évoluent, négocient leurs existences publiques, et « construisent » leurs carrières. À la différence des lieux d’exposition traditionnels (galeries, musées), Instagram est à la fois global, permanent, et massivement fréquenté. Il est devenu, pour reprendre les termes de la théorie critique, un milieu total, un espace dans lequel les frontières entre création, communication, autopromotion, performance de soi et marché sont brouillées, confondues, voire rendues indissociables.","context_note":null,"remarks":null,"access_type_id":2,"license_id":null,"jury_points":17.5,"jury_note_added":0,"submitted_at":"2026-06-08 08:33:14","defense_date":null,"published_at":null,"is_published":1,"baiu_link":null,"created_at":"2026-06-08 08:33:14","updated_at":"2026-06-08 08:33:36","exemplaire_baiu":0,"exemplaire_erg":0,"cc2r":0,"license_custom":null,"deleted_at":null,"contact_visible":null}}
|
||||
{"timestamp":"2026-06-09T10:13:23+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","actor":"127.0.0.1","action":"UPDATE","table":"theses","record_id":143,"old_data":{"id":143,"identifier":"2025-072","title":"Pourquoi les artistes sont-ils encore sur Instagram alors que j’ai vu une story disant qu’il fallait quitter META","subtitle":null,"year":2025,"is_doctoral":0,"objet":"tfe","orientation_id":6,"ap_program_id":2,"finality_id":3,"synopsis":"Depuis une quinzaine d’années, Instagram s’est imposé comme un acteur central du monde de l’art visuel. Ce qui était un réseau social destiné au partage d’images personnelles est devenu, pour toute une génération d’artistes, un lieu incontournable de visibilité, de circulation symbolique, de reconnaissance professionnelle. En soi, un dispositif de légitimation culturelle. Il ne s’agit plus simplement d’un outil de diffusion parmi d’autres, mais d’un environnement structurant, un écosystème dans lequel les artistes évoluent, négocient leurs existences publiques, et « construisent » leurs carrières. À la différence des lieux d’exposition traditionnels (galeries, musées), Instagram est à la fois global, permanent, et massivement fréquenté. Il est devenu, pour reprendre les termes de la théorie critique, un milieu total, un espace dans lequel les frontières entre création, communication, autopromotion, performance de soi et marché sont brouillées, confondues, voire rendues indissociables.","context_note":null,"remarks":null,"access_type_id":2,"license_id":null,"jury_points":17.5,"jury_note_added":0,"submitted_at":"2026-06-08 08:33:14","defense_date":null,"published_at":null,"is_published":1,"baiu_link":null,"created_at":"2026-06-08 08:33:14","updated_at":"2026-06-08 08:33:36","exemplaire_baiu":0,"exemplaire_erg":0,"cc2r":0,"license_custom":null,"deleted_at":null,"contact_visible":null}}
|
||||
20
app/storage/logs/error-2026-06-09.log
Normal file
20
app/storage/logs/error-2026-06-09.log
Normal file
@@ -0,0 +1,20 @@
|
||||
{"timestamp":"2026-06-09T10:12:58+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","context":"thesis_edit_tx","exception":"PDOException","message":"SQLSTATE[HY000]: General error: 25 column index out of range","trace":"#0 /home/padlock/repos/xamxam/app/src/Database.php(2186): PDOStatement->execute()
|
||||
#1 /home/padlock/repos/xamxam/app/src/Controllers/ThesisEditController.php(233): Database->updateThesis()
|
||||
#2 /home/padlock/repos/xamxam/app/public/admin/actions/edit.php(36): ThesisEditController->save()
|
||||
#3 /home/padlock/repos/xamxam/app/router.php(46): include('...')
|
||||
#4 {main}","extra":{"thesis_id":143}}
|
||||
{"timestamp":"2026-06-09T10:12:58+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","context":"thesis_edit","exception":"PDOException","message":"SQLSTATE[HY000]: General error: 25 column index out of range","trace":"#0 /home/padlock/repos/xamxam/app/src/Database.php(2186): PDOStatement->execute()
|
||||
#1 /home/padlock/repos/xamxam/app/src/Controllers/ThesisEditController.php(233): Database->updateThesis()
|
||||
#2 /home/padlock/repos/xamxam/app/public/admin/actions/edit.php(36): ThesisEditController->save()
|
||||
#3 /home/padlock/repos/xamxam/app/router.php(46): include('...')
|
||||
#4 {main}","extra":{"thesis_id":143}}
|
||||
{"timestamp":"2026-06-09T10:13:23+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","context":"thesis_edit_tx","exception":"PDOException","message":"SQLSTATE[HY000]: General error: 25 column index out of range","trace":"#0 /home/padlock/repos/xamxam/app/src/Database.php(2186): PDOStatement->execute()
|
||||
#1 /home/padlock/repos/xamxam/app/src/Controllers/ThesisEditController.php(233): Database->updateThesis()
|
||||
#2 /home/padlock/repos/xamxam/app/public/admin/actions/edit.php(36): ThesisEditController->save()
|
||||
#3 /home/padlock/repos/xamxam/app/router.php(46): include('...')
|
||||
#4 {main}","extra":{"thesis_id":143}}
|
||||
{"timestamp":"2026-06-09T10:13:23+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","context":"thesis_edit","exception":"PDOException","message":"SQLSTATE[HY000]: General error: 25 column index out of range","trace":"#0 /home/padlock/repos/xamxam/app/src/Database.php(2186): PDOStatement->execute()
|
||||
#1 /home/padlock/repos/xamxam/app/src/Controllers/ThesisEditController.php(233): Database->updateThesis()
|
||||
#2 /home/padlock/repos/xamxam/app/public/admin/actions/edit.php(36): ThesisEditController->save()
|
||||
#3 /home/padlock/repos/xamxam/app/router.php(46): include('...')
|
||||
#4 {main}","extra":{"thesis_id":143}}
|
||||
@@ -42,6 +42,7 @@
|
||||
$contactInterne = null;
|
||||
$contactPublic = false;
|
||||
$currentContextNote = null;
|
||||
$currentContactVisible = null;
|
||||
|
||||
include APP_ROOT . '/templates/partials/form/form.php';
|
||||
?>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
'subtitle' => $thesis['subtitle'] ?? '',
|
||||
'auteurice' => $thesis['authors'] ?? '',
|
||||
'mail' => $contactInterne ?? '',
|
||||
'contact_visible' => $currentContactVisible ?? '',
|
||||
'synopsis' => $thesis['synopsis'] ?? '',
|
||||
'tag' => $thesis['keywords'] ?? '',
|
||||
'année' => $thesis['year'],
|
||||
|
||||
@@ -45,10 +45,12 @@
|
||||
<dt>Sous-titre</dt><dd><?= htmlspecialchars($thesis['subtitle']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<dt>Auteur·ice(s)</dt><dd><?= htmlspecialchars($thesis['authors']) ?></dd>
|
||||
<?php if (!empty($thesis['contact_interne'])): ?>
|
||||
<dt>Contact (interne)</dt><dd><?= htmlspecialchars($thesis['contact_interne']) ?></dd>
|
||||
<?php if (!empty($thesis['contact_visible'])): ?>
|
||||
<dt>Contact visible</dt><dd><?= htmlspecialchars($thesis['contact_visible']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($thesis['contact_interne'])): ?>
|
||||
<dt>Contact interne (privé)</dt><dd><?= htmlspecialchars($thesis['contact_interne']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<dt>Contact public</dt><dd><?= !empty($thesis['contact_public']) ? 'Oui' : 'Non' ?></dd>
|
||||
<dt>Année</dt><dd><?= htmlspecialchars((string)$thesis['year']) ?></dd>
|
||||
<dt>Objet</dt><dd><?= htmlspecialchars($thesis['objet'] ?? 'tfe') ?></dd>
|
||||
<?php if ($thesis['is_doctoral']): ?>
|
||||
@@ -113,7 +115,7 @@
|
||||
<dd class="recap-synopsis"><?= nl2br(htmlspecialchars($thesis['synopsis'] ?? '–')) ?></dd>
|
||||
|
||||
<?php if ($thesis['context_note']): ?>
|
||||
<dt>Note contextuelle</dt>
|
||||
<dt>Note contextuelle relative à soutenance</dt>
|
||||
<dd class="recap-long-text"><?= nl2br(htmlspecialchars($thesis['context_note'])) ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -58,12 +58,14 @@ $adminMode = $adminMode ?? false;
|
||||
$hint = 'Séparez les auteur·ices par des virgules.';
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
<?php if (!$adminMode): ?>
|
||||
<?php
|
||||
$name = 'mail'; $label = 'Contact visible (optionnel) [mail/site/insta/etc.] :'; $value = $oldFn('mail');
|
||||
$attrs = ['autocomplete' => 'email'];
|
||||
$hint = 'Un seul contact. Indiquez l\'URL complète pour un site (https://…), l\'adresse mail, le nom d\'utilisateur avec @ pour Instagram (@pseudo), ou l\'adresse complète pour Mastodon (@pseudo@instance). Ce contact sera visible publiquement sur la fiche du TFE.';
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div>
|
||||
<label for="synopsis">Synopsis :<?= $adminMode ? '' : ' <span class="asterisk">*</span>' ?></label>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
* bool $showContact — Contact checkbox fieldset
|
||||
* bool $showCoverPreview — cover image preview + remove checkbox
|
||||
* bool $showExistingFiles — existing thesis files list (deletable)
|
||||
* bool $showBackoffice — Backoffice fieldset (context_note, jury_points, remarks, baiu_link, exemplaires, contact_interne, is_published)
|
||||
* bool $showBackoffice — Backoffice fieldset (context_note, jury_points, remarks, baiu_link, exemplaires, contact_visible, contact_interne, is_published)
|
||||
* bool $showEmailConfirmation — E-mail de confirmation fieldset
|
||||
|
||||
* string $helpFn — fn(string $key): string (for help blocks)
|
||||
@@ -408,9 +408,9 @@ if ($filesMode === 'add'): ?>
|
||||
<fieldset>
|
||||
<legend>Backoffice</legend>
|
||||
|
||||
<!-- 1. Note contextuelle -->
|
||||
<!-- 1. Note contextuelle relative à soutenance -->
|
||||
<div class="admin-form-group">
|
||||
<label for="context_note">Note contextuelle :</label>
|
||||
<label for="context_note">Note contextuelle relative à soutenance :</label>
|
||||
<div>
|
||||
<textarea id="context_note" name="context_note"
|
||||
rows="4" maxlength="1500"><?= htmlspecialchars(
|
||||
@@ -479,16 +479,25 @@ if ($filesMode === 'add'): ?>
|
||||
<small>Case logistique : cocher si un exemplaire physique est disponible à l'ERG.</small>
|
||||
</div>
|
||||
|
||||
<!-- 7. Contact interne -->
|
||||
<!-- 7. Contact visible (public) -->
|
||||
<div class="admin-form-group">
|
||||
<label for="contact_interne">Contact interne :</label>
|
||||
<label for="contact_visible">Contact visible :</label>
|
||||
<input type="text" id="contact_visible" name="contact_visible"
|
||||
value="<?= htmlspecialchars($currentContactVisible ?? $formData['contact_visible'] ?? '') ?>"
|
||||
placeholder="email, URL, @pseudo...">
|
||||
<small>Contact affiché publiquement sur la page du TFE (email, site web, réseau social…). Laisser vide pour ne rien afficher.</small>
|
||||
</div>
|
||||
|
||||
<!-- 8. Contact interne (privé) -->
|
||||
<div class="admin-form-group">
|
||||
<label for="contact_interne">Contact interne (privé) :</label>
|
||||
<input type="email" id="contact_interne" name="contact_interne"
|
||||
value="<?= htmlspecialchars($contactInterne ?? $formData['contact_interne'] ?? '') ?>"
|
||||
placeholder="ton.email@exemple.be">
|
||||
<small>Adresse de contact interne (non visible publiquement). Peut être laissé vide.</small>
|
||||
<small>Email privé de l'étudiant·e, utilisé pour l'envoi de la confirmation du formulaire. Non visible publiquement.</small>
|
||||
</div>
|
||||
|
||||
<!-- 8. Publication -->
|
||||
<!-- 9. Publication -->
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="is_published" value="1"
|
||||
|
||||
@@ -185,16 +185,16 @@
|
||||
|
||||
<?php if (!empty($data["context_note"])): ?>
|
||||
<p class="tfe-meta-item tfe-meta-note">
|
||||
<span class="tfe-meta-label">Note :</span>
|
||||
<span class="tfe-meta-label">Note contextuelle relative à soutenance :</span>
|
||||
<span class="tfe-note-value"><?= nl2br(htmlspecialchars($data["context_note"])) ?></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data["contact_interne"]) && !empty($data["contact_public"])): ?>
|
||||
<?php if (!empty($data["contact_visible"])): ?>
|
||||
<p class="tfe-meta-item">
|
||||
<span class="tfe-meta-label">Contact :</span>
|
||||
<?php
|
||||
$_contact = $data["contact_interne"];
|
||||
$_contact = $data["contact_visible"];
|
||||
$_isUrl = filter_var($_contact, FILTER_VALIDATE_URL) !== false;
|
||||
$_isEmail = !$_isUrl && str_contains($_contact, "@");
|
||||
if ($_isUrl):
|
||||
|
||||
Reference in New Issue
Block a user