feat: harden xml parsing to avoid uncaught errors
This commit is contained in:
@@ -47,4 +47,23 @@ abstract class AbstractParser
|
|||||||
|
|
||||||
return $this->stringToArray($node->text(), $separator);
|
return $this->stringToArray($node->text(), $separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getAttrOrNullValue(Crawler $node, string $attribute): ?string
|
||||||
|
{
|
||||||
|
if (0 === $node->count()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $node->attr($attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRequiredAttrValue(Crawler $node, string $attribute, string $context): string
|
||||||
|
{
|
||||||
|
$value = $this->getAttrOrNullValue($node, $attribute);
|
||||||
|
if (null === $value || '' === trim($value)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Missing required attribute "%s" in %s.', $attribute, $context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,15 @@ class ApiResponseParser extends AbstractParser
|
|||||||
|
|
||||||
$resultNode = $crawler->filterXPath('//ergebnis');
|
$resultNode = $crawler->filterXPath('//ergebnis');
|
||||||
|
|
||||||
|
try {
|
||||||
switch ($responseType) {
|
switch ($responseType) {
|
||||||
case ApiClient::TYPE_NOTIFICATION:
|
case ApiClient::TYPE_NOTIFICATION:
|
||||||
return (new NotificationParser())->parse($crawler);
|
return (new NotificationParser())->parse($crawler);
|
||||||
case ApiClient::TYPE_CUSTOMER_DATA:
|
case ApiClient::TYPE_CUSTOMER_DATA:
|
||||||
$subType = $resultNode->filterXPath('//art')->text();
|
$subType = $this->getStringOrNullValue($resultNode->filterXPath('//art'));
|
||||||
|
if (null === $subType || '' === $subType) {
|
||||||
|
throw new ResponseParserException('Unable to determine customer data subtype from XML response');
|
||||||
|
}
|
||||||
|
|
||||||
switch ($subType) {
|
switch ($subType) {
|
||||||
case 'Adressdaten':
|
case 'Adressdaten':
|
||||||
@@ -68,6 +72,10 @@ class ApiResponseParser extends AbstractParser
|
|||||||
case ApiClient::TYPE_PRODUCT_DATA:
|
case ApiClient::TYPE_PRODUCT_DATA:
|
||||||
$travelNode = $crawler->filterXPath('//reise/termin');
|
$travelNode = $crawler->filterXPath('//reise/termin');
|
||||||
|
|
||||||
|
if (0 === $travelNode->count()) {
|
||||||
|
throw new ResponseParserException('No travel data node found in product data response');
|
||||||
|
}
|
||||||
|
|
||||||
return (new TravelParser())->parse($travelNode->first(), ...$additionalArgs);
|
return (new TravelParser())->parse($travelNode->first(), ...$additionalArgs);
|
||||||
case ApiClient::TYPE_AGENCIES:
|
case ApiClient::TYPE_AGENCIES:
|
||||||
return (new AgencyParser())->parse($resultNode);
|
return (new AgencyParser())->parse($resultNode);
|
||||||
@@ -76,6 +84,9 @@ class ApiResponseParser extends AbstractParser
|
|||||||
case ApiClient::TYPE_PROMO_VOUCHER:
|
case ApiClient::TYPE_PROMO_VOUCHER:
|
||||||
return (new PromoVoucherParser())->parse($resultNode);
|
return (new PromoVoucherParser())->parse($resultNode);
|
||||||
}
|
}
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
throw new ResponseParserException('Invalid XML response structure: '.$e->getMessage(), 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
throw new ResponseParserException('Unable to parse XML response');
|
throw new ResponseParserException('Unable to parse XML response');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ class BookingParser extends AbstractParser
|
|||||||
|
|
||||||
$travelData = $node->filterXPath('//reise');
|
$travelData = $node->filterXPath('//reise');
|
||||||
|
|
||||||
$booking->travelName = $travelData->attr('bezeichnung');
|
$booking->travelName = $this->getAttrOrNullValue($travelData, 'bezeichnung');
|
||||||
$booking->dateId = $this->getIntOrNullValue($node->filterXPath('//idreise'));
|
$booking->dateId = $this->getIntOrNullValue($node->filterXPath('//idreise'));
|
||||||
$booking->travelCode = $travelData->attr('code');
|
$booking->travelCode = $this->getAttrOrNullValue($travelData, 'code');
|
||||||
$booking->travelDate = $this->stringToDate($travelData->attr('termin'));
|
$booking->travelDate = $this->stringToDate($this->getAttrOrNullValue($travelData, 'termin'));
|
||||||
|
|
||||||
$booking->hotelId = $this->getIntOrNullValue($node->filterXPath('//idpartner'));
|
$booking->hotelId = $this->getIntOrNullValue($node->filterXPath('//idpartner'));
|
||||||
$booking->hotelName = $this->getStringOrNullValue($node->filterXPath('//partner'));
|
$booking->hotelName = $this->getStringOrNullValue($node->filterXPath('//partner'));
|
||||||
@@ -56,17 +56,17 @@ class BookingParser extends AbstractParser
|
|||||||
$booking->participants = $this->parseParticipants($node->filterXPath('//teilnehmerliste/teilnehmer'));
|
$booking->participants = $this->parseParticipants($node->filterXPath('//teilnehmerliste/teilnehmer'));
|
||||||
|
|
||||||
$paymentData = $node->filterXPath('//zahlung');
|
$paymentData = $node->filterXPath('//zahlung');
|
||||||
$booking->paymentId = (int) $paymentData->attr('idzahlungsart');
|
$booking->paymentId = $this->getAttrOrNullValue($paymentData, 'idzahlungsart');
|
||||||
$booking->paymentLabel = $paymentData->attr('bezeichnung');
|
$booking->paymentLabel = $this->getAttrOrNullValue($paymentData, 'bezeichnung');
|
||||||
$booking->paymentType = $paymentData->attr('art');
|
$booking->paymentType = $this->getAttrOrNullValue($paymentData, 'art');
|
||||||
|
|
||||||
if (0 < $paymentData->children()->count()) {
|
if (0 < $paymentData->children()->count()) {
|
||||||
$bankDataNode = $paymentData->children()->first();
|
$bankDataNode = $paymentData->children()->first();
|
||||||
$bankAccount = new BankAccount();
|
$bankAccount = new BankAccount();
|
||||||
$bankAccount->iban = $bankDataNode->attr('iban');
|
$bankAccount->iban = $this->getAttrOrNullValue($bankDataNode, 'iban');
|
||||||
$bankAccount->bic = $bankDataNode->attr('bic');
|
$bankAccount->bic = $this->getAttrOrNullValue($bankDataNode, 'bic');
|
||||||
$bankAccount->bankName = $bankDataNode->attr('kreditinstitut');
|
$bankAccount->bankName = $this->getAttrOrNullValue($bankDataNode, 'kreditinstitut');
|
||||||
$bankAccount->holder = $bankDataNode->attr('kontoinhaber');
|
$bankAccount->holder = $this->getAttrOrNullValue($bankDataNode, 'kontoinhaber');
|
||||||
$booking->bankAccount = $bankAccount;
|
$booking->bankAccount = $bankAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,12 @@ class BookingParser extends AbstractParser
|
|||||||
$participants = [];
|
$participants = [];
|
||||||
|
|
||||||
$node->each(function (Crawler $node) use (&$participants) {
|
$node->each(function (Crawler $node) use (&$participants) {
|
||||||
$id = (int) $node->attr('id');
|
$id = (int) ($this->getAttrOrNullValue($node, 'id') ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$participants[$id - 1] = $this->parsePersonalData($node);
|
$participants[$id - 1] = $this->parsePersonalData($node);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,11 @@ class BookingResponseParser extends AbstractParser
|
|||||||
{
|
{
|
||||||
public function parse(Crawler $node): BookingResponse
|
public function parse(Crawler $node): BookingResponse
|
||||||
{
|
{
|
||||||
$status = $node->filterXPath('//buchung')->text();
|
$status = $this->getStringOrNullValue($node->filterXPath('//buchung'));
|
||||||
|
if (null === $status || '' === $status) {
|
||||||
|
throw new \InvalidArgumentException('Missing required booking status in booking response.');
|
||||||
|
}
|
||||||
|
|
||||||
$bookingNumber = $this->getIntOrNullValue($node->filterXPath('//vorgang'));
|
$bookingNumber = $this->getIntOrNullValue($node->filterXPath('//vorgang'));
|
||||||
$totalPrice = $this->getFloatOrNullValue($node->filterXPath('//gesamtpreis'));
|
$totalPrice = $this->getFloatOrNullValue($node->filterXPath('//gesamtpreis'));
|
||||||
$message = $this->getStringOrNullValue($node->filterXPath('//hinweis'));
|
$message = $this->getStringOrNullValue($node->filterXPath('//hinweis'));
|
||||||
@@ -60,18 +64,24 @@ class BookingResponseParser extends AbstractParser
|
|||||||
$priceItems = [];
|
$priceItems = [];
|
||||||
|
|
||||||
$node->filterXPath('//preise/preis')->each(function (Crawler $priceNode) use (&$priceItems): void {
|
$node->filterXPath('//preise/preis')->each(function (Crawler $priceNode) use (&$priceItems): void {
|
||||||
|
$position = (int) $this->getRequiredAttrValue($priceNode, 'position', 'booking response price item');
|
||||||
|
$type = $this->getRequiredAttrValue($priceNode, 'art', 'booking response price item');
|
||||||
|
$label = $this->getRequiredAttrValue($priceNode, 'bezeichnung', 'booking response price item');
|
||||||
|
$unitPrice = $this->stringToFloat($this->getRequiredAttrValue($priceNode, 'preis', 'booking response price item'));
|
||||||
|
$totalPrice = $this->stringToFloat($this->getRequiredAttrValue($priceNode, 'gesamtpreis', 'booking response price item'));
|
||||||
|
|
||||||
$priceItems[] = new PriceItem(
|
$priceItems[] = new PriceItem(
|
||||||
position: (int) $priceNode->attr('position'),
|
position: $position,
|
||||||
type: $priceNode->attr('art'),
|
type: $type,
|
||||||
subType: $priceNode->attr('unterart'),
|
subType: $this->getAttrOrNullValue($priceNode, 'unterart'),
|
||||||
label: $priceNode->attr('bezeichnung'),
|
label: $label,
|
||||||
dateFrom: $priceNode->attr('terminvon'),
|
dateFrom: $this->getAttrOrNullValue($priceNode, 'terminvon'),
|
||||||
dateTo: $priceNode->attr('terminbis'),
|
dateTo: $this->getAttrOrNullValue($priceNode, 'terminbis'),
|
||||||
quantity: (int) $priceNode->attr('anzahl'),
|
quantity: (int) ($this->getAttrOrNullValue($priceNode, 'anzahl') ?? 0),
|
||||||
assignment: $priceNode->attr('zuordnung'),
|
assignment: $this->getAttrOrNullValue($priceNode, 'zuordnung'),
|
||||||
unitPrice: $this->stringToFloat($priceNode->attr('preis')),
|
unitPrice: $unitPrice,
|
||||||
totalPrice: $this->stringToFloat($priceNode->attr('gesamtpreis')),
|
totalPrice: $totalPrice,
|
||||||
id: $priceNode->attr('id')
|
id: $this->getAttrOrNullValue($priceNode, 'id')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,25 +104,26 @@ class BookingResponseParser extends AbstractParser
|
|||||||
$finalPaymentAmount = null;
|
$finalPaymentAmount = null;
|
||||||
$finalPaymentDate = null;
|
$finalPaymentDate = null;
|
||||||
|
|
||||||
$depositNode = $paymentNode->filterXPath('//anzahlung');
|
$depositNode = $paymentNode->filterXPath('.//anzahlung');
|
||||||
if ($depositNode->count() > 0) {
|
if ($depositNode->count() > 0) {
|
||||||
$depositAmount = $this->stringToFloat($depositNode->attr('betrag'));
|
$depositAmount = $this->stringToFloat($this->getAttrOrNullValue($depositNode, 'betrag'));
|
||||||
$depositDate = $depositNode->attr('termin');
|
$depositDate = $this->getAttrOrNullValue($depositNode, 'termin');
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalPaymentNode = $paymentNode->filterXPath('//restzahlung');
|
$finalPaymentNode = $paymentNode->filterXPath('.//restzahlung');
|
||||||
if ($finalPaymentNode->count() > 0) {
|
if ($finalPaymentNode->count() > 0) {
|
||||||
$finalPaymentAmount = $this->stringToFloat($finalPaymentNode->attr('betrag'));
|
$finalPaymentAmount = $this->stringToFloat($this->getAttrOrNullValue($finalPaymentNode, 'betrag'));
|
||||||
$finalPaymentDate = $finalPaymentNode->attr('termin');
|
$finalPaymentDate = $this->getAttrOrNullValue($finalPaymentNode, 'termin');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse purchase vouchers (kaufgutschein elements)
|
// Parse purchase vouchers (kaufgutschein elements)
|
||||||
$appliedPurchaseVouchers = [];
|
$appliedPurchaseVouchers = [];
|
||||||
$voucherNodes = $paymentNode->filterXPath('//kaufgutschein');
|
$voucherNodes = $paymentNode->filterXPath('.//kaufgutschein');
|
||||||
$voucherNodes->each(function (Crawler $voucherNode) use (&$appliedPurchaseVouchers): void {
|
$voucherNodes->each(function (Crawler $voucherNode) use (&$appliedPurchaseVouchers): void {
|
||||||
|
$amount = $this->getAttrOrNullValue($voucherNode, 'betrag');
|
||||||
$appliedPurchaseVouchers[] = [
|
$appliedPurchaseVouchers[] = [
|
||||||
'nummer' => $voucherNode->attr('nummer') ?? '',
|
'nummer' => $this->getAttrOrNullValue($voucherNode, 'nummer') ?? '',
|
||||||
'betrag' => $this->stringToFloat($voucherNode->attr('betrag')),
|
'betrag' => $this->stringToFloat($amount),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ class BookingUpdateParser extends AbstractParser
|
|||||||
{
|
{
|
||||||
public function parse(Crawler $node): BookingUpdate
|
public function parse(Crawler $node): BookingUpdate
|
||||||
{
|
{
|
||||||
|
$changeStatus = $this->getStringOrNullValue($node->filterXPath('//aenderung'));
|
||||||
|
|
||||||
$bookingUpdate = new BookingUpdate();
|
$bookingUpdate = new BookingUpdate();
|
||||||
$bookingUpdate->valid = 'möglich' === $node->filterXPath('//aenderung')->text();
|
$bookingUpdate->valid = 'möglich' === $changeStatus;
|
||||||
$bookingUpdate->success = 'erfolgt' === $node->filterXPath('//aenderung')->text();
|
$bookingUpdate->success = 'erfolgt' === $changeStatus;
|
||||||
$bookingUpdate->status = $node->filterXPath('//status')->text();
|
$bookingUpdate->status = $this->getStringOrNullValue($node->filterXPath('//status'));
|
||||||
|
|
||||||
return $bookingUpdate;
|
return $bookingUpdate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ class ContactFormResponseParser extends AbstractParser
|
|||||||
{
|
{
|
||||||
public function parse(Crawler $node): RegistrationResponse
|
public function parse(Crawler $node): RegistrationResponse
|
||||||
{
|
{
|
||||||
$addressId = (int) $node->filterXPath('//idadresse')->text();
|
$addressId = $this->getIntOrNullValue($node->filterXPath('//idadresse'));
|
||||||
$personId = (int) $node->filterXPath('//idperson')->text();
|
$personId = $this->getIntOrNullValue($node->filterXPath('//idperson'));
|
||||||
|
if (null === $addressId || null === $personId) {
|
||||||
|
throw new \InvalidArgumentException('Missing required idadresse or idperson in contact form response.');
|
||||||
|
}
|
||||||
|
|
||||||
$isNewRecord = $this->getBoolValue($node->filterXPath('//neuanlage'));
|
$isNewRecord = $this->getBoolValue($node->filterXPath('//neuanlage'));
|
||||||
|
|
||||||
return new RegistrationResponse(
|
return new RegistrationResponse(
|
||||||
|
|||||||
@@ -44,15 +44,29 @@ class DocumentsParser
|
|||||||
|
|
||||||
private function decode(Crawler $node): string
|
private function decode(Crawler $node): string
|
||||||
{
|
{
|
||||||
[, $pdfData] = explode(',', $node->text());
|
if (0 === $node->count()) {
|
||||||
|
throw new \InvalidArgumentException('Missing PDF node in document response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode(',', $node->text(), 2);
|
||||||
|
if (2 !== count($parts)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid PDF payload in document response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
[, $pdfData] = $parts;
|
||||||
|
|
||||||
return base64_decode($pdfData);
|
return base64_decode($pdfData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getFileInfo(Crawler $node): array
|
private function getFileInfo(Crawler $node): array
|
||||||
{
|
{
|
||||||
$pdfData = $this->decode($node->filterXPath('//pdf'));
|
$pdfData = $this->decode($node->filterXPath('.//pdf'));
|
||||||
$filename = $node->filterXPath('//datei')->text();
|
$filenameNode = $node->filterXPath('.//datei');
|
||||||
|
if (0 === $filenameNode->count()) {
|
||||||
|
throw new \InvalidArgumentException('Missing filename node in document response.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = $filenameNode->text();
|
||||||
|
|
||||||
return [$filename, $pdfData];
|
return [$filename, $pdfData];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class PersonalDataParser extends AbstractParser
|
|||||||
$remarksNode = $addressDataNode->filterXPath('//bemerkung');
|
$remarksNode = $addressDataNode->filterXPath('//bemerkung');
|
||||||
|
|
||||||
if (0 < $remarksNode->count()) {
|
if (0 < $remarksNode->count()) {
|
||||||
$personalData->remarks = $remarksNode->text();
|
$personalData->remarks = $this->getStringOrNullValue($remarksNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $personalData;
|
return $personalData;
|
||||||
|
|||||||
@@ -49,36 +49,40 @@ class TravelParser extends AbstractParser
|
|||||||
*/
|
*/
|
||||||
public function parse(Crawler $node, ?int $hotelId = null): Travel
|
public function parse(Crawler $node, ?int $hotelId = null): Travel
|
||||||
{
|
{
|
||||||
|
if (0 === $node->count()) {
|
||||||
|
throw new \InvalidArgumentException('Cannot parse travel from empty XML node.');
|
||||||
|
}
|
||||||
|
|
||||||
$hotelNode = $this->getHotelNode($node, $hotelId);
|
$hotelNode = $this->getHotelNode($node, $hotelId);
|
||||||
|
|
||||||
$dateFrom = $this->stringToDate($node->attr('termin'));
|
$dateFrom = $this->stringToDate($this->getRequiredAttrValue($node, 'termin', 'travel termin node'));
|
||||||
$dateTo = $this->stringToDate($node->attr('bis'));
|
$dateTo = $this->stringToDate($this->getRequiredAttrValue($node, 'bis', 'travel termin node'));
|
||||||
|
|
||||||
$travel = new Travel();
|
$travel = new Travel();
|
||||||
$travel->id = (int) $node->attr('idbuspro');
|
$travel->id = (int) $this->getRequiredAttrValue($node, 'idbuspro', 'travel termin node');
|
||||||
$travel->hotelId = (int) $hotelNode->attr('idbuspro');
|
$travel->hotelId = (int) $this->getRequiredAttrValue($hotelNode, 'idbuspro', 'travel hotel node');
|
||||||
$travel->label = $this->getStringOrNullValue($node->filterXPath('//text'));
|
$travel->label = $this->getStringOrNullValue($node->filterXPath('.//text'));
|
||||||
$travel->dateFrom = $dateFrom;
|
$travel->dateFrom = $dateFrom;
|
||||||
$travel->dateTo = $dateTo;
|
$travel->dateTo = $dateTo;
|
||||||
$travel->code = $node->attr('code');
|
$travel->code = $this->getAttrOrNullValue($node, 'code');
|
||||||
|
|
||||||
// Parse product code from parent reise node using DOM
|
// Parse product code from parent reise node using DOM
|
||||||
$domNode = $node->getNode(0);
|
$domNode = $node->getNode(0);
|
||||||
if (null !== $domNode && null !== $domNode->parentNode) {
|
if (null !== $domNode && $domNode->parentNode instanceof \DOMElement) {
|
||||||
$travel->productCode = $domNode->parentNode->getAttribute('code');
|
$travel->productCode = $domNode->parentNode->getAttribute('code');
|
||||||
}
|
}
|
||||||
|
|
||||||
$travel->type = $node->attr('reiseart');
|
$travel->type = $this->getAttrOrNullValue($node, 'reiseart');
|
||||||
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('//abpreis')));
|
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('.//abpreis')));
|
||||||
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('//selektiongruppe'));
|
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('.//selektiongruppe'));
|
||||||
// Parse only termin-level additional services; exclude hotel-nested duplicates that can override flags.
|
// Parse only termin-level additional services; exclude hotel-nested duplicates that can override flags.
|
||||||
$travel->additionalServices = $this
|
$travel->additionalServices = $this
|
||||||
->getAdditionalServices($node->filterXPath('.//lei_sonstiges[not(ancestor::hotel)]/leistung'));
|
->getAdditionalServices($node->filterXPath('.//lei_sonstiges[not(ancestor::hotel)]/leistung'));
|
||||||
$travel->transportationServices = $this
|
$travel->transportationServices = $this
|
||||||
->getTransportationServices($node->filterXPath('//lei_befoerderung/leistung'));
|
->getTransportationServices($node->filterXPath('.//lei_befoerderung/leistung'));
|
||||||
$travel->rooms = $this->getRooms($hotelNode);
|
$travel->rooms = $this->getRooms($hotelNode);
|
||||||
$travel->pickups = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom, true);
|
$travel->pickups = $this->getPickups($node->filterXPath('.//zustiege/zustieg'), $dateFrom, true);
|
||||||
$travel->dropOffs = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck'), $dateTo, false);
|
$travel->dropOffs = $this->getPickups($node->filterXPath('.//zustiege_rueck/zustieg_rueck'), $dateTo, false);
|
||||||
|
|
||||||
// Merge drop-off prices into pickups for split pricing support
|
// Merge drop-off prices into pickups for split pricing support
|
||||||
$this->mergeDropOffPricesIntoPickups($travel->pickups, $travel->dropOffs);
|
$this->mergeDropOffPricesIntoPickups($travel->pickups, $travel->dropOffs);
|
||||||
@@ -103,19 +107,19 @@ class TravelParser extends AbstractParser
|
|||||||
$selectionGroups = [];
|
$selectionGroups = [];
|
||||||
|
|
||||||
$node->each(function (Crawler $groupNode) use (&$selectionGroups) {
|
$node->each(function (Crawler $groupNode) use (&$selectionGroups) {
|
||||||
$groupId = (int) $groupNode->attr('idbuspro');
|
$groupId = (int) $this->getRequiredAttrValue($groupNode, 'idbuspro', 'selection group node');
|
||||||
$selectionGroup = new CrmSelectionGroup();
|
$selectionGroup = new CrmSelectionGroup();
|
||||||
$selectionGroup->id = $groupId;
|
$selectionGroup->id = $groupId;
|
||||||
$selectionGroup->label = $groupNode->attr('bezeichnung');
|
$selectionGroup->label = $this->getAttrOrNullValue($groupNode, 'bezeichnung');
|
||||||
|
|
||||||
$groupNode
|
$groupNode
|
||||||
->filterXPath('//selektion')
|
->filterXPath('.//selektion')
|
||||||
->each(function (Crawler $selectionNode) use (&$selectionGroups, &$selectionGroup, $groupId) {
|
->each(function (Crawler $selectionNode) use (&$selectionGroups, &$selectionGroup, $groupId) {
|
||||||
$selectionId = (int) $selectionNode->attr('idbuspro');
|
$selectionId = (int) $this->getRequiredAttrValue($selectionNode, 'idbuspro', 'selection node');
|
||||||
$selection = new CrmSelection();
|
$selection = new CrmSelection();
|
||||||
$selection->id = $selectionId;
|
$selection->id = $selectionId;
|
||||||
$selection->label = $selectionNode->attr('bezeichnung');
|
$selection->label = $this->getAttrOrNullValue($selectionNode, 'bezeichnung');
|
||||||
$selectionGroups[$groupId]['selections'][$selectionId] = $selectionNode->attr('bezeichnung');
|
$selectionGroups[$groupId]['selections'][$selectionId] = $this->getAttrOrNullValue($selectionNode, 'bezeichnung');
|
||||||
$selectionGroup->selections[] = $selection;
|
$selectionGroup->selections[] = $selection;
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
@@ -141,26 +145,26 @@ class TravelParser extends AbstractParser
|
|||||||
$additionalServices = [];
|
$additionalServices = [];
|
||||||
|
|
||||||
$node->each(function (Crawler $serviceNode) use (&$additionalServices) {
|
$node->each(function (Crawler $serviceNode) use (&$additionalServices) {
|
||||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
$serviceId = (int) $this->getRequiredAttrValue($serviceNode, 'idbuspro', 'additional service node');
|
||||||
|
|
||||||
$service = new Service();
|
$service = new Service();
|
||||||
$service->source = Constants::SOURCE_TRAVEL;
|
$service->source = Constants::SOURCE_TRAVEL;
|
||||||
$service->category = Constants::CATEGORY_ADDITIONAL;
|
$service->category = Constants::CATEGORY_ADDITIONAL;
|
||||||
$service->id = $serviceId;
|
$service->id = $serviceId;
|
||||||
$service->subType = $serviceNode->attr('unterart');
|
$service->subType = $this->getAttrOrNullValue($serviceNode, 'unterart');
|
||||||
$service->mandatory = $this->stringToBool($serviceNode->attr('pflicht'));
|
$service->mandatory = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'pflicht'));
|
||||||
$autoBookValue = $serviceNode->attr('automatisch_buchen');
|
$autoBookValue = $this->getAttrOrNullValue($serviceNode, 'automatisch_buchen');
|
||||||
$service->autoBook = $this->stringToBool($autoBookValue)
|
$service->autoBook = $this->stringToBool($autoBookValue)
|
||||||
|| 'true' === strtolower((string) $autoBookValue);
|
|| 'true' === strtolower((string) $autoBookValue);
|
||||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
$service->dateFrom = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'termin'));
|
||||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
$service->dateTo = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'bis'));
|
||||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('.//text'));
|
||||||
$service->price = $this
|
$service->price = $this
|
||||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('//preis')));
|
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('.//preis')));
|
||||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('.//status'));
|
||||||
|
|
||||||
// Parse optional description from hinweis node
|
// Parse optional description from hinweis node
|
||||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('//hinweis'));
|
$description = $this->getStringOrNullValue($serviceNode->filterXPath('.//hinweis'));
|
||||||
if (null !== $description && '' !== trim($description)) {
|
if (null !== $description && '' !== trim($description)) {
|
||||||
$service->description = $description;
|
$service->description = $description;
|
||||||
}
|
}
|
||||||
@@ -169,7 +173,7 @@ class TravelParser extends AbstractParser
|
|||||||
$this->parseServiceAgeConstraints($serviceNode, $service);
|
$this->parseServiceAgeConstraints($serviceNode, $service);
|
||||||
|
|
||||||
// Parse insurance calculation flag
|
// Parse insurance calculation flag
|
||||||
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
|
$service->includeInInsuranceCalculation = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'versicherungsberechnung'));
|
||||||
|
|
||||||
$additionalServices[$serviceId] = $service;
|
$additionalServices[$serviceId] = $service;
|
||||||
});
|
});
|
||||||
@@ -192,34 +196,34 @@ class TravelParser extends AbstractParser
|
|||||||
$transportationServices = [];
|
$transportationServices = [];
|
||||||
|
|
||||||
$node->each(function (Crawler $serviceNode) use (&$transportationServices) {
|
$node->each(function (Crawler $serviceNode) use (&$transportationServices) {
|
||||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
$serviceId = (int) $this->getRequiredAttrValue($serviceNode, 'idbuspro', 'transportation service node');
|
||||||
|
|
||||||
$service = new Service();
|
$service = new Service();
|
||||||
$service->source = Constants::SOURCE_TRAVEL;
|
$service->source = Constants::SOURCE_TRAVEL;
|
||||||
$service->category = Constants::CATEGORY_TRANSPORTATION;
|
$service->category = Constants::CATEGORY_TRANSPORTATION;
|
||||||
$service->id = $serviceId;
|
$service->id = $serviceId;
|
||||||
$service->subType = $serviceNode->attr('unterart');
|
$service->subType = $this->getAttrOrNullValue($serviceNode, 'unterart');
|
||||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
$service->dateFrom = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'termin'));
|
||||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
$service->dateTo = $this->stringToDate($this->getAttrOrNullValue($serviceNode, 'bis'));
|
||||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('.//text'));
|
||||||
$service->direction = $this->getStringOrNullValue($serviceNode->filterXPath('//richtung'));
|
$service->direction = $this->getStringOrNullValue($serviceNode->filterXPath('.//richtung'));
|
||||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('.//status'));
|
||||||
$service->price = $this
|
$service->price = $this
|
||||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('//preis')));
|
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('.//preis')));
|
||||||
|
|
||||||
// Parse optional description from hinweis node
|
// Parse optional description from hinweis node
|
||||||
$description = $this->getStringOrNullValue($serviceNode->filterXPath('//hinweis'));
|
$description = $this->getStringOrNullValue($serviceNode->filterXPath('.//hinweis'));
|
||||||
if (null !== $description && '' !== trim($description)) {
|
if (null !== $description && '' !== trim($description)) {
|
||||||
$service->description = $description;
|
$service->description = $description;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $timeFrom = $serviceNode->attr('uhrzeit_von')) {
|
if (null !== $timeFrom = $this->getAttrOrNullValue($serviceNode, 'uhrzeit_von')) {
|
||||||
$service->timeFrom = $timeFrom;
|
$service->timeFrom = $timeFrom;
|
||||||
$service->dayTime = (new DayTimeUtility())->mapTime($timeFrom);
|
$service->dayTime = (new DayTimeUtility())->mapTime($timeFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse insurance calculation flag
|
// Parse insurance calculation flag
|
||||||
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
|
$service->includeInInsuranceCalculation = $this->stringToBool($this->getAttrOrNullValue($serviceNode, 'versicherungsberechnung'));
|
||||||
|
|
||||||
$transportationServices[$serviceId] = $service;
|
$transportationServices[$serviceId] = $service;
|
||||||
});
|
});
|
||||||
@@ -246,8 +250,8 @@ class TravelParser extends AbstractParser
|
|||||||
$guideNode = $guideNodes->first();
|
$guideNode = $guideNodes->first();
|
||||||
|
|
||||||
$guide = new Guide();
|
$guide = new Guide();
|
||||||
$guide->name = $this->getStringOrNullValue($guideNode->filterXPath('//name'));
|
$guide->name = $this->getStringOrNullValue($guideNode->filterXPath('.//name'));
|
||||||
$guide->phone = $this->getStringOrNullValue($guideNode->filterXPath('//telefon'));
|
$guide->phone = $this->getStringOrNullValue($guideNode->filterXPath('.//telefon'));
|
||||||
|
|
||||||
return $guide;
|
return $guide;
|
||||||
}
|
}
|
||||||
@@ -321,10 +325,20 @@ class TravelParser extends AbstractParser
|
|||||||
{
|
{
|
||||||
// In case not hotel id is provided, take the first hotel node (which is most probably the only one)
|
// In case not hotel id is provided, take the first hotel node (which is most probably the only one)
|
||||||
if (null === $hotelId) {
|
if (null === $hotelId) {
|
||||||
return $node->filterXPath('//hotel')->first();
|
$hotelNode = $node->filterXPath('.//hotel');
|
||||||
|
|
||||||
|
if (0 === $hotelNode->count()) {
|
||||||
|
throw new \InvalidArgumentException('No hotel node found in travel XML.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$hotelNode = $node->filterXPath(sprintf('//hotel[@idbuspro="%d"]', $hotelId));
|
return $hotelNode->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$hotelNode = $node->filterXPath(sprintf('.//hotel[@idbuspro="%d"]', $hotelId));
|
||||||
|
|
||||||
|
if (0 === $hotelNode->count()) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('No hotel node found for idbuspro %d.', $hotelId));
|
||||||
|
}
|
||||||
|
|
||||||
return $hotelNode->first();
|
return $hotelNode->first();
|
||||||
}
|
}
|
||||||
@@ -341,7 +355,7 @@ class TravelParser extends AbstractParser
|
|||||||
*/
|
*/
|
||||||
public function getRooms(Crawler $node): array
|
public function getRooms(Crawler $node): array
|
||||||
{
|
{
|
||||||
$roomNodes = $node->filterXPath('//zimmer/preis');
|
$roomNodes = $node->filterXPath('.//zimmer/preis');
|
||||||
|
|
||||||
if (0 === $roomNodes->count()) {
|
if (0 === $roomNodes->count()) {
|
||||||
return [];
|
return [];
|
||||||
@@ -350,27 +364,29 @@ class TravelParser extends AbstractParser
|
|||||||
$rooms = [];
|
$rooms = [];
|
||||||
|
|
||||||
$roomNodes->each(function (Crawler $roomNode) use (&$rooms) {
|
$roomNodes->each(function (Crawler $roomNode) use (&$rooms) {
|
||||||
$roomCode = $roomNode->attr('zimmercode');
|
$roomCode = $this->getAttrOrNullValue($roomNode, 'zimmercode');
|
||||||
|
|
||||||
if (true === in_array($roomCode, $this->ignoredRoomCodes, true)) {
|
if (true === in_array($roomCode, $this->ignoredRoomCodes, true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$roomId = (int) $roomNode->attr('idbuspro_zimmer');
|
$roomId = (int) $this->getRequiredAttrValue($roomNode, 'idbuspro_zimmer', 'room price node');
|
||||||
|
|
||||||
$room = new Room();
|
$room = new Room();
|
||||||
$room->id = $roomId;
|
$room->id = $roomId;
|
||||||
$room->code = $roomCode;
|
$room->code = $roomCode;
|
||||||
$room->category = $roomNode->attr('kat');
|
$room->category = $this->getAttrOrNullValue($roomNode, 'kat');
|
||||||
$room->boardId = (int) $roomNode->attr('idbuspro_vp');
|
$room->boardId = (int) $this->getAttrOrNullValue($roomNode, 'idbuspro_vp');
|
||||||
$room->label = $roomNode->attr('zimmertext');
|
$room->label = $this->getAttrOrNullValue($roomNode, 'zimmertext');
|
||||||
$room->minPax = (int) $roomNode->attr('minpax');
|
$room->minPax = (int) $this->getAttrOrNullValue($roomNode, 'minpax');
|
||||||
$room->maxPax = (int) $roomNode->attr('maxpax');
|
$room->maxPax = (int) $this->getAttrOrNullValue($roomNode, 'maxpax');
|
||||||
$room->nights = (int) $roomNode->attr('naechte');
|
$room->nights = (int) $this->getAttrOrNullValue($roomNode, 'naechte');
|
||||||
$room->price = $roomNode->attr('preis') ?
|
$roomPrice = $this->getAttrOrNullValue($roomNode, 'preis');
|
||||||
$this->stringToFloat($roomNode->attr('preis')) : null;
|
$room->price = null !== $roomPrice && '' !== $roomPrice
|
||||||
$room->status = $roomNode->attr('status');
|
? $this->stringToFloat($roomPrice)
|
||||||
$room->available = (int) $roomNode->attr('verfuegbar');
|
: null;
|
||||||
|
$room->status = $this->getAttrOrNullValue($roomNode, 'status');
|
||||||
|
$room->available = (int) $this->getAttrOrNullValue($roomNode, 'verfuegbar');
|
||||||
|
|
||||||
$rooms[$roomId] = $room;
|
$rooms[$roomId] = $room;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user