At Morningstar Digital, we're committed to pushing the boundaries of what's possible with digital solutions. Our experience spans from crafting bespoke websites for small businesses to deploying scalable e-commerce solutions for global enterprises. Today, we're diving deep into a common challenge many of our clients face: managing bookings for multiple resources per product in WooCommerce.
WooCommerce Bookings offers a robust platform for online businesses, but its standard booking functionalities can sometimes fall short for those requiring management of multiple resources per product. Whether it's booking an entire facility or allocating various resources for a single booking, the need for a more tailored solution is clear.
We've developed a custom code solution designed to expand WooCommerce's booking capabilities. This solution is ideal for full facility hires but is adaptable for various other needs. Let's walk through the key components of our code:
1. Adding a Custom Field for Resource Requirements
Firstly, we introduce a new option within the WooCommerce product data tab. This checkbox allows you to specify if all resources are required for booking a product. It's a crucial first step in ensuring your bookings are handled accurately according to your business needs.
/**
* Adds a custom field to indicate if all resources are required for a bookable product.
*/
add_action('woocommerce_product_options_general_product_data', 'mstar_add_require_all_resources_field');
function mstar_add_require_all_resources_field() {
echo '<div class="options_group show_if_bookable">';
woocommerce_wp_checkbox([
'id' => '_require_all_resources',
'label' => __('Require All Resources', 'your-text-domain'),
'description' => __('If enabled, this booking can only be made if all resources are available.', 'your-text-domain'),
]);
echo '</div>';
}
2. Saving the Custom Field Value
When changes are made to the product, our code ensures that the state of the new checkbox is saved, keeping your product data consistent and up-to-date.
/**
* Saves the state of the "_require_all_resources" checkbox when the product is saved.
*/
add_action('woocommerce_admin_process_product_object', 'mstar_save_require_all_resources_field', 10, 1);
function mstar_save_require_all_resources_field($product) {
if (isset($_POST['_require_all_resources'])) {
$product->update_meta_data('_require_all_resources', 'yes');
} else {
$product->delete_meta_data('_require_all_resources');
}
}
3. Adjusting Availability Based on Resource Requirements
Our solution then modifies the booking availability, ensuring that a product can only be booked if all required resources are available. This step is vital for businesses that cannot operate without full resource availability.
/**
* Modifies the HTML output for time slots based on resource availability for bookings.
*/
add_filter('wc_bookings_get_time_slots_html', 'mstar_custom_adjust_time_slots_html_based_on_bookings', 10, 2);
function mstar_custom_adjust_time_slots_html_based_on_bookings($html, $available_blocks) {
$doc = new DOMDocument();
@$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($doc);
$productNodes = $xpath->query("//div[contains(@class, 'wc-bookings-start-time-container')]");
if ($productNodes->length > 0) {
$productNode = $productNodes->item(0);
$productID = $productNode->getAttribute('data-product-id');
$product = wc_get_product($productID);
if ($product && 'yes' === $product->get_meta('_require_all_resources', true)) {
foreach ($available_blocks as $timestamp => $block) {
if ($block['booked'] > 0) {
$block_time = date('Hi', $timestamp);
foreach ($xpath->query("//option[@data-block='$block_time']") as $option) {
$option->parentNode->removeChild($option);
}
}
}
}
}
return $doc->saveHTML();
}
4. Booking All Required Resources
For products that require all resources, our code automatically books every necessary resource upon booking creation. This ensures no resource is left unallocated, streamlining the booking process for both the business and the customer.
/**
* Creates additional bookings if a product requires all resources to be booked.
*/
add_action('woocommerce_new_booking', 'mstar_handle_all_resources_booking', 10, 1);
function mstar_handle_all_resources_booking($booking_id) {
$booking = get_wc_booking($booking_id);
if (!$booking || $booking->get_meta('_booking_parent_id', true) != 0) {
return;
}
$product = wc_get_product($booking->get_product_id());
if (!$product || 'yes' !== $product->get_meta('_require_all_resources', true)) {
return;
}
$resources = $product->get_resources();
foreach ($resources as $resource) {
if ($resource->get_id() == $booking->get_resource_id()) {
continue; // Skip the already booked resource.
}
$new_booking_data = [
'product_id' => $booking->get_product_id(),
'resource_id' => $resource->get_id(),
'start_date' => $booking->get_start(),
'end_date' => $booking->get_end(),
'customer_id' => $booking->get_customer_id(),
'persons' => $booking->get_persons(),
'parent_id' => $booking_id,
];
create_wc_booking($new_booking_data['product_id'], $new_booking_data, 'in-cart', false);
}
}
5. Registering Actions for Status Changes
To start, we register actions for various booking status changes within WooCommerce:
// Register actions to handle updates to booking statuses.
add_action('woocommerce_booking_unpaid', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_trash', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_paid', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_pending-confirmation', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_confirmed', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_complete', 'mstar_update_related_bookings_status', 10, 1);
add_action('woocommerce_booking_cancelled', 'mstar_update_related_bookings_status', 10, 1);
This comprehensive approach ensures that no booking status change goes unnoticed, from payments pending to cancellations.
6. Updating Related Booking Statuses
When a booking's status changes, our custom function kicks in to adjust related bookings accordingly:
function mstar_update_related_bookings_status($booking_id) {
$booking = get_wc_booking($booking_id);
if (!$booking || $booking->get_meta('_booking_parent_id', true) != 0) {
return; // Only proceed for parent bookings.
}
$new_status = $booking->get_status(); // Retrieve the updated status.
// Fetch and update the status of all child bookings to match the parent.
$related_bookings = get_related_bookings($booking_id);
foreach ($related_bookings as $related_booking_id) {
$related_booking = get_wc_booking($related_booking_id);
if ($related_booking) {
$related_booking->update_status($new_status);
}
}
}
7. Identifying Related Bookings
Lastly, to ensure we can accurately update all relevant bookings, we've devised a method to identify all related bookings efficiently:
function get_related_bookings($booking_id) {
global $wpdb;
return $wpdb->get_col($wpdb->prepare("
SELECT post_id
FROM {$wpdb->postmeta}
WHERE meta_key = '_booking_parent_id'
AND meta_value = %d
", $booking_id));
}
While this code serves as a powerful starting point, we understand the nuances of every business might require specific adjustments. That's where our expertise shines.
Let's collaborate to refine your booking process. Book a discovery session with Morningstar Digital, and together we'll explore custom solutions tailored to your WooCommerce booking needs. Whether you're managing a complex array of resources or need a nuanced approach for specific situations, we're here to create a solution that works seamlessly for you.