| @@ -197,8 +197,26 @@ | ||
| 197 | 197 | // DEPARTURES ENDPOINTS |
| 198 | 198 | // ========================================================================= |
| 199 | 199 | |
| 200 | 200 | /** |
| 201 | + * Sanitised pagination for the departure list endpoints. | |
| 202 | + * | |
| 203 | + * Returns [page, per_page]. per_page is 0 when the caller did not ask for | |
| 204 | + * pagination, so the list keeps returning every matching row for | |
| 205 | + * consumers that never sent it (the previous behaviour); page is always | |
| 206 | + * >= 1. Only when per_page > 0 is a LIMIT / OFFSET window applied. | |
| 207 | + * | |
| 208 | + * @return array{0: int, 1: int} | |
| 209 | + */ | |
| 210 | + private function paginationParams(WP_REST_Request $request): array | |
| 211 | + { | |
| 212 | + $perPage = max(0, (int) $request->get_param('per_page')); | |
| 213 | + $page = max(1, (int) $request->get_param('page')); | |
| 214 | + | |
| 215 | + return [$page, $perPage]; | |
| 216 | + } | |
| 217 | + | |
| 218 | + /** | |
| 201 | 219 | * GET /trips/{trip_id}/departures |
| 202 | 220 | */ |
| 203 | 221 | public function get_departures(WP_REST_Request $request): WP_REST_Response |
| 204 | 222 | { |
| @@ -203,15 +221,21 @@ | ||
| 203 | 221 | public function get_departures(WP_REST_Request $request): WP_REST_Response |
| 204 | 222 | { |
| 205 | 223 | $tripId = (int) $request->get_param('trip_id'); |
| 206 | 224 | $status = $request->get_param('status'); |
| 225 | + $availability = $request->get_param('availability'); | |
| 226 | + $search = $request->get_param('search'); | |
| 207 | 227 | $source = $request->get_param('source'); |
| 208 | 228 | $dateFrom = $request->get_param('date_from'); |
| 209 | 229 | $dateTo = $request->get_param('date_to'); |
| 210 | 230 | $includePast = $request->get_param('include_past') !== 'false'; |
| 211 | - | |
| 231 | + | |
| 212 | 232 | $filters = []; |
| 213 | 233 | if ($status) $filters['status'] = $status; |
| 234 | + // Capacity is filtered independently of status (see DepartureRepository::applyAvailabilityClause). | |
| 235 | + if ($availability && in_array($availability, ['available', 'partial', 'full'], true)) $filters['availability'] = $availability; | |
| 236 | + // Free-text search on date / notes (see DepartureRepository::applySearchClause). | |
| 237 | + if (is_string($search) && trim($search) !== '') $filters['search'] = trim($search); | |
| 214 | 238 | if ($source) $filters['source'] = $source; |
| 215 | 239 | if ($dateFrom && trim($dateFrom) !== '') $filters['date_from'] = $dateFrom; |
| 216 | 240 | if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo; |
| 217 | 241 | $filters['include_past'] = $includePast; |
| @@ -216,8 +240,17 @@ | ||
| 216 | 240 | if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo; |
| 217 | 241 | $filters['include_past'] = $includePast; |
| 218 | 242 | |
| 219 | 243 | try { |
| 244 | + // Server-side pagination, only when the caller asks for it. | |
| 245 | + [$page, $perPage] = $this->paginationParams($request); | |
| 246 | + if ($perPage > 0) { | |
| 247 | + $filters['per_page'] = $perPage; | |
| 248 | + $filters['page'] = $page; | |
| 249 | + } | |
| 250 | + // True total for the SAME filters, independent of the page window — | |
| 251 | + // count($departures) was the size of the returned page, not the total. | |
| 252 | + $total = $this->departureService->countByTripId($tripId, $filters); | |
| 220 | 253 | $departures = $this->departureService->getByTripId($tripId, $filters); |
| 221 | 254 | |
| 222 | 255 | // Get trip information |
| 223 | 256 | $tripRepository = new \Yatra\Repositories\TripRepository(); |
| @@ -345,9 +378,12 @@ | ||
| 345 | 378 | // Debug: Log time and revenue values |
| 346 | 379 | return $departureArray; |
| 347 | 380 | }, $departures), |
| 348 | 381 | 'meta' => [ |
| 349 | - 'total' => count($departures), | |
| 382 | + 'total' => $total, | |
| 383 | + 'page' => $page, | |
| 384 | + 'per_page' => $perPage > 0 ? $perPage : $total, | |
| 385 | + 'total_pages' => $perPage > 0 ? max(1, (int) ceil($total / $perPage)) : 1, | |
| 350 | 386 | ], |
| 351 | 387 | ]); |
| 352 | 388 | } catch (\Exception $e) { |
| 353 | 389 | return new WP_REST_Response([ |
| @@ -605,15 +641,21 @@ | ||
| 605 | 641 | */ |
| 606 | 642 | public function get_all_departures(WP_REST_Request $request): WP_REST_Response |
| 607 | 643 | { |
| 608 | 644 | $status = $request->get_param('status'); |
| 645 | + $availability = $request->get_param('availability'); | |
| 646 | + $search = $request->get_param('search'); | |
| 609 | 647 | $source = $request->get_param('source'); |
| 610 | 648 | $dateFrom = $request->get_param('date_from'); |
| 611 | 649 | $dateTo = $request->get_param('date_to'); |
| 612 | 650 | $includePast = $request->get_param('include_past') !== 'false'; |
| 613 | - | |
| 651 | + | |
| 614 | 652 | $filters = []; |
| 615 | 653 | if ($status) $filters['status'] = $status; |
| 654 | + // Capacity is filtered independently of status (see DepartureRepository::applyAvailabilityClause). | |
| 655 | + if ($availability && in_array($availability, ['available', 'partial', 'full'], true)) $filters['availability'] = $availability; | |
| 656 | + // Free-text search on date / notes (see DepartureRepository::applySearchClause). | |
| 657 | + if (is_string($search) && trim($search) !== '') $filters['search'] = trim($search); | |
| 616 | 658 | if ($source) $filters['source'] = $source; |
| 617 | 659 | if ($dateFrom && trim($dateFrom) !== '') $filters['date_from'] = $dateFrom; |
| 618 | 660 | if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo; |
| 619 | 661 | $filters['include_past'] = $includePast; |
| @@ -618,8 +660,17 @@ | ||
| 618 | 660 | if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo; |
| 619 | 661 | $filters['include_past'] = $includePast; |
| 620 | 662 | |
| 621 | 663 | try { |
| 664 | + // Server-side pagination, only when the caller asks for it. | |
| 665 | + [$page, $perPage] = $this->paginationParams($request); | |
| 666 | + if ($perPage > 0) { | |
| 667 | + $filters['per_page'] = $perPage; | |
| 668 | + $filters['page'] = $page; | |
| 669 | + } | |
| 670 | + // True total for the SAME filters, independent of the page window — | |
| 671 | + // count($processed) was the size of the returned page, not the total. | |
| 672 | + $total = $this->departureService->countAllDepartures($filters); | |
| 622 | 673 | // Get all departures (no trip filter) |
| 623 | 674 | $departures = $this->departureService->getAllDepartures($filters); |
| 624 | 675 | |
| 625 | 676 | // Get repository for additional data |
| @@ -763,9 +814,12 @@ | ||
| 763 | 814 | return new WP_REST_Response([ |
| 764 | 815 | 'success' => true, |
| 765 | 816 | 'data' => $processed, |
| 766 | 817 | 'meta' => [ |
| 767 | - 'total' => count($processed), | |
| 818 | + 'total' => $total, | |
| 819 | + 'page' => $page, | |
| 820 | + 'per_page' => $perPage > 0 ? $perPage : $total, | |
| 821 | + 'total_pages' => $perPage > 0 ? max(1, (int) ceil($total / $perPage)) : 1, | |
| 768 | 822 | ], |
| 769 | 823 | ]); |
| 770 | 824 | } catch (\Exception $e) { |
| 771 | 825 | return new WP_REST_Response([ |
| @@ -806,9 +860,13 @@ | ||
| 806 | 860 | public function get_available_dates(WP_REST_Request $request): WP_REST_Response |
| 807 | 861 | { |
| 808 | 862 | $tripId = (int) $request->get_param('trip_id'); |
| 809 | 863 | $fromDate = $request->get_param('from_date') ?: date('Y-m-d'); |
| 810 | - $toDate = $request->get_param('to_date') ?: date('Y-m-d', strtotime('+12 months')); | |
| 864 | + // An explicit to_date always wins; only the default follows the | |
| 865 | + // configurable booking horizon (12 months unless changed). The default | |
| 866 | + // is counted from TODAY — not from from_date — exactly as before, so a | |
| 867 | + // client that sends only from_date gets the same window it always did. | |
| 868 | + $toDate = $request->get_param('to_date') ?: yatra_get_availability_horizon_date(); | |
| 811 | 869 | |
| 812 | 870 | try { |
| 813 | 871 | $dates = $this->departureService->getAvailableDates($tripId, $fromDate, $toDate); |
| 814 | 872 | |