{$key} ?? $default; } if (is_array($source)) { return $source[$key] ?? $default; } return $default; }; /** * Normalize a mixed-shape list (highlights, includes, excludes) to a * flat array of trimmed strings. Each input item may be a plain * string, an associative array with a `title`/`name`/`text` key, or a * stdClass with the same properties — the trip builder writes either * shape depending on which UI version saved the record. The previous * loop called `trim($item)` directly and 500'd whenever it hit a * stdClass (see the PHP fatal error in the previous render). Empty * entries are filtered out so we never print bullet points with no * text. */ $yatra_normalize_list = static function ($value): array { if (is_string($value)) { $value = explode("\n", $value); } if (!is_array($value)) { return []; } $out = []; foreach ($value as $item) { if (is_string($item)) { $s = trim($item); } elseif (is_array($item)) { $s = trim((string) ($item['title'] ?? $item['name'] ?? $item['text'] ?? '')); } elseif (is_object($item)) { $s = trim((string) ($item->title ?? $item->name ?? $item->text ?? '')); } else { $s = ''; } if ($s !== '') { $out[] = $s; } } return $out; }; // Optional logo + header colour supplied by the White Label module. // Unbranded sites keep this document's original header colour and show no logo. $pdfBranding = function_exists('yatra_get_pdf_branding') ? yatra_get_pdf_branding('#1e40af') : ['logo_url' => '', 'header_color' => '#1e40af']; $brandLogoUrl = (string) ($pdfBranding['logo_url'] ?? ''); $brandHeaderColor = (string) ($pdfBranding['header_color'] ?? '#1e40af'); ?> <?php echo esc_html__('Travel Itinerary', 'yatra'); ?> - <?php echo htmlspecialchars($bookingRef, ENT_QUOTES, 'UTF-8'); ?> block is positioned at the END // of body (just before ) instead of here at the top — // critical reason: Dompdf's processPageScript() iterates the // pages that exist AT THE MOMENT page_text() / page_script() are // called. Placing the script at the top of body means only // page 1 has been laid out, so the header/footer registers for // page 1 ONLY (and even the "{PAGE_COUNT}" placeholder resolves // to 1 instead of the real total). Moving it to the end means // all pages exist by the time the script runs, so the chrome // applies to every page and the page counter is accurate. $_headerLeft = ($companyName !== '' ? $companyName : __('Travel Company', 'yatra')) . ' · ' . __('Travel Itinerary', 'yatra'); $_headerRight = (string) $bookingRef; $_footerLeftParts = []; if ($companyName !== '') { $_footerLeftParts[] = $companyName; } if ($companyEmail !== '') { $_footerLeftParts[] = $companyEmail; } if ($companyPhone !== '') { $_footerLeftParts[] = $companyPhone; } $_footerLeft = implode(' · ', $_footerLeftParts); $_pageLabel = __('Page', 'yatra') . ' '; ?>

0) { $tripRows[] = [__('Travelers', 'yatra'), (string) $travelerCount, false]; } $travelRows = []; if ($departureLocation !== '') { $travelRows[] = [__('Departure Location', 'yatra'), $departureLocation, false]; } if ($destination !== '') { $travelRows[] = [__('Destination', 'yatra'), $destination, false]; } if ($travelDate !== '') { $travelRows[] = [__('Travel Date', 'yatra'), $travelDate, false]; } if (!empty($returnDate)) { $travelRows[] = [__('Return Date', 'yatra'), $returnDate, false]; } $travelerRows = []; if ($customerName !== '') { $travelerRows[] = [__('Name', 'yatra'), $customerName, false]; } if ($customerEmail !== '') { $travelerRows[] = [__('Email', 'yatra'), $customerEmail, false]; } $bookingRows = []; if ($bookingRef !== '') { $bookingRows[] = [__('Reference', 'yatra'), $bookingRef, false]; } if ($bookingDate !== '') { $bookingRows[] = [__('Issued On', 'yatra'), $bookingDate, false]; } if ($bookingStatus !== '') { $bookingRows[] = [__('Status', 'yatra'), '' . htmlspecialchars($bookingStatus, ENT_QUOTES, 'UTF-8') . '', true]; } $renderCard = static function (string $heading, array $rows): void { if (empty($rows)) return; echo '
'; echo '

' . htmlspecialchars($heading, ENT_QUOTES, 'UTF-8') . '

'; foreach ($rows as [$label, $value, $rawHtml]) { echo '
'; echo '' . htmlspecialchars((string) $label, ENT_QUOTES, 'UTF-8') . ''; echo '' . ($rawHtml ? $value : htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8')) . ''; echo '
'; } echo '
'; }; $renderTwoCardRow = static function (string $leftHeading, array $leftRows, string $rightHeading, array $rightRows) use ($renderCard): void { if (empty($leftRows) && empty($rightRows)) return; echo ''; if (!empty($leftRows)) { $width = !empty($rightRows) ? '49%' : '100%'; echo ''; } if (!empty($rightRows)) { $width = !empty($leftRows) ? '49%' : '100%'; echo ''; } echo '
'; $renderCard($leftHeading, $leftRows); echo ''; $renderCard($rightHeading, $rightRows); echo '
'; }; ?>
title ?? $h->name ?? $h->text ?? '')); $description = trim((string) ($h->description ?? $h->desc ?? '')); } if ($title !== '' || $description !== '') { $highlightsList[] = [$title, $description]; } } } ?>
•
$day): $dayNumber = (int) ($yatra_itinerary_get($day, 'day_number', $dayIndex + 1)); $dayTitle = (string) ($yatra_itinerary_get($day, 'title', $yatra_itinerary_get($day, 'day_title', ''))); $dayDescription = (string) ($yatra_itinerary_get($day, 'description', $yatra_itinerary_get($day, 'day_description', ''))); $dayEntries = $yatra_itinerary_get($day, 'entries', []); if (!is_array($dayEntries)) { $dayEntries = []; } // Resolve the calendar date for this day, if we // know the travel start: Day 1 = travel_date, // Day N = travel_date + (N-1) days. Skipped // silently when no travel_date is available. $dayDateLabel = ''; if (!empty($travel_date)) { $startTs = strtotime((string) ($travel_date_raw ?? '')); } // Use the already-formatted $travelDate for Day 1, // and compute later days from $travelDate's source // when available via the global travel_date var. ?>
[] ·
2): for ($i = 2; $i < $derivedDays; $i++): ?>

•

•