mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
refactor: encapsulate thesis creation SQL in Database::createThesis()
Move the raw identifier-generation query and the INSERT INTO theses /
INSERT INTO thesis_authors statements out of formulaire.php into two new
Database methods:
generateThesisIdentifier(int $year): string
– counts existing theses for the year inside the open transaction so
concurrent workers cannot produce duplicate YYYY-NNN identifiers.
createThesis(array $data): int
– generates the identifier, INSERTs the thesis row, links the author
via thesis_authors (author_order=1), returns the new thesis ID.
getThesisIdentifier(int $id): string
– fetches the stored identifier for a thesis ID; used by formulaire.php
to reconstruct the upload path (storage/theses/YYYY/YYYY-NNN/).
formulaire.php now calls $db->createThesis([…]) + $db->getThesisIdentifier()
and no longer holds any raw PDO queries for the core thesis insert.
The $pdo local variable (previously $db->getPDO()) is removed entirely.
All four test suites (Unit, RateLimit, Integration, Security) pass.
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -399,7 +399,7 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis
|
|||||||
- [x] `edit.php` (line 155): unparameterised `"… WHERE id = $thesisId"` SQL injection → fixed; raw `SELECT banner_path` → `getThesisBannerPath(int $id): ?string`
|
- [x] `edit.php` (line 155): unparameterised `"… WHERE id = $thesisId"` SQL injection → fixed; raw `SELECT banner_path` → `getThesisBannerPath(int $id): ?string`
|
||||||
- [x] `edit.php`: raw `SELECT license_id, access_type_id, context_note` → `getThesisRawFields(int $id): ?array`
|
- [x] `edit.php`: raw `SELECT license_id, access_type_id, context_note` → `getThesisRawFields(int $id): ?array`
|
||||||
- [x] `system.php`: raw `SELECT COUNT(*) FROM theses` → `getThesisCount(): int`
|
- [x] `system.php`: raw `SELECT COUNT(*) FROM theses` → `getThesisCount(): int`
|
||||||
- [ ] `formulaire.php`: raw identifier-generation query + all junction-table INSERTs → encapsulate in `Database::createThesis(array $data): int`
|
- [x] `formulaire.php`: raw identifier-generation query + all junction-table INSERTs → encapsulate in `Database::createThesis(array $data): int`
|
||||||
|
|
||||||
- [x] **`sanitize_string()` in `formulaire.php` applies `htmlspecialchars` at write time** —
|
- [x] **`sanitize_string()` in `formulaire.php` applies `htmlspecialchars` at write time** —
|
||||||
HTML-escaping belongs at render time (in the template), not at storage time. Storing
|
HTML-escaping belongs at render time (in the template), not at storage time. Storing
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ function validate_required($value, $fieldName) {
|
|||||||
try {
|
try {
|
||||||
// Initialize database connection
|
// Initialize database connection
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
$pdo = $db->getPDO();
|
|
||||||
|
|
||||||
// Begin transaction - all or nothing
|
// Begin transaction - all or nothing
|
||||||
$db->beginTransaction();
|
$db->beginTransaction();
|
||||||
|
|
||||||
@@ -140,44 +138,22 @@ try {
|
|||||||
$authorId = $db->findOrCreateAuthor($auteurName, $mail);
|
$authorId = $db->findOrCreateAuthor($auteurName, $mail);
|
||||||
error_log("Author ID: $authorId");
|
error_log("Author ID: $authorId");
|
||||||
|
|
||||||
// ===== INSERT THESIS RECORD =====
|
// ===== INSERT THESIS RECORD + LINK AUTHOR =====
|
||||||
|
$thesisId = $db->createThesis([
|
||||||
// Generate unique identifier (YYYY-NNN format)
|
'year' => $annee,
|
||||||
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM theses WHERE year = ?");
|
'orientation_id' => $orientationId,
|
||||||
$stmt->execute([$annee]);
|
'ap_program_id' => $apProgramId,
|
||||||
$count = $stmt->fetch()['count'] + 1;
|
'finality_id' => $finalityId,
|
||||||
$identifier = sprintf("%d-%03d", $annee, $count);
|
'title' => $titre,
|
||||||
|
'subtitle' => $subtitle,
|
||||||
$stmt = $pdo->prepare("
|
'synopsis' => $synopsis,
|
||||||
INSERT INTO theses (
|
'file_size_info' => $durationInfo,
|
||||||
identifier, title, subtitle, year,
|
'baiu_link' => $lien,
|
||||||
orientation_id, ap_program_id, finality_id,
|
'license_id' => $licenseId,
|
||||||
synopsis, file_size_info,
|
'author_id' => $authorId,
|
||||||
baiu_link, license_id,
|
|
||||||
submitted_at
|
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
||||||
");
|
|
||||||
|
|
||||||
$stmt->execute([
|
|
||||||
$identifier,
|
|
||||||
$titre,
|
|
||||||
!empty($subtitle) ? $subtitle : null,
|
|
||||||
$annee,
|
|
||||||
$orientationId,
|
|
||||||
$apProgramId,
|
|
||||||
$finalityId,
|
|
||||||
$synopsis,
|
|
||||||
!empty($durationInfo) ? $durationInfo : null,
|
|
||||||
!empty($lien) ? $lien : null,
|
|
||||||
$licenseId
|
|
||||||
]);
|
]);
|
||||||
|
$identifier = $db->getThesisIdentifier($thesisId);
|
||||||
$thesisId = $pdo->lastInsertId();
|
error_log("Thesis ID: $thesisId (identifier: $identifier)");
|
||||||
error_log("Thesis ID: $thesisId");
|
|
||||||
|
|
||||||
// ===== LINK AUTHOR TO THESIS =====
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)");
|
|
||||||
$stmt->execute([$thesisId, $authorId]);
|
|
||||||
|
|
||||||
// ===== LINK JURY TO THESIS =====
|
// ===== LINK JURY TO THESIS =====
|
||||||
$db->setThesisJury($thesisId, $juryMembers);
|
$db->setThesisJury($thesisId, $juryMembers);
|
||||||
|
|||||||
@@ -1101,6 +1101,84 @@ class Database {
|
|||||||
return (int)$this->pdo->query("SELECT COUNT(*) FROM theses")->fetchColumn();
|
return (int)$this->pdo->query("SELECT COUNT(*) FROM theses")->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique YYYY-NNN identifier for a new thesis in the given year.
|
||||||
|
* Counts existing theses for that year (published or not) to determine the next sequence
|
||||||
|
* number. Must be called inside the same transaction that performs the INSERT so that
|
||||||
|
* concurrent requests cannot produce duplicate identifiers.
|
||||||
|
*/
|
||||||
|
public function generateThesisIdentifier(int $year): string {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM theses WHERE year = ?");
|
||||||
|
$stmt->execute([$year]);
|
||||||
|
$count = (int)$stmt->fetchColumn() + 1;
|
||||||
|
return sprintf("%d-%03d", $year, $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new thesis row, link its author, and return the new thesis ID.
|
||||||
|
*
|
||||||
|
* Expected keys in $data:
|
||||||
|
* year (int), orientation_id (int), ap_program_id (int), finality_id (int),
|
||||||
|
* title (string), subtitle (?string), synopsis (string),
|
||||||
|
* file_size_info (?string), baiu_link (?string), license_id (?int),
|
||||||
|
* author_id (int)
|
||||||
|
*
|
||||||
|
* The identifier is generated automatically from $data['year'].
|
||||||
|
* Must be called inside an open transaction.
|
||||||
|
*
|
||||||
|
* @return int The new thesis ID.
|
||||||
|
*/
|
||||||
|
public function createThesis(array $data): int {
|
||||||
|
$identifier = $this->generateThesisIdentifier((int)$data['year']);
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare("
|
||||||
|
INSERT INTO theses (
|
||||||
|
identifier, title, subtitle, year,
|
||||||
|
orientation_id, ap_program_id, finality_id,
|
||||||
|
synopsis, file_size_info,
|
||||||
|
baiu_link, license_id,
|
||||||
|
submitted_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$identifier,
|
||||||
|
$data['title'],
|
||||||
|
!empty($data['subtitle']) ? $data['subtitle'] : null,
|
||||||
|
(int)$data['year'],
|
||||||
|
(int)$data['orientation_id'],
|
||||||
|
(int)$data['ap_program_id'],
|
||||||
|
(int)$data['finality_id'],
|
||||||
|
$data['synopsis'],
|
||||||
|
!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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$thesisId = (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the stored identifier string (e.g. "2024-003") for an existing thesis.
|
||||||
|
*/
|
||||||
|
public function getThesisIdentifier(int $thesisId): string {
|
||||||
|
$stmt = $this->pdo->prepare("SELECT identifier FROM theses WHERE id = ?");
|
||||||
|
$stmt->execute([$thesisId]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
if (!$row) {
|
||||||
|
throw new \RuntimeException("Thesis #$thesisId not found");
|
||||||
|
}
|
||||||
|
return (string)$row['identifier'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a thesis file record
|
* Insert a thesis file record
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[1774702033]
|
[1774702347]
|
||||||
BIN
storage/test.db
BIN
storage/test.db
Binary file not shown.
Reference in New Issue
Block a user