gettable.php 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. <?php
  2. /**
  3. * client-assets/table/gettable.php
  4. *
  5. * AJAX DataTable endpoint. Requires authentication and a whitelisted table name.
  6. */
  7. require_once __DIR__ . '/../../config/database.php';
  8. require_once __DIR__ . '/../../lib/auth.php';
  9. if (session_status() === PHP_SESSION_NONE) {
  10. session_start();
  11. }
  12. if (!isLoggedIn()) {
  13. http_response_code(401);
  14. echo json_encode(['error' => 'Unauthorised']);
  15. exit;
  16. }
  17. // Only these tables may be queried through this endpoint
  18. $allowedTables = [
  19. 'soil_records',
  20. 'client_records',
  21. 'plant_records',
  22. 'animal_records',
  23. 'water_records',
  24. 'weather_station',
  25. 'calendar_events',
  26. 'fertiliser_specifications',
  27. 'block_info',
  28. 'crop_info',
  29. ];
  30. $table = $_REQUEST['table'] ?? '';
  31. if (!in_array($table, $allowedTables, true)) {
  32. http_response_code(400);
  33. echo json_encode(['error' => 'Invalid table']);
  34. exit;
  35. }
  36. $num_rows = isset($_REQUEST['num_rows']) ? max(1, min((int) $_REQUEST['num_rows'], 200)) : 30;
  37. $page = isset($_REQUEST['page']) ? max(0, (int) $_REQUEST['page']) : 0;
  38. // Allowed ORDER BY columns must also be whitelisted to prevent injection
  39. $allowedOrders = [
  40. 'id', 'date', 'client_name', 'email', 'created_at', 'date_sampled',
  41. 'lab_no', 'site_id', 'crop_type', 'soil_type',
  42. ];
  43. $allowedDirs = ['ASC', 'DESC'];
  44. $order = isset($_REQUEST['order']) && in_array($_REQUEST['order'], $allowedOrders, true)
  45. ? $_REQUEST['order'] : null;
  46. $dir = isset($_REQUEST['dir']) && in_array(strtoupper($_REQUEST['dir']), $allowedDirs, true)
  47. ? strtoupper($_REQUEST['dir']) : 'ASC';
  48. try {
  49. $pdo = getDBConnection();
  50. // Column names
  51. $stmt = $pdo->query("SHOW COLUMNS FROM `{$table}`");
  52. $fields = $stmt->fetchAll(PDO::FETCH_COLUMN);
  53. // Main query — table name is from whitelist so safe to interpolate
  54. $sql = "SELECT * FROM `{$table}`";
  55. if ($order !== null) {
  56. $sql .= " ORDER BY `{$order}` {$dir}";
  57. }
  58. $sql .= ' LIMIT :limit OFFSET :offset';
  59. $stmt = $pdo->prepare($sql);
  60. $stmt->bindValue(':limit', $num_rows, PDO::PARAM_INT);
  61. $stmt->bindValue(':offset', $num_rows * $page, PDO::PARAM_INT);
  62. $stmt->execute();
  63. $rows = $stmt->fetchAll(PDO::FETCH_NUM);
  64. // Count
  65. $countStmt = $pdo->query("SELECT COUNT(*) FROM `{$table}`");
  66. $total = (int) $countStmt->fetchColumn();
  67. header('Content-Type: application/json');
  68. echo json_encode([
  69. 'rows' => $rows,
  70. 'fields' => $fields,
  71. 'total_entries' => $total,
  72. ]);
  73. } catch (PDOException $e) {
  74. error_log('gettable.php DB error: ' . $e->getMessage());
  75. http_response_code(500);
  76. echo json_encode(['error' => 'Database error']);
  77. }