false, 'error' => 'Not authenticated']); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'error' => 'Method not allowed']); exit; } if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) { http_response_code(403); echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']); exit; } $specId = (int)($_POST['spec_id'] ?? 0); $column = (string)($_POST['column'] ?? ''); $value = trim((string)($_POST['value'] ?? '')); if (!$specId || $column === '') { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Missing required fields']); exit; } // Allowlist — only columns present in soil_specifications (excluding system cols) $allowed = [ 'soil_type', 'cec', 'NO3_N', 'NH3_N', 'p_mehlick', 'p_bray2', 'p_morgan', 'p_colwell', 'k_morgan', 'ca_morgan', 'mg_morgan', 'na_morgan', 'ch_h2o', 'ocarbon', 'omatter', 'fe', 'ec', 'ph_cacl2', 'ph_h2o', 's_morgan', 'b_cacl2', 'mn_dtpa', 'zn_dtpa', 'fe_dtpa', 'cu_dtpa', 'al', 'sl_cacl2', 'm_dtpa', 'co_dtpa', 'se', 'ca_mehlick3', 'mg_mehlick3', 'k_mehlick3', 'na_mehlick3', 'al_mehlick3', 'BS_ca2', 'BS_ca_ppm', 'BS_mg2', 'BS_mg_ppm', 'BS_k', 'BS_k_ppm', 'BS_na', 'BS_na_ppm', 'BS_al3', 'BS_ob', 'BS_h', 'ca_mg_ratio', 'c_n_ratio', // spec-range columns 'ca_ppm_min', 'ca_ppm_max', 'mg_ppm_min', 'mg_ppm_max', 'k_ppm_min', 'k_ppm_max', 'na_ppm_min', 'na_ppm_max', 'cabs_min', 'cabs_max', 'mgbs_min', 'mgbs_max', 'kbs_min', 'kbs_max', 'nabs_min', 'nabs_max', 'ob_rec', 'h_rec', 'ca_mg_ratio', 'c_n_ratio', 'ph', ]; if (!in_array($column, $allowed, true)) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Column not editable']); exit; } try { $pdo = getDBConnection(); $userId = getCurrentUserId(); // The backtick-quoted column name is safe because it is validated against the allowlist above. $stmt = $pdo->prepare("UPDATE soil_specifications SET `{$column}` = ? WHERE id = ? AND modx_user_id = ?"); $stmt->execute([$value, $specId, $userId]); if ($stmt->rowCount() === 0) { http_response_code(404); echo json_encode(['success' => false, 'error' => 'Record not found']); exit; } echo json_encode(['success' => true]); } catch (PDOException $e) { error_log('DB error in updateSoilSpecification.php: ' . $e->getMessage()); http_response_code(500); echo json_encode(['success' => false, 'error' => 'Database error']); } exit;