Summary of the technical journey for Italy Rome Tour's Website
Italy Rome Tour is a comprehensive tour booking platform for tourists visiting Rome and Italy.
Project Overview
Italy Rome Tour is a comprehensive tour booking platform for tourists visiting Rome and Italy. The platform allows customers to browse, customize, and book guided tours with features including dynamic pricing, multiple pickup locations, participant management, and professional PDF voucher generation.
The Challenge
Building a complete tour booking system required solving several complex problems:
- Complex Pricing Models: Tours have different prices for adults, children, and infants, with dynamic discounts based on group size
- Multiple Pickup Scenarios: Customers can be picked up from hotels, airports, or cruise ships – each requiring different information
- Ticket Requirements: Some tours require participant names exactly as they appear on IDs for museum entry
- Professional Vouchers: Generating branded PDF booking confirmations with all relevant tour details
- Content Management: Allowing tour operators to easily manage tour specifications, itineraries, and pricing without touching code
Technology Stack
| Category | Technology | Purpose |
|---|---|---|
| CMS | WordPress 6.x | Content management and user administration |
| E-Commerce | WooCommerce | Product catalog, cart, checkout, and order management |
| Booking Engine | YITH WooCommerce Booking Premium | Date/time selection, availability, and booking logic |
| Page Builder | Elementor Pro | Visual page design and template building |
| Custom Fields | Advanced Custom Fields Pro | Tour metadata, specifications, and itinerary management |
| Theme | Hello Elementor + Custom Child | Lightweight base with extensive customizations |
| Payments | Stripe | Secure payment processing |
| Hosting | Cloudways (DigitalOcean) | Managed WordPress hosting with optimization |
| Version Control | GitLab | Code versioning and CI/CD pipeline |
| GDPR Compliance | iubenda | Cookie consent and privacy policy management |
| Caching | W3 Total Cache | Performance optimization |
Architecture Overview
Custom Child Theme Structure
wp-content/themes/hello-theme-irt/
├── functions.php # 539 lines of custom functionality
├── style.css # Theme metadata
├── acf-json/ # ACF field group configurations
│ ├── group_654df0fdcf62e.json # Tour specifications
│ ├── group_654df4ba6b08f.json # Category/tag additional data
│ ├── group_65eaee42de1c3.json # Page extras
│ └── group_664c522c0867c.json # Booking notices
└── yith-woocommerce-booking-templates/
├── booking/
│ ├── pdf/ # Custom PDF templates
│ └── booking-details.php
├── emails/ # Custom email templates
└── single-product/add-to-cart/ # Booking form customizations
Data Flow
- Tour Creation: Admin creates WooCommerce product with ACF fields for specifications
- Frontend Display: Shortcodes render tour specs, pricing from YITH booking data
- Booking Process: Custom form fields collect participant names and pickup details
- Cart Persistence: Data flows through WooCommerce hooks to order meta
- Confirmation: PDF voucher generated with custom branding and notices
Custom Development
1. Tour Specifications Shortcode
Displays a formatted table of tour details with custom icons, pulling data from ACF fields.
add_shortcode( 'irt_tour_specs', 'render_irt_tour_specs' );
function render_irt_tour_specs() {
$spec_fields = [
[ 'Duration', 'irt_tour_duration' ],
[ 'Start', 'irt_starting_time' ],
[ 'Pick up location', 'irt_pick_up_location' ],
[ 'Skip the line', 'irt_skip_the_line' ],
[ 'Official Tour Guide', 'irt_official_tour_guide' ],
[ 'Tickets', 'irt_tickets_included' ],
[ 'Transportation', 'irt_transportation' ],
[ 'Wheelchair users', 'irt_wheelchair_users' ],
[ 'Activity', 'irt_activity_level' ],
];
?>
<table>
<tbody>
<?php
foreach ( $spec_fields as $spec_field ) {
if ( $value = get_field( $spec_field[1] ) ) {
?>
<tr class="<?= $spec_field[1]; ?>">
<th>
<img src="/wp-content/uploads/2023/11/<?= $spec_field[1]; ?>.svg"
alt="<?= $spec_field[0]; ?> icon"
width="18" height="18"/>
<?= $spec_field[0]; ?>
</th>
<td>
<?php
if ( is_array( $value ) ) {
// Handle checkbox fields (e.g., multiple pickup locations)
if ( $spec_field[1] === 'irt_pick_up_location'
&& in_array( 'Other', $value )
) {
echo get_field( 'irt_other_pick_up_location' );
} else {
echo implode( ", ", $value );
}
} else {
if ( $spec_field[1] === 'irt_transportation' && $value === '' ) {
echo _x( 'Not included', 'hello-irt-theme' );
} else {
echo $value;
}
}
?>
</td>
</tr>
<?php
}
}
?>
</tbody>
</table>
<?php
}
Key Features:
- Dynamic field mapping with labels and ACF field names
- SVG icon support for each specification type
- Conditional logic for “Other” pickup location text
- Handles both single values and arrays (checkboxes)
2. Custom Booking Form Fields
Collects additional customer data required for tour operations – participant names for ticketed attractions and pickup location details.
function yith_wc_custom_input_data() {
global $product;
$product_id = $product ? $product->get_id() : 0;
$tickets_included = get_field( 'irt_tickets_included', $product_id );
$pickup_location = get_field( 'irt_pick_up_location', $product_id );
// Participant Names Field - Only shown for tours with tickets
if ( $product_id && $tickets_included ) {
$label = _x( 'Participants', 'hello-irt-theme' );
printf(
'<div class="yith_wc_input_text__wrapper yith-wcbk-form-section participants"
style="display: none;">
<label>%s</label>
<p>Please provide the first and last name of each traveler exactly
as they appear on your official travel documents. This additional
data is collected for ticket booking purposes only.
<strong>Failure to do so will result in denied entry and loss of tour</strong>.</p>
<div class="participants_fields"></div>
<input type="hidden" id="irt_booking_names" name="irt_booking_names" value="" />
</div>',
esc_html( $label )
);
}
// Pickup Point Field - Context-aware instructions
if ( $product_id && ! empty( $pickup_location ) ) {
// Determine pickup type based on product tags
if ( has_term( 1384, 'product_tag', $product_id ) ) {
// From Rome City Center
$pickup_instructions = 'Please enter the pickup location in Rome city center.';
} elseif ( has_term( 1383, 'product_tag', $product_id ) ) {
// From Port/Cruise
$pickup_instructions = _x(
'Please enter the pickup location. In the case of coming from a cruise,
please indicate the name of the ship and the arrival port.',
'hello-irt-theme'
);
} elseif ( has_term( 1382, 'product_tag', $product_id ) ) {
// From Airport
$pickup_instructions = _x(
'Please enter the pickup location. In the case of coming from an airport,
please indicate the code of the flight and the airport and the estimated
arrival time.',
'hello-irt-theme'
);
} else {
// Generic fallback
$pickup_instructions = _x(
'Please enter the pickup location. In the case of coming from a cruise,
please indicate the name of the ship and the arrival port.',
'hello-irt-theme'
);
}
$label = _x( 'Pick up point', 'hello-irt-theme' );
printf(
'<div class="yith_wc_input_text__wrapper yith-wcbk-form-section pick-up-point">
<label>%s</label>
<p>' . $pickup_instructions . '</p>
<textarea id="irt_pickup_point" name="irt_pickup_point"
value="" required></textarea>
</div>',
esc_html( $label )
);
}
}
add_action( 'woocommerce_before_add_to_cart_button', 'yith_wc_custom_input_data' );
Cart Data Persistence:
function yith_wc_add_cart_item_data( $cart_item_data, $product_id, $variation_id, $quantity ) {
if ( ! empty( $_POST['irt_booking_names'] ) ) {
$cart_item_data['irt_booking_names'] = sanitize_text_field(
wp_unslash( $_POST['irt_booking_names'] )
);
}
if ( ! empty( $_POST['irt_pickup_point'] ) ) {
$cart_item_data['irt_pickup_point'] = sanitize_text_field(
wp_unslash( $_POST['irt_pickup_point'] )
);
}
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'yith_wc_add_cart_item_data', 10, 4 );
Display in Cart:
function yith_wc_get_item_data( $cart_data, $cart_item ) {
if ( ! empty( $cart_item['irt_booking_names'] ) ) {
$cart_data[] = [
'name' => esc_html__( 'Participants', 'hello-irt-theme' ),
'display' => $cart_item['irt_booking_names'],
];
}
if ( ! empty( $cart_item['irt_pickup_point'] ) ) {
$cart_data[] = [
'name' => esc_html__( 'Pick up point', 'hello-irt-theme' ),
'display' => $cart_item['irt_pickup_point'],
];
}
return $cart_data;
}
add_filter( 'woocommerce_get_item_data', 'yith_wc_get_item_data', 25, 2 );
Save to Order Meta:
function yith_wc_order_line_item( $item, $cart_item_key, $values, $order ) {
foreach ( $item as $cart_item_key => $data ) {
if ( isset( $data['irt_booking_names'] ) ) {
$item->add_meta_data( 'irt_booking_names', $data['irt_booking_names'], TRUE );
}
if ( isset( $data['irt_pickup_point'] ) ) {
$item->add_meta_data( 'irt_pickup_point', $data['irt_pickup_point'], TRUE );
}
}
}
add_action( 'woocommerce_checkout_create_order_line_item', 'yith_wc_order_line_item', 10, 4 );
Key Features:
- Product tag-based conditional instructions
- Complete WooCommerce data flow (form → cart → order)
- Proper sanitization and security
- Translation-ready strings
3. Weather API Integration
Custom REST API endpoint providing real-time Rome weather data using OpenWeatherMap.
// Fetch and cache weather data
function fetch_openweathermap_data() {
$api_url = 'https://api.openweathermap.org/data/2.5/weather?q=Rome,IT&appid=YOUR_API_KEY&units=metric';
$response = wp_remote_get( $api_url );
if ( is_wp_error( $response ) ) {
return;
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, TRUE );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return;
}
// Store in transient cache for 1 hour
set_transient( 'openweathermap_rome_data', $data, HOUR_IN_SECONDS );
}
// Schedule hourly updates
if ( ! wp_next_scheduled( 'fetch_openweathermap_data_hook' ) ) {
wp_schedule_event( time(), 'hourly', 'fetch_openweathermap_data_hook' );
}
add_action( 'fetch_openweathermap_data_hook', 'fetch_openweathermap_data' );
// REST API callback
function get_openweathermap_data() {
$data = get_transient( 'openweathermap_rome_data' );
if ( $data === FALSE ) {
fetch_openweathermap_data();
return new WP_Error( 'no_data', 'No weather data available', [ 'status' => 404 ] );
}
return rest_ensure_response( $data );
}
// Register REST route
function register_openweathermap_route() {
register_rest_route( 'custom/v1', '/weather', [
'methods' => 'GET',
'callback' => 'get_openweathermap_data',
] );
}
add_action( 'rest_api_init', 'register_openweathermap_route' );
// Cleanup on deactivation
function my_plugin_deactivation() {
$timestamp = wp_next_scheduled( 'fetch_openweathermap_data_hook' );
wp_unschedule_event( $timestamp, 'fetch_openweathermap_data_hook' );
}
register_deactivation_hook( __FILE__, 'my_plugin_deactivation' );
Key Features:
- WordPress Transient API for caching (1-hour TTL)
- WP Cron for scheduled updates
- Custom REST API endpoint at
/wp-json/custom/v1/weather - Proper error handling and cleanup
4. Dynamic Price Calculations
Parses YITH Booking’s serialized price data to display intelligent pricing information.
function irt_get_best_base_price() {
$post_id = get_the_ID();
// Retrieve serialized YITH booking costs
$serialized_data = get_post_meta($post_id, '_yith_booking_costs_range', TRUE);
if (empty($serialized_data)) {
return NULL;
}
$data = maybe_unserialize($serialized_data);
$lowest_price = PHP_FLOAT_MAX;
$lowest_name = '';
foreach ($data as $availability) {
// Skip entries without required data
if (!isset($availability['base_price']) ||
empty($availability['conditions']) ||
!isset($availability['conditions'][1]['type'])) {
continue;
}
// Skip Kids (person-type-24) and Infants (person-type-25)
// to show best adult/group price
$type = $availability['conditions'][1]['type'];
if ($type === 'person-type-24' || $type === 'person-type-25') {
continue;
}
$base_price = floatval($availability['base_price']);
$name = $availability['name'];
if ($base_price < $lowest_price && $base_price > 0) {
$lowest_price = $base_price;
$lowest_name = $name;
}
}
if ($lowest_price === PHP_FLOAT_MAX) {
return NULL;
}
// Return formatted price with tier name
return '<p class="price-range">Best price p.p. at ' .
'<span class="woocommerce-Price-amount amount"><bdi>' .
number_format($lowest_price, 2, ',', '.') .
' <span class="woocommerce-Price-currencySymbol">€</span></bdi></span> (' .
$lowest_name . ')</p>';
}
add_shortcode('irt_get_best_base_price', 'irt_get_best_base_price');
Key Features:
- Parses YITH’s complex serialized pricing structure
- Filters out kids/infant prices for meaningful “best price” display
- European number formatting (comma decimals, dot thousands)
- Displays the pricing tier name for transparency
5. PDF Booking Voucher Customization
Custom PDF template with branded header and configurable notice sections.
Custom Header (header.php):
<?php
defined( 'YITH_WCBK' ) || exit;
$logo = apply_filters( 'yith_wcbk_booking_pdf_logo_url', '' );
$booking_link = $is_admin
? get_edit_post_link( $booking->get_id() )
: $booking->get_view_booking_url();
?>
<div class="logo">
<!-- Embedded base64 logo for reliable PDF rendering -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQ..."
alt="Italy Rome Tour" width="100" height="100"/>
</div>
<div class="clear"></div>
<div class="booking-title">
<h2 style="color: #026D61;">
<a href="<?php echo esc_url( $booking_link ); ?>" style="color: #026D61;">
<?php echo esc_html( $booking->get_name() ); ?>
</a>
</h2>
</div>
Custom Booking Template (booking.php) – Notice Section:
<div class="notice">
<?php if ($notice_1 = get_post_meta($booking->get_id(), 'irt_booking_notice_1', true)) { ?>
<h3 style="color: #026D61;">Please note</h3>
<p><?= $notice_1; ?></p>
<?php } ?>
<?php if ($notice_2 = get_post_meta($booking->get_id(), 'irt_booking_notice_2', true)) { ?>
<p><?= $notice_2; ?></p>
<?php } ?>
<?php if ($notice_3 = get_post_meta($booking->get_id(), 'irt_booking_notice_3', true)) { ?>
<p><?= $notice_3; ?></p>
<?php } ?>
</div>
<div class="sign">
<p>Your Local Operator</p>
<p>Max Parini<br/>
<p>Official Tour Guide</p>
<p><b>Phone:</b> +393939901484</p>
</div>
PDF Styling:
body {
color: #2f3742;
font-family: DejaVu Sans, sans-serif;
}
.logo img {
width: 100px;
height: auto;
}
.booking-table th, .booking-table td {
padding: 11px 20px;
text-align: left;
border-bottom: 1px solid #676f76;
}
.notice p {
font-size: 11px;
font-style: italic;
}
.footer {
position: fixed;
width: 100%;
bottom: 0;
text-align: center;
font-size: 70%;
}
Key Features:
- Base64-embedded logo for reliable rendering in PDF
- Brand color (#026D61 – teal green) consistency
- Three configurable notice sections via ACF
- Professional operator signature block
- DejaVu Sans font for PDF compatibility
6. ACF Field Configuration
Tour specifications managed through Advanced Custom Fields with JSON sync.
{
"key": "group_654df0fdcf62e",
"title": "Tour additional information",
"fields": [
{
"key": "field_654e141684d5e",
"label": "Tour specifications",
"type": "tab",
"placement": "top"
},
{
"key": "field_654df0fe0295d",
"label": "Tour Duration (verbose)",
"name": "irt_tour_duration",
"type": "text"
},
{
"key": "field_654e1287583d1",
"label": "Starting time",
"name": "irt_starting_time",
"type": "text"
},
{
"key": "field_654e12b8583d2",
"label": "Pick up Location",
"name": "irt_pick_up_location",
"type": "checkbox",
"choices": {
"Hotel": "Hotel",
"Bed and Breakfast": "Bed and Breakfast",
"Cruise Ship": "Cruise Ship",
"Other": "Other"
}
},
{
"key": "field_65afc9d44e806",
"label": "Other Pickup Location",
"name": "irt_other_pick_up_location",
"type": "text",
"conditional_logic": [
[{
"field": "field_654e12b8583d2",
"operator": "==",
"value": "Other"
}]
]
},
{
"key": "field_654e1302583d3",
"label": "Skip the line",
"name": "irt_skip_the_line",
"type": "radio",
"choices": {
"Included": "Included",
"Not included": "Not included"
},
"default_value": "Included"
},
{
"key": "field_654e133a583d4",
"label": "Transportation",
"name": "irt_transportation",
"type": "text",
"default_value": "Luxury Mercedes minivan and driver guide"
},
{
"key": "field_654e15b47f7f8",
"label": "Tour Itinerary Steps",
"name": "irt_tour_itinerary_steps",
"type": "repeater",
"layout": "table",
"sub_fields": [
{
"key": "field_654e15ed7f7f9",
"label": "Tour Itinerary Step",
"name": "irt_tour_itinerary_step",
"type": "text"
}
]
}
],
"location": [[{
"param": "post_type",
"operator": "==",
"value": "product"
}]]
}
Field Groups Summary:
- Tour Specifications: 10 fields covering duration, tickets, transportation, accessibility
- Tour Itinerary: Repeater field for unlimited itinerary steps
- Category/Tag Data: Hero images and introductions for archive pages
- Booking Notices: Three customizable notices for PDF and email templates
The 2024 Migration
From Custom Plugin to YITH WooCommerce Booking
Original System (2019-2024):
- Custom
tours-bookingplugin - Separate
toursandshared-tourscustom post types - External API for cart management (panel.italyrometour.com)
- Direct Stripe integration with Checkout.js
Migration (May 2024):
- Consolidated to WooCommerce products
- YITH WooCommerce Booking Premium for availability/pricing
- Native WooCommerce cart and checkout
- Stripe through WooCommerce payment gateway
Benefits:
- Better long-term maintainability
- WooCommerce ecosystem compatibility
- Improved security with managed payment processing
- Easier content management for tour operators
- Access to YITH’s continuous development and support
Key Features Summary
| Feature | Implementation |
|---|---|
| Tour Specifications | ACF fields + custom shortcode with icons |
| Dynamic Pricing | YITH Booking + custom price calculation shortcodes |
| Multiple Pickup Types | Product tag-based conditional form fields |
| Participant Management | Custom WooCommerce form fields persisted to orders |
| PDF Vouchers | YITH template overrides with custom branding |
| Weather Widget | Custom REST API with OpenWeatherMap + transient caching |
| GDPR Compliance | iubenda integration with Elementor form hooks |
| Blog Pagination | Elementor query hooks for custom layouts |
| Price Prefix | WooCommerce filter adding “from” on archive pages |
Technical Highlights
- 539 lines of custom PHP in theme functions.php
- 74+ template overrides for YITH Booking
- 4 ACF field groups with JSON sync enabled
- Custom REST API endpoint for weather data
- Complete WooCommerce integration for custom booking fields
- Schema.org structured data for price markup
- Multi-language ready with translation functions
File Reference
| File | Purpose |
|---|---|
functions.php | Core customizations (shortcodes, hooks, API) |
acf-json/*.json | Field group configurations |
yith-woocommerce-booking-templates/booking/pdf/ | PDF voucher templates |
yith-woocommerce-booking-templates/emails/ | Email notification templates |