standardise multi-author support across all forms

- ThesisCreateController: comma-split auteurice, sort alphabetically,
  use setThesisAuthors() instead of hardcoded createThesis() author_id
- Database::createThesis(): removed author_id param and hardcoded insert
- Database::findDuplicateThesis(): accepts array of author names, matches
  any shared author via IN + DISTINCT
- ThesisEditController::save(): sort authors alphabetically on save
- File folder naming: slug from all authors alphabetically sorted
- v_theses_full GROUP_CONCAT: ORDER BY a.name ASC for deterministic display
- Migration 012_author_view_order.sql: rebuilds view with alphabetical order
This commit is contained in:
Pontoporeia
2026-05-05 10:31:06 +02:00
parent 125c501f40
commit 95066de7b4
6 changed files with 143 additions and 48 deletions

View File

@@ -146,7 +146,7 @@ class ThesisCreateController
// ── 1b. Duplicate detection ───────────────────────────────────────────
require_once APP_ROOT . '/src/DuplicateThesisException.php';
$duplicate = $this->db->findDuplicateThesis($data['titre'], $data['auteurName'], $data['annee']);
$duplicate = $this->db->findDuplicateThesis($data['titre'], $data['authorNames'], $data['annee']);
if ($duplicate !== null) {
throw new DuplicateThesisException(
$duplicate['id'],
@@ -157,9 +157,17 @@ class ThesisCreateController
);
}
// ── 2. Find / create author ───────────────────────────────────────────
$authorId = $this->db->findOrCreateAuthor($data['auteurName'], $data['mail'] ?: null, $data['showContact']);
error_log("ThesisCreateController: author ID $authorId");
// ── 2. Build author entries (alphabetically sorted) ───────────────────
$authorEntries = [];
foreach ($data['authorNames'] as $i => $name) {
$authorEntries[] = [
'name' => $name,
'email' => $i === 0 ? ($data['mail'] ?: null) : null,
'show_contact' => $i === 0 ? $data['showContact'] : false,
];
}
$allAuthorsStr = implode(', ', $data['authorNames']);
$authorSlug = $this->generateAuthorSlug($allAuthorsStr);
// ── 34. DB writes in a transaction ───────────────────────────────────
$this->db->beginTransaction();
@@ -178,12 +186,12 @@ class ThesisCreateController
'license_id' => $data['licenseId'],
'access_type_id' => $data['accessTypeId'],
'objet' => $data['objet'],
'author_id' => $authorId,
]);
$identifier = $this->db->getThesisIdentifier($thesisId);
error_log("ThesisCreateController: created thesis #$thesisId ($identifier)");
error_log("ThesisCreateController: created thesis #$thesisId ($identifier) with " . count($authorEntries) . " author(s)");
$this->db->setThesisAuthors($thesisId, $authorEntries);
$this->db->setThesisJury($thesisId, $data['juryMembers']);
$this->db->setThesisLanguages($thesisId, $data['languageIds']);
$this->db->setThesisFormats($thesisId, $data['formatIds']);
@@ -199,7 +207,7 @@ 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, $data['auteurName'], $post);
$this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post);
return $thesisId;
}
@@ -263,10 +271,17 @@ class ThesisCreateController
*/
private function validateAndSanitise(array $post): array
{
$auteurName = $this->validateRequired(
$this->sanitiseString($post['auteurice'] ?? ''),
'Nom/Prénom/Pseudo'
);
// Split authors by comma, trim, filter empty, sort alphabetically.
$authorRaw = $this->sanitiseString($post['auteurice'] ?? '');
$authorNames = [];
if ($authorRaw !== '') {
$authorNames = array_filter(array_map('trim', explode(',', $authorRaw)), fn($n) => $n !== '');
$authorNames = array_values($authorNames);
sort($authorNames, SORT_NATURAL);
}
if (empty($authorNames)) {
throw new Exception("Le champ 'Nom/Prénom/Pseudo' est requis.");
}
$mail = !empty($post['mail']) ? $this->sanitiseString($post['mail']) : '';
$showContact = !empty($post['contact_public']) ? true : false;
@@ -371,7 +386,7 @@ class ThesisCreateController
}
return compact(
'auteurName',
'authorNames',
'mail',
'showContact',
'confirmationEmail',
@@ -445,16 +460,14 @@ class ThesisCreateController
* @param int $year Used for the storage sub-directory path.
* @param string $identifier Thesis identifier slug (e.g. "2024-003").
* @param array|null $uploads Multi-file $_FILES entry (may be null).
* @param string $authorName Author name for folder and file naming.
* @param string $authorSlug Pre-computed author slug for folder and file naming.
*/
private function handleThesisFiles(int $thesisId, int $year, string $identifier, ?array $uploads, string $authorName, array $post = []): void
private function handleThesisFiles(int $thesisId, int $year, string $identifier, ?array $uploads, string $authorSlug, array $post = []): void
{
if (!$uploads || !is_array($uploads['name'] ?? null)) {
return;
}
// Generate author slug and unique folder name
$authorSlug = $this->generateAuthorSlug($authorName);
$folderName = $this->ensureUniqueFolder($year, $authorSlug);
$uploadDir = STORAGE_ROOT . "/theses/{$year}/{$folderName}/";