diff --git a/TODO.md b/TODO.md index 56b9719..4d64166 100644 --- a/TODO.md +++ b/TODO.md @@ -12,4 +12,11 @@ - [x] Migration 038: corriger les identifiers theses qui ne matchent pas leur année - [x] Filtres finalité + format dans la page de recherche (search.php) - [x] Styliser boutons Filtrer/Réinitialiser : plus compacts, Réinitialiser en neutre -- [ ] Commit + jj new +- [x] Commit + jj new + +- [x] Fix identifier-year mismatch: extend save() to regenerate identifier when prefix doesn't match year (not just on year change) +- [x] Fix migration runner run.php to support .php migrations alongside .sql +- [x] Fix runner: treat PHP subprocess idempotent errors (no such column / already exists) as skippable rather than fatal +- [x] Update 016 and 038 PHP migrations to accept $argv[1] DB path +- [x] Fix migration 016 to gracefully handle banner_path column already being dropped +- [x] Commit + jj new diff --git a/app/migrations/applied/016_merge_banners_into_covers.php b/app/migrations/applied/016_merge_banners_into_covers.php index 5f9f9b5..6a47426 100644 --- a/app/migrations/applied/016_merge_banners_into_covers.php +++ b/app/migrations/applied/016_merge_banners_into_covers.php @@ -15,7 +15,8 @@ defined('APP_ROOT') || define('APP_ROOT', dirname(__DIR__, 2)); defined('STORAGE_ROOT') || define('STORAGE_ROOT', APP_ROOT . '/storage'); -$dbPath = APP_ROOT . '/storage/xamxam.db'; +// Accept optional DB path from command line (used by run.php runner) +$dbPath = $argv[1] ?? (APP_ROOT . '/storage/xamxam.db'); if (!file_exists($dbPath)) { echo "ERROR: database not found at $dbPath\n"; exit(1); @@ -34,9 +35,17 @@ if (!is_dir($coverDir)) { echo "Created covers/ directory.\n"; } -// Fetch all theses with a non-null banner_path -$stmt = $pdo->query("SELECT id, banner_path FROM theses WHERE banner_path IS NOT NULL"); -$rows = $stmt->fetchAll(); +// Detect if banner_path column still exists (dropped by migration 028) +try { + $stmt = $pdo->query("SELECT id, banner_path FROM theses WHERE banner_path IS NOT NULL"); + $rows = $stmt->fetchAll(); +} catch (\PDOException $e) { + if (stripos($e->getMessage(), 'no such column') !== false) { + echo "banner_path column already removed — migration 016 already applied, nothing to do.\n"; + exit(0); + } + throw $e; +} if (empty($rows)) { echo "No banners to migrate.\n"; diff --git a/app/migrations/applied/038_fix_mismatched_identifiers.php b/app/migrations/applied/038_fix_mismatched_identifiers.php index 482c31e..3f12cbb 100644 --- a/app/migrations/applied/038_fix_mismatched_identifiers.php +++ b/app/migrations/applied/038_fix_mismatched_identifiers.php @@ -19,7 +19,8 @@ defined('APP_ROOT') || define('APP_ROOT', dirname(__DIR__, 2)); defined('STORAGE_ROOT') || define('STORAGE_ROOT', APP_ROOT . '/storage'); -$dbPath = APP_ROOT . '/storage/xamxam.db'; +// Accept optional DB path from command line (used by run.php runner) +$dbPath = $argv[1] ?? (APP_ROOT . '/storage/xamxam.db'); if (!file_exists($dbPath)) { echo "ERROR: database not found at $dbPath\n"; exit(1); diff --git a/app/migrations/run.php b/app/migrations/run.php index 048eae7..321c4d9 100644 --- a/app/migrations/run.php +++ b/app/migrations/run.php @@ -38,11 +38,11 @@ if (!is_dir($appliedDir)) { mkdir($appliedDir, 0755, true); } -// Collect .sql files from both pending and applied dirs +// Collect .sql and .php files from both pending and applied dirs $files = []; foreach ([$pendingDir, $appliedDir] as $dir) { if (!is_dir($dir)) continue; - foreach (glob($dir . '/*.sql') as $f) { + foreach (glob($dir . '/*.{sql,php}', GLOB_BRACE) as $f) { $files[basename($f)] = $f; } } @@ -61,12 +61,51 @@ foreach ($files as $name => $file) { continue; } + $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); echo "Applying: $name\n"; - $sql = file_get_contents($file); + + $isPhp = $ext === 'php'; try { - $pdo->exec($sql); - } catch (PDOException $e) { + if ($isPhp) { + // PHP migrations: execute in a subprocess for isolation + $cmd = sprintf( + 'php %s %s 2>&1', + escapeshellarg($file), + escapeshellarg($dbPath) + ); + exec($cmd, $output, $exitCode); + $outputStr = implode("\n", $output); + echo $outputStr . "\n"; + if ($exitCode !== 0) { + // Check output for idempotent errors before treating as fatal + $skipPatterns = [ + 'no such column', + 'duplicate column name', + 'already exists', + ]; + $shouldSkip = false; + foreach ($skipPatterns as $pat) { + if (stripos($outputStr, $pat) !== false) { + $shouldSkip = true; + break; + } + } + if ($shouldSkip) { + echo " Skipping (already applied)\n"; + // Mark as applied so we don't re-attempt + $pdo->prepare("INSERT OR REPLACE INTO _migrations (name) VALUES (?)")->execute([$name]); + $count++; + continue; + } + throw new RuntimeException("PHP migration exited with code $exitCode"); + } + } else { + // SQL migrations: execute inline + $sql = file_get_contents($file); + $pdo->exec($sql); + } + } catch (\Throwable $e) { $msg = $e->getMessage(); // Ignore idempotent errors (column/trigger/index already exists or already removed) if (stripos($msg, 'duplicate column name') !== false diff --git a/app/src/Controllers/ThesisEditController.php b/app/src/Controllers/ThesisEditController.php index 53de2fe..13e617e 100644 --- a/app/src/Controllers/ThesisEditController.php +++ b/app/src/Controllers/ThesisEditController.php @@ -220,14 +220,19 @@ class ThesisEditController 'cc2r' => !empty($post['cc2r']), 'license_custom' => trim($post['license_custom'] ?? ''), ]; - // Regenerate identifier if year changed + // Regenerate identifier if year changed or if identifier prefix doesn't match year $oldThesis = $this->db->getThesis($thesisId); $oldYear = (int)($oldThesis['year'] ?? 0); $newYear = $meta['year']; - if ($newYear !== $oldYear && $newYear >= 2000) { + $oldIdentifier = $oldThesis['identifier'] ?? ''; + $oldIdentifierYear = ($oldIdentifier !== '' && preg_match('/^(\d{4})/', $oldIdentifier, $m)) ? (int)$m[1] : 0; + if ($newYear >= 2000 && ($newYear !== $oldYear || $oldIdentifierYear !== $newYear)) { $newIdentifier = $this->db->generateThesisIdentifier($newYear); $meta['identifier'] = $newIdentifier; - error_log('[ThesisEdit] Year changed ' . $oldYear . ' → ' . $newYear . ', new identifier: ' . $newIdentifier); + $reason = $newYear !== $oldYear + ? 'Year changed ' . $oldYear . ' → ' . $newYear + : 'Mismatched identifier ' . $oldIdentifier . ' for year=' . $newYear; + error_log('[ThesisEdit] ' . $reason . ', new identifier: ' . $newIdentifier); } $this->db->updateThesis($thesisId, $meta);