mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
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:
9
TODO.md
9
TODO.md
@@ -36,6 +36,15 @@
|
|||||||
- [x] `templates/admin/acces.php` — archive button, archived links collapsible section
|
- [x] `templates/admin/acces.php` — archive button, archived links collapsible section
|
||||||
- [x] `scripts/setup-server.sh` — provision `/var/log/xamxam.log` with correct ownership
|
- [x] `scripts/setup-server.sh` — provision `/var/log/xamxam.log` with correct ownership
|
||||||
|
|
||||||
|
## Multi-author support
|
||||||
|
- [x] `ThesisCreateController::validateAndSanitise()` — comma-split `auteurice`, sort alphabetically, build author entries array
|
||||||
|
- [x] `Database::createThesis()` — removed hardcoded `author_id` insert; authors linked via `setThesisAuthors()` instead
|
||||||
|
- [x] `ThesisEditController::save()` — authors sorted alphabetically before `setThesisAuthors()`
|
||||||
|
- [x] `Database::findDuplicateThesis()` — accepts `array` of author names, matches any shared author via `IN` + `DISTINCT`
|
||||||
|
- [x] File folder naming — slug generated from all authors alphabetically sorted (both create and edit)
|
||||||
|
- [x] `v_theses_full` GROUP_CONCAT — `ORDER BY a.name ASC` for deterministic alphabetical display
|
||||||
|
- [x] Migration `012_author_view_order.sql` — rebuilds view with alphabetical author ordering
|
||||||
|
|
||||||
## Fix remote 500s and broken TFE pages (post-deploy)
|
## Fix remote 500s and broken TFE pages (post-deploy)
|
||||||
- [x] `migrations/pending/008_share_links_is_archived.sql` — `ALTER TABLE share_links ADD COLUMN is_archived` (missing on remote; breaks `acces.php`)
|
- [x] `migrations/pending/008_share_links_is_archived.sql` — `ALTER TABLE share_links ADD COLUMN is_archived` (missing on remote; breaks `acces.php`)
|
||||||
- [x] `migrations/pending/009_admin_audit_log.sql` — `CREATE TABLE admin_audit_log` (missing on remote)
|
- [x] `migrations/pending/009_admin_audit_log.sql` — `CREATE TABLE admin_audit_log` (missing on remote)
|
||||||
|
|||||||
59
app/migrations/pending/012_author_view_order.sql
Normal file
59
app/migrations/pending/012_author_view_order.sql
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
-- Rebuild v_theses_full to ORDER BY a.name in the GROUP_CONCAT for authors,
|
||||||
|
-- ensuring the view always returns authors alphabetically.
|
||||||
|
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,
|
||||||
|
t.objet,
|
||||||
|
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.access_type_id,
|
||||||
|
t.jury_points,
|
||||||
|
t.submitted_at,
|
||||||
|
t.defense_date,
|
||||||
|
t.published_at,
|
||||||
|
t.is_published,
|
||||||
|
t.baiu_link,
|
||||||
|
t.banner_path,
|
||||||
|
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' 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;
|
||||||
@@ -146,7 +146,7 @@ class ThesisCreateController
|
|||||||
|
|
||||||
// ── 1b. Duplicate detection ───────────────────────────────────────────
|
// ── 1b. Duplicate detection ───────────────────────────────────────────
|
||||||
require_once APP_ROOT . '/src/DuplicateThesisException.php';
|
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) {
|
if ($duplicate !== null) {
|
||||||
throw new DuplicateThesisException(
|
throw new DuplicateThesisException(
|
||||||
$duplicate['id'],
|
$duplicate['id'],
|
||||||
@@ -157,9 +157,17 @@ class ThesisCreateController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 2. Find / create author ───────────────────────────────────────────
|
// ── 2. Build author entries (alphabetically sorted) ───────────────────
|
||||||
$authorId = $this->db->findOrCreateAuthor($data['auteurName'], $data['mail'] ?: null, $data['showContact']);
|
$authorEntries = [];
|
||||||
error_log("ThesisCreateController: author ID $authorId");
|
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);
|
||||||
|
|
||||||
// ── 3–4. DB writes in a transaction ───────────────────────────────────
|
// ── 3–4. DB writes in a transaction ───────────────────────────────────
|
||||||
$this->db->beginTransaction();
|
$this->db->beginTransaction();
|
||||||
@@ -178,12 +186,12 @@ class ThesisCreateController
|
|||||||
'license_id' => $data['licenseId'],
|
'license_id' => $data['licenseId'],
|
||||||
'access_type_id' => $data['accessTypeId'],
|
'access_type_id' => $data['accessTypeId'],
|
||||||
'objet' => $data['objet'],
|
'objet' => $data['objet'],
|
||||||
'author_id' => $authorId,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$identifier = $this->db->getThesisIdentifier($thesisId);
|
$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->setThesisJury($thesisId, $data['juryMembers']);
|
||||||
$this->db->setThesisLanguages($thesisId, $data['languageIds']);
|
$this->db->setThesisLanguages($thesisId, $data['languageIds']);
|
||||||
$this->db->setThesisFormats($thesisId, $data['formatIds']);
|
$this->db->setThesisFormats($thesisId, $data['formatIds']);
|
||||||
@@ -199,7 +207,7 @@ class ThesisCreateController
|
|||||||
// ── 5. File uploads (outside transaction — filesystem ops) ────────────
|
// ── 5. File uploads (outside transaction — filesystem ops) ────────────
|
||||||
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null);
|
$this->handleCoverUpload($thesisId, $files['couverture'] ?? null);
|
||||||
$this->db->handleBannerUpload($thesisId, $files['banner'] ?? 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;
|
return $thesisId;
|
||||||
}
|
}
|
||||||
@@ -263,10 +271,17 @@ class ThesisCreateController
|
|||||||
*/
|
*/
|
||||||
private function validateAndSanitise(array $post): array
|
private function validateAndSanitise(array $post): array
|
||||||
{
|
{
|
||||||
$auteurName = $this->validateRequired(
|
// Split authors by comma, trim, filter empty, sort alphabetically.
|
||||||
$this->sanitiseString($post['auteurice'] ?? ''),
|
$authorRaw = $this->sanitiseString($post['auteurice'] ?? '');
|
||||||
'Nom/Prénom/Pseudo'
|
$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']) : '';
|
$mail = !empty($post['mail']) ? $this->sanitiseString($post['mail']) : '';
|
||||||
$showContact = !empty($post['contact_public']) ? true : false;
|
$showContact = !empty($post['contact_public']) ? true : false;
|
||||||
@@ -371,7 +386,7 @@ class ThesisCreateController
|
|||||||
}
|
}
|
||||||
|
|
||||||
return compact(
|
return compact(
|
||||||
'auteurName',
|
'authorNames',
|
||||||
'mail',
|
'mail',
|
||||||
'showContact',
|
'showContact',
|
||||||
'confirmationEmail',
|
'confirmationEmail',
|
||||||
@@ -445,16 +460,14 @@ class ThesisCreateController
|
|||||||
* @param int $year Used for the storage sub-directory path.
|
* @param int $year Used for the storage sub-directory path.
|
||||||
* @param string $identifier Thesis identifier slug (e.g. "2024-003").
|
* @param string $identifier Thesis identifier slug (e.g. "2024-003").
|
||||||
* @param array|null $uploads Multi-file $_FILES entry (may be null).
|
* @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)) {
|
if (!$uploads || !is_array($uploads['name'] ?? null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate author slug and unique folder name
|
|
||||||
$authorSlug = $this->generateAuthorSlug($authorName);
|
|
||||||
$folderName = $this->ensureUniqueFolder($year, $authorSlug);
|
$folderName = $this->ensureUniqueFolder($year, $authorSlug);
|
||||||
$uploadDir = STORAGE_ROOT . "/theses/{$year}/{$folderName}/";
|
$uploadDir = STORAGE_ROOT . "/theses/{$year}/{$folderName}/";
|
||||||
|
|
||||||
|
|||||||
@@ -181,21 +181,22 @@ class ThesisEditController
|
|||||||
'is_published' => isset($post['is_published']),
|
'is_published' => isset($post['is_published']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ── 2. Authors ────────────────────────────────────────────────────
|
// ── 2. Authors (alphabetically sorted) ─────────────────────────────
|
||||||
$authorsRaw = trim($post['auteurice'] ?? '');
|
$authorsRaw = trim($post['auteurice'] ?? '');
|
||||||
$showContact = !empty($post['contact_public']);
|
$showContact = !empty($post['contact_public']);
|
||||||
$authorEntries = [];
|
$authorNames = [];
|
||||||
if ($authorsRaw !== '') {
|
if ($authorsRaw !== '') {
|
||||||
foreach (array_map('trim', explode(',', $authorsRaw)) as $i => $name) {
|
$authorNames = array_values(array_filter(array_map('trim', explode(',', $authorsRaw)), fn($n) => $n !== ''));
|
||||||
if ($name !== '') {
|
sort($authorNames, SORT_NATURAL);
|
||||||
|
}
|
||||||
|
$authorEntries = [];
|
||||||
|
foreach ($authorNames as $i => $name) {
|
||||||
$authorEntries[] = [
|
$authorEntries[] = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'email' => $i === 0 ? ($post['mail'] ?? null) : null,
|
'email' => $i === 0 ? ($post['mail'] ?? null) : null,
|
||||||
'show_contact' => $i === 0 ? $showContact : false,
|
'show_contact' => $i === 0 ? $showContact : false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->db->setThesisAuthors($thesisId, $authorEntries);
|
$this->db->setThesisAuthors($thesisId, $authorEntries);
|
||||||
|
|
||||||
// ── 3. Jury ───────────────────────────────────────────────────────
|
// ── 3. Jury ───────────────────────────────────────────────────────
|
||||||
@@ -338,7 +339,11 @@ class ThesisEditController
|
|||||||
|
|
||||||
$year = (int)($post['année'] ?? date('Y'));
|
$year = (int)($post['année'] ?? date('Y'));
|
||||||
$authorName = trim($post['auteurice'] ?? 'unknown');
|
$authorName = trim($post['auteurice'] ?? 'unknown');
|
||||||
$authorSlug = $this->generateAuthorSlug($authorName);
|
|
||||||
|
// Sort the raw comma-separated string alphabetically, then slugify.
|
||||||
|
$names = array_values(array_filter(array_map('trim', explode(',', $authorName)), fn($n) => $n !== ''));
|
||||||
|
sort($names, SORT_NATURAL);
|
||||||
|
$authorSlug = $this->generateAuthorSlug(implode(', ', $names));
|
||||||
|
|
||||||
// Per-file labels and sort orders submitted alongside the upload inputs
|
// Per-file labels and sort orders submitted alongside the upload inputs
|
||||||
$fileLabels = $post['file_labels'] ?? [];
|
$fileLabels = $post['file_labels'] ?? [];
|
||||||
|
|||||||
@@ -838,7 +838,7 @@ class Database
|
|||||||
t.id, t.identifier, t.title, t.subtitle, t.year,
|
t.id, t.identifier, t.title, t.subtitle, t.year,
|
||||||
o.name as orientation,
|
o.name as orientation,
|
||||||
ap.name as ap_program,
|
ap.name as ap_program,
|
||||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
GROUP_CONCAT(DISTINCT a.name ORDER BY a.name ASC) as authors,
|
||||||
t.submitted_at,
|
t.submitted_at,
|
||||||
t.is_published,
|
t.is_published,
|
||||||
at.name as access_type
|
at.name as access_type
|
||||||
@@ -983,18 +983,35 @@ class Database
|
|||||||
* @param int $year Proposed year.
|
* @param int $year Proposed year.
|
||||||
* @return array{id:int,identifier:string,title:string,author:string,year:int}|null
|
* @return array{id:int,identifier:string,title:string,author:string,year:int}|null
|
||||||
*/
|
*/
|
||||||
public function findDuplicateThesis(string $title, string $authorName, int $year): ?array
|
/**
|
||||||
|
* @param string $title Proposed title.
|
||||||
|
* @param string[] $authorNames Proposed author names (already trimmed, non-empty).
|
||||||
|
* @param int $year Proposed year.
|
||||||
|
* @return array{id:int,identifier:string,title:string,author:string,year:int}|null
|
||||||
|
*/
|
||||||
|
public function findDuplicateThesis(string $title, array $authorNames, int $year): ?array
|
||||||
{
|
{
|
||||||
// Fetch all theses for the same year and author (case-insensitive).
|
if (empty($authorNames)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all theses for the same year that share any author with the submission.
|
||||||
|
$numAuthors = count($authorNames);
|
||||||
|
$ph = implode(',', array_fill(0, $numAuthors, 'LOWER(TRIM(?))'));
|
||||||
|
$params = array_merge([$year], $authorNames);
|
||||||
$stmt = $this->pdo->prepare(
|
$stmt = $this->pdo->prepare(
|
||||||
'SELECT t.id, t.identifier, t.title, a.name AS author, t.year
|
"SELECT DISTINCT t.id, t.identifier, t.title, t.year,
|
||||||
|
GROUP_CONCAT(a2.name ORDER BY ta2.author_order ASC) AS authors
|
||||||
FROM theses t
|
FROM theses t
|
||||||
JOIN thesis_authors ta ON ta.thesis_id = t.id AND ta.author_order = 1
|
JOIN thesis_authors ta ON ta.thesis_id = t.id
|
||||||
JOIN authors a ON a.id = ta.author_id
|
JOIN authors a ON a.id = ta.author_id
|
||||||
|
JOIN thesis_authors ta2 ON ta2.thesis_id = t.id
|
||||||
|
JOIN authors a2 ON a2.id = ta2.author_id
|
||||||
WHERE t.year = ?
|
WHERE t.year = ?
|
||||||
AND LOWER(TRIM(a.name)) = LOWER(TRIM(?))'
|
AND LOWER(TRIM(a.name)) IN ({$ph})
|
||||||
|
GROUP BY t.id"
|
||||||
);
|
);
|
||||||
$stmt->execute([$year, $authorName]);
|
$stmt->execute($params);
|
||||||
$candidates = $stmt->fetchAll();
|
$candidates = $stmt->fetchAll();
|
||||||
|
|
||||||
if (empty($candidates)) {
|
if (empty($candidates)) {
|
||||||
@@ -1016,7 +1033,7 @@ class Database
|
|||||||
'id' => (int)$row['id'],
|
'id' => (int)$row['id'],
|
||||||
'identifier' => $row['identifier'],
|
'identifier' => $row['identifier'],
|
||||||
'title' => $row['title'],
|
'title' => $row['title'],
|
||||||
'author' => $row['author'],
|
'author' => $row['authors'],
|
||||||
'year' => (int)$row['year'],
|
'year' => (int)$row['year'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1050,7 @@ class Database
|
|||||||
'id' => (int)$row['id'],
|
'id' => (int)$row['id'],
|
||||||
'identifier' => $row['identifier'],
|
'identifier' => $row['identifier'],
|
||||||
'title' => $row['title'],
|
'title' => $row['title'],
|
||||||
'author' => $row['author'],
|
'author' => $row['authors'],
|
||||||
'year' => (int)$row['year'],
|
'year' => (int)$row['year'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1050,7 +1067,7 @@ class Database
|
|||||||
'id' => (int)$row['id'],
|
'id' => (int)$row['id'],
|
||||||
'identifier' => $row['identifier'],
|
'identifier' => $row['identifier'],
|
||||||
'title' => $row['title'],
|
'title' => $row['title'],
|
||||||
'author' => $row['author'],
|
'author' => $row['authors'],
|
||||||
'year' => (int)$row['year'],
|
'year' => (int)$row['year'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1849,15 +1866,7 @@ class Database
|
|||||||
$objet,
|
$objet,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$thesisId = (int)$this->pdo->lastInsertId();
|
return (int)$this->pdo->lastInsertId();
|
||||||
|
|
||||||
// Link author — always author_order = 1 for single-author submissions.
|
|
||||||
$stmt = $this->pdo->prepare(
|
|
||||||
'INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)'
|
|
||||||
);
|
|
||||||
$stmt->execute([$thesisId, (int)$data['author_id']]);
|
|
||||||
|
|
||||||
return $thesisId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ SELECT
|
|||||||
t.is_published,
|
t.is_published,
|
||||||
t.baiu_link,
|
t.baiu_link,
|
||||||
t.banner_path,
|
t.banner_path,
|
||||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
GROUP_CONCAT(DISTINCT a.name ORDER BY a.name ASC) as authors,
|
||||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
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 = '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 = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||||
|
|||||||
Reference in New Issue
Block a user