jobs.php 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. <?php
  2. require_once __DIR__ . '/db.php';
  3. // Apply a single-field change to a job. If the column is audited
  4. // (status or ack) and the value changed, append a job_history row.
  5. function apply_job_change(array $job, string $col, $new, string $actor): void {
  6. $pdo = db();
  7. $old = $job[$col];
  8. if ((string) $old === (string) $new) return;
  9. // Use BEGIN IMMEDIATE so the write lock is acquired up front (PDO's
  10. // beginTransaction uses BEGIN DEFERRED, which can short-circuit
  11. // SQLite's busy handler). Application-level retry on SQLITE_BUSY
  12. // is a backstop in case the PDO timeout isn't honoured.
  13. $attempts = 0;
  14. while (true) {
  15. $attempts++;
  16. try {
  17. $pdo->exec('BEGIN IMMEDIATE');
  18. break;
  19. } catch (PDOException $e) {
  20. if ($attempts >= 50 || !is_busy_error($e)) throw $e;
  21. usleep(100000); // 100 ms
  22. }
  23. }
  24. try {
  25. $stmt = $pdo->prepare("UPDATE jobs SET $col = ?, updated_at = datetime('now') WHERE id = ?");
  26. $stmt->execute([$new, $job['id']]);
  27. // Every field change is audited — the log page reads this table.
  28. $hist = $pdo->prepare(
  29. 'INSERT INTO job_history(job_id, field, old_value, new_value, actor)
  30. VALUES (?, ?, ?, ?, ?)'
  31. );
  32. $hist->execute([$job['id'], $col, (string) $old, (string) $new, $actor]);
  33. $pdo->exec('COMMIT');
  34. } catch (Throwable $e) {
  35. $pdo->exec('ROLLBACK');
  36. throw $e;
  37. }
  38. }
  39. function is_busy_error(PDOException $e): bool {
  40. return stripos($e->getMessage(), 'database is locked') !== false
  41. || stripos($e->getMessage(), 'database table is locked') !== false;
  42. }
  43. // Parse the same M-D / M-D-Y forms the original PDQUpdates.php accepted.
  44. // Returns ISO 'YYYY-MM-DD' or null (which clears the date).
  45. function parse_due_date(string $raw): ?string {
  46. $raw = trim(str_replace('/', '-', $raw));
  47. if ($raw === '') return null;
  48. $parts = explode('-', $raw);
  49. $today = getdate();
  50. if (count($parts) === 3) {
  51. [$m, $d, $y] = $parts;
  52. } elseif (count($parts) === 2) {
  53. [$m, $d] = $parts;
  54. $y = $today['year'];
  55. } else {
  56. return null;
  57. }
  58. $m = (int) $m; $d = (int) $d; $y = (int) $y;
  59. if ($y < 100) $y += 2000;
  60. if (!checkdate($m, $d, $y)) return null;
  61. return sprintf('%04d-%02d-%02d', $y, $m, $d);
  62. }