diff --git a/api.http b/api.http index b8c74af..83d2709 100644 --- a/api.http +++ b/api.http @@ -49,7 +49,7 @@ Accept: application/json Authorization: Bearer {{$auth.token("oauth2_api")}} ### API travel -GET {{base_url}}/api/travels/DWWMP301125 +GET {{base_url}}/api/travels/DPWBSP030229 Accept: application/json Authorization: Bearer {{$auth.token("oauth2_api")}} diff --git a/src/BusProNet/Traits/TypeConversionTrait.php b/src/BusProNet/Traits/TypeConversionTrait.php index 33dcf6e..9b3a96b 100644 --- a/src/BusProNet/Traits/TypeConversionTrait.php +++ b/src/BusProNet/Traits/TypeConversionTrait.php @@ -66,7 +66,35 @@ trait TypeConversionTrait } try { - $dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16))->startOfDay(); + $dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16)); + + return $dateTime->toDateTimeImmutable(); + } catch (InvalidFormatException $e) { + return null; + } + } + + /** + * This method is used primarily to parse pickup-times since data is + * either provided with date and time or time only. In this case a + * date has to be provided which defaults to a travel's start date. + */ + protected function stringToDateTimeFuzzy(?string $string, \DateTimeImmutable $defaultDate): ?\DateTimeImmutable + { + if (true === empty($string)) { + return null; + } + + try { + if (1 === preg_match('/^\d{2}:\d{2}$/', $string)) { + // time only provided + $dateTime = Carbon::createFromFormat('H:i', $string); + // assign default date + $dateTime->setDateFrom($defaultDate); + } else { + // date and time provided + $dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16)); + } return $dateTime->toDateTimeImmutable(); } catch (InvalidFormatException $e) { diff --git a/src/BusProNet/Utility/TravelCodeUtility.php b/src/BusProNet/Utility/TravelCodeUtility.php new file mode 100644 index 0000000..328a743 --- /dev/null +++ b/src/BusProNet/Utility/TravelCodeUtility.php @@ -0,0 +1,23 @@ +getHotelNode($node, $hotelId); + $dateFrom = $this->stringToDate($node->attr('termin')); + $travel = new Travel(); $travel->id = (int) $node->attr('idbuspro'); $travel->hotelId = (int) $hotelNode->attr('idbuspro'); $travel->label = $this->getStringOrNullValue($node->filterXPath('//text')); - $travel->dateFrom = $this->stringToDate($node->attr('termin')); + $travel->dateFrom = $dateFrom; $travel->dateTo = $this->stringToDate($node->attr('bis')); $travel->code = $node->attr('code'); $travel->type = $node->attr('reiseart'); @@ -145,7 +148,7 @@ class TravelLoader extends AbstractLoader $travel->transportationServices = $this ->getTransportationServices($node->filterXPath('//lei_befoerderung/leistung')); $travel->rooms = $this->getRooms($hotelNode); - $travel->pickupsTo = $this->getPickups($node->filterXPath('//zustiege/zustieg')); + $travel->pickupsTo = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom); $travel->pickupsFro = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck')); $travel->guide = $this->getGuide($node); @@ -248,19 +251,23 @@ class TravelLoader extends AbstractLoader return $transportationServices; } - public function getPickups(Crawler $node): array + public function getPickups(Crawler $node, ?\DateTimeImmutable $defaultDate = null): array { $pickups = []; - $node->each(function (Crawler $pickupNode) use (&$pickups) { + $node->each(function (Crawler $pickupNode) use (&$pickups, $defaultDate) { $pickupId = (int) $pickupNode->attr('idbuspro'); $pickup = new Pickup(); $pickup->id = $pickupId; - $pickup->time = $this->stringToDateTime($pickupNode->attr('zeit')); $pickup->price = $pickupNode->attr('preis') ? $this->stringToFloat($pickupNode->attr('preis')) : null; + // parse date and time only for direction 'to' indicated by provided default date + if (null !== $defaultDate) { + $pickup->time = $this->stringToDateTimeFuzzy($pickupNode->attr('zeit'), $defaultDate); + } + $pickups[$pickupId] = $pickup; }); @@ -352,6 +359,8 @@ class TravelLoader extends AbstractLoader public function patchBookings(BaseData $bookings): void { + $travelCodeUtility = new TravelCodeUtility(); + foreach ($bookings->getItems() as $booking) { if (null === $travelId = $booking->travelId) { continue; @@ -368,11 +377,12 @@ class TravelLoader extends AbstractLoader $travelDate = $travelData->dateFrom; $travelCode = $travelData->code; + // generate url to travelinfo page five days before travel begins if ($travelDate->modify('-5 days') < $now) { $booking->travelInfoUrl = sprintf( '%s/%s', rtrim($this->travelInfoBaseUrl, '/'), - str_replace('/', '-', $travelCode) + $travelCodeUtility->sanitize($travelCode) ); } } diff --git a/src/Controller/Api/TravelController.php b/src/Controller/Api/TravelController.php index 7a3ac31..b6f475b 100644 --- a/src/Controller/Api/TravelController.php +++ b/src/Controller/Api/TravelController.php @@ -3,6 +3,7 @@ namespace App\Controller\Api; use App\BusProNet\Model\Travel; +use App\BusProNet\Utility\TravelCodeUtility; use App\BusProNet\XmlLoader\HotelLoader; use App\BusProNet\XmlLoader\PickupLoader; use App\BusProNet\XmlLoader\TravelLoader; @@ -55,7 +56,8 @@ class TravelController extends AbstractController )] public function singleByCode(string $travelCode, ?string $hotelCode = null): JsonResponse { - $travelCode = str_replace('-', '/', $travelCode); + // sanitize travel code by removing potential dividers + $travelCode = (new TravelCodeUtility())->sanitize($travelCode); $travelId = $this->travelXmlLoader->mapCodeToId($travelCode); $hotelId = $hotelCode ? $this->hotelXmlLoader->mapCodeToId($travelCode) : null; diff --git a/tests/BusProNet/Utility/TravelCodeUtilityTest.php b/tests/BusProNet/Utility/TravelCodeUtilityTest.php new file mode 100644 index 0000000..b1392dc --- /dev/null +++ b/tests/BusProNet/Utility/TravelCodeUtilityTest.php @@ -0,0 +1,48 @@ +sanitize($travelCode); + $this->assertEquals('ABCDEF010125', $sanitized); + + $travelCode = 'ABCDEF-010125'; + $sanitized = $utility->sanitize($travelCode); + $this->assertEquals('ABCDEF010125', $sanitized); + + $travelCode = 'abcdef/010125'; + $sanitized = $utility->sanitize($travelCode); + $this->assertEquals('ABCDEF010125', $sanitized); + } + + public function testGetBaseCode(): void + { + $utility = new TravelCodeUtility(); + + $travelCode = 'ABCDEF010125'; + $baseCode = $utility->getBaseCode($travelCode); + $this->assertEquals('ABCDEF', $baseCode); + + $travelCode = 'abcdef010125'; + $baseCode = $utility->getBaseCode($travelCode); + $this->assertEquals('ABCDEF', $baseCode); + } + + public function testGetDate(): void + { + $utility = new TravelCodeUtility(); + + $travelCode = 'ABCDEF010125'; + $date = $utility->getDate($travelCode); + $this->assertEquals('2025-01-01', $date->format('Y-m-d')); + } +}