refactor: extract buildSearchConditions, add getThesesList, remove dead code, fix SearchTest

- Database: extract private buildSearchConditions(array $params): array shared by
  searchTheses() and countSearchResults(), eliminating ~80 lines of duplication;
  add array type hints to both public methods
- Database: add getThesesList(array $filters) and getAllYears() so admin/index.php
  no longer builds raw SQL inline
- admin/index.php: replace inline PDO query block with $db->getThesesList() /
  $db->getAllYears(); drop the now-unused $pdo local
- config/bootstrap.php: remove dead include_template() helper and the
  vendor/autoload.php Composer stub (no vendor/ directory exists)
- apps/: delete entire directory (leftover artefact, no code references it)
- tests/Integration/SearchTest.php: fix three searchTheses() calls from bare
  strings to proper array params to match the method signature (prevented TypeError)
This commit is contained in:
Pontoporeia
2026-02-24 23:21:44 +01:00
parent d30153871f
commit eaad740574
8 changed files with 102 additions and 375 deletions

View File

@@ -257,13 +257,13 @@ class Database {
}
/**
* Search theses with filters (secure implementation)
* Build WHERE conditions and named bindings from validated search params.
* Always includes the `is_published = 1` guard.
*
* @param array $params Already-validated params (output of validateSearchParams)
* @return array{0: string[], 1: array<string,mixed>} [$conditions, $bindings]
*/
public function searchTheses($params = [], $limit = 20, $offset = 0) {
$params = $this->validateSearchParams($params);
$limit = max(1, min(100, intval($limit)));
$offset = max(0, intval($offset));
private function buildSearchConditions(array $params): array {
$conditions = ["is_published = 1"];
$bindings = [];
@@ -319,6 +319,19 @@ class Database {
$bindings[':is_doctoral'] = $params['is_doctoral'] ? 1 : 0;
}
return [$conditions, $bindings];
}
/**
* Search theses with filters (secure implementation)
*/
public function searchTheses(array $params = [], $limit = 20, $offset = 0) {
$params = $this->validateSearchParams($params);
$limit = max(1, min(100, intval($limit)));
$offset = max(0, intval($offset));
[$conditions, $bindings] = $this->buildSearchConditions($params);
$whereClause = implode(' AND ', $conditions);
$sql = "SELECT * FROM v_theses_public WHERE $whereClause ORDER BY year DESC, title ASC LIMIT :limit OFFSET :offset";
@@ -336,62 +349,10 @@ class Database {
/**
* Count search results
*/
public function countSearchResults($params = []) {
public function countSearchResults(array $params = []) {
$params = $this->validateSearchParams($params);
$conditions = ["is_published = 1"];
$bindings = [];
if (!empty($params['query'])) {
$conditions[] = "(
title LIKE :query ESCAPE '\\' OR
subtitle LIKE :query ESCAPE '\\' OR
synopsis LIKE :query ESCAPE '\\' OR
authors LIKE :query ESCAPE '\\' OR
supervisors LIKE :query ESCAPE '\\' OR
keywords LIKE :query ESCAPE '\\'
)";
$bindings[':query'] = '%' . $params['query'] . '%';
}
if (!empty($params['year'])) {
$conditions[] = "year = :year";
$bindings[':year'] = $params['year'];
}
if (!empty($params['orientation'])) {
$conditions[] = "orientation LIKE :orientation ESCAPE '\\'";
$bindings[':orientation'] = '%' . $params['orientation'] . '%';
}
if (!empty($params['ap_program'])) {
$conditions[] = "ap_program LIKE :ap_program ESCAPE '\\'";
$bindings[':ap_program'] = '%' . $params['ap_program'] . '%';
}
if (!empty($params['finality'])) {
$conditions[] = "finality_type LIKE :finality ESCAPE '\\'";
$bindings[':finality'] = '%' . $params['finality'] . '%';
}
if (!empty($params['keyword'])) {
$conditions[] = "keywords LIKE :keyword ESCAPE '\\'";
$bindings[':keyword'] = '%' . $params['keyword'] . '%';
}
if (!empty($params['format'])) {
$conditions[] = "formats LIKE :format ESCAPE '\\'";
$bindings[':format'] = '%' . $params['format'] . '%';
}
if (!empty($params['language'])) {
$conditions[] = "languages LIKE :language ESCAPE '\\'";
$bindings[':language'] = '%' . $params['language'] . '%';
}
if (isset($params['is_doctoral'])) {
$conditions[] = "is_doctoral = :is_doctoral";
$bindings[':is_doctoral'] = $params['is_doctoral'] ? 1 : 0;
}
[$conditions, $bindings] = $this->buildSearchConditions($params);
$whereClause = implode(' AND ', $conditions);
$sql = "SELECT COUNT(*) as count FROM v_theses_public WHERE $whereClause";
@@ -508,6 +469,71 @@ class Database {
return $this->getLanguages();
}
// ========================================================================
// ADMIN LIST METHOD
// ========================================================================
/**
* Return theses for the admin list view, with optional filters.
*
* Filters (all optional):
* 'search' string matches title, subtitle, or author name (LIKE)
* 'year' int exact year match
* 'orientation' int orientation_id exact match
*
* @param array $filters
* @return array
*/
public function getThesesList(array $filters = []): array {
$sql = "SELECT
t.id, t.identifier, t.title, t.subtitle, t.year,
o.name as orientation,
ap.name as ap_program,
GROUP_CONCAT(DISTINCT a.name) as authors,
t.submitted_at,
t.is_published
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 thesis_authors ta ON t.id = ta.thesis_id
LEFT JOIN authors a ON ta.author_id = a.id
WHERE 1=1";
$params = [];
if (!empty($filters['search'])) {
$sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)";
$searchParam = '%' . $filters['search'] . '%';
$params[] = $searchParam;
$params[] = $searchParam;
$params[] = $searchParam;
}
if (!empty($filters['year'])) {
$sql .= " AND t.year = ?";
$params[] = intval($filters['year']);
}
if (!empty($filters['orientation'])) {
$sql .= " AND t.orientation_id = ?";
$params[] = intval($filters['orientation']);
}
$sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* Get distinct years present in the theses table (admin, includes unpublished).
*/
public function getAllYears(): array {
$stmt = $this->pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
// ========================================================================
// CRUD METHODS (from formulaire)
// ========================================================================