WooCommerce Bookings Plugin Development and Integration with Multi Vendor
Tags: rental, calendar, woocommerce, multivendor
Published: 2022-04-19 08:36:56

### Choosing A Rental Bookings Plugin for WooCommerce
There are many rental booking plugins available for WooCommerce. As a [developer of rental bookings systems](https://rentalbookingsoftware.com/) for Magento 2, we had the benefit of using that experience and applying it towards WooCommerce. There is a huge difference between the two as far as their technology stack, but the basics remain the same. Some of the features we wanted to make sure to implement were: 

- Real rental inventory tracking, not just adding product attributes, but being able to query inventory for a given start and end date
- A flexible calendar that is well supported by the community, ideally it would support date ranges as well as times. We decided on Flatpickr.
- A day by day as well as hour by hour rental inventory report page

### Choosing A Multi Vendor Plugin for WooCommerce
For the multi vendor portion we tested out a variety of different plugins for WooCommerce. What we found was that most of them to not support product bundles, and each has their own integration style. We settled upon Dokan because it had most of the features our client was looking for such as an integration with a WooCommerce auctions plugin, Stripe payouts, and the UI was nice to use. 

### Integrating Rentals Admin Pages with Dokan
So the harder part comes to how do we integrate the Dokan Dashboard with our rental plugin admin reports? Not only that but we also wanted to integrate the processing of Advanced Custom Fields when a product is updated. Here's how we did it. First we needed to add these three filters: 

```
add_filter( 'dokan_get_dashboard_nav', [$this, 'add_booking_menu'] );
add_filter( 'dokan_query_var_filter', array( $this, 'add_dokan_booking_endpoint' ) );
add_action( 'dokan_load_custom_template', array( $this, 'load_booking_templates' ), 10, 1 );
```

(These are added in the init() method of our class, for class structure see below)

Here are the methods for each of those: 

```
 public function add_booking_menu($urls){
        $urls['bookinginventory1'] = [
                'title' => __('Booking Inventory', 'sibooking'),
                'icon' => '<i class="fa fa-truck"></i>',
                'url' => dokan_get_navigation_url( 'sibooking/inventory'),
                'pos' => 51,
                'permission' => 'dokan_view_order_menu'
            ];
        $urls['bookingcalendar'] = [
            'title' => __('Booking Calendar', 'sibooking'),
            'icon' => '<i class="fa fa-calendar"></i>',
            'url' => dokan_get_navigation_url( 'sibooking/calendar'),
            'pos' => 51,
            'permission' => 'dokan_view_order_menu'
            ];
        return $urls;
    }

    public function add_dokan_booking_endpoint($query_var){
        $query_var[] = 'sibooking';
        return $query_var;
    }

    public function load_booking_templates( $args ) {
        if ( isset( $args['sibooking'] )) {
            include(SIBOOKING_PLUGIN_ROOT . 'Templates/dokan/dashboard/calendar.php');
        }

    }
```

After that we added the templates to the folder: Templates/dokan/dashboard here is a sample of how that looks: 

```
<?php do_action( 'dokan_dashboard_wrap_start' ); ?>

<div class="dokan-dashboard-wrap">

    <?php
    $current_page = get_query_var( 'sibooking' );
    /**
     *  dokan_dashboard_content_before hook
     *  dokan_dashboard_support_content_before
     *
     *  @hooked get_dashboard_side_navigation
     *
     *  @since 2.4
     */
    do_action( 'dokan_dashboard_content_before' );
    do_action( 'dokan_dashboard_support_content_before' );
    ?>

    <div class="dokan-dashboard-content dokan-booking-wrapper dokan-product-edit">

<?php
switch ( $current_page ) {
    case 'calendar':
        echo do_shortcode('[vg_display_admin_page page_url="' . admin_url() . '?page=sibooking-calendar-report"]');
        break;
    case 'inventory':
        echo do_shortcode('[vg_display_admin_page page_url="' . admin_url() . '?page=sibooking-inventory-report"]');

        break;
}
?>
    </div><!-- .dokan-dashboard-content -->

    <?php

    /**
     *  dokan_dashboard_content_after hook
     *  dokan_dashboard_support_content_after hook
     *
     *  @since 2.4
     */
    do_action( 'dokan_dashboard_content_after' );
    do_action( 'dokan_dashboard_support_content_after' );
    ?>

</div><!-- .dokan-dashboard-wrap -->

<?php do_action( 'dokan_dashboard_wrap_end' ); ?>
```

As you can see in this code we are using the plugin WP Frontend on Admin for this. Here are the results: 


![Rental Inventory Report](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9wmonf7ue9qnw266nptr.jpg)

### A Few Code Samples For Specific Parts of the Multi Vendor Rental System

So we've gone over a bit of the Rental Bookings to Dokan multi vendor integration, but what about the rental calendar system itself, how does that work? Here are a few code samples: 

###### Loading the Rental Calendar

As you can see this is a action plugin for after the add to cart button is shown, it shows a custom template. That template has the code for loading the Flatpickr divs. 

```
add_action( 'woocommerce_before_add_to_cart_button', [$this, 'load_calendar'] );

    public function load_calendar() {
        if ( is_product() ) {
            if(sibooking_is_booking(wc_get_product()) == '1'){
                $template = '/single-product/add-to-cart/calendar.php';
                wc_get_template($template, $args = [], $template_path = '', dirname(__FILE__, 2) . '/Templates/');
            }
        }
    }

```

How the rental calendar looks on the frontned, we of course also added the Flatpickr js and css files which we won't describe here, and wrote custom date range availability checks: 


![calendar front end](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jf9sletzmeibvm8tzth.jpg)

#### Composer Loading
We of course wanted to use Composer autoloading for our WooCommerce rental plugin. Here is how we accomplished that. Within the main entry point to the plugin we have the following:

```

$libraries = require_once SIBOOKING_PLUGIN_ROOT . 'vendor/autoload.php';

add_action(
        'plugins_loaded', static function () use ( $libraries ) {
            // for phpunit to not throw error
            if(is_bool($libraries)){
                return;
            }
        new Sibookings($libraries);
    });

 public function __construct(\Composer\Autoload\ClassLoader $composer) {
        $this->composer = $composer;
        $this->version = SIBOOKING_VERSION;
        $this->plugin_name = 'sibooking';
        $this->set_locale();
        $this->get_classes();
        $this->init_classes();
    }

private function get_classes() {
        $prefix    = $this->composer->getPrefixesPsr4();
        $classmap  = $this->composer->getClassMap();
        $classes = \array_keys( $classmap );

        foreach ( $classes as $class ) {
            if ( strpos($class,'Sibooking') === FALSE ||
                strpos($class,'BookingDataObject') !== FALSE ||
                strpos($class,'BookingDataStore') !== FALSE ||
                strpos($class,'BookingEloquentModel') !== FALSE ||
                strpos($class,'BookingsTable') !== FALSE ||
                strpos($class,'BookingEloquentStore') !== FALSE ||
                strpos($class,'Utility') !== FALSE
            ) {
                continue;
            }
            $this->classes[] = $class;
        }
        return $this->classes;
    }

    private function init_classes() {
        foreach ( $this->classes as $class ) {
            try {
                $temp = new $class;
                $temp->init();
            } catch ( \Throwable $err ) {
                \do_action( 'sibooking_initialize_failed', $err );
                if ( WP_DEBUG ) {
                    throw new \Exception( $err->getMessage() );
                }
            }
        }
    }

```
This method works really well as you can exclude certain classes that you don't want to load by using the strpo() area of the classes loop. 


#### Class Structure

#### Custom Rental Pricing
Two hooks are needed to change to a custom rental price for rental products in WooCommerce. Here is the code for that: 

```
add_filter('woocommerce_product_get_price', [$this, 'set_rental_price_product'], 99, 2);
add_filter('woocommerce_add_cart_item', [$this,  'set_rental_price_cart'], 10, 2);

 /**
     * @param $price
     * @param \WC_Product $product
     * @return int|mixed|null
     */
    public function set_rental_price_product($price = null, $product = null) {
        $isbooking = sibooking_is_booking($product);
        $isbookingpurchase = !empty($product->get_meta['_sibooking-isbookingpurchase']) ? true : false;
        if(!$isbooking || $isbookingpurchase == true){
            return $price;
        }

        $startDate = $product->get_meta('sibooking-from');
        $endDate = $product->get_meta('sibooking-to');

        if($startDate == '') {
            return $price;
        }

        $price = $this->priceCalculation->calculatePrice($product->get_id(), $startDate, $endDate);

        return $price;
    }

 /**
     * @param $cart_item
     * @return int|mixed
     */
    public function set_rental_price_cart($cart_item, $cart_item_key) {
        $product = $cart_item['data'];
        $isbooking = sibooking_is_booking($product);
        $isbookingpurchase = !empty($cart_item['_sibooking-isbookingpurchase']) ? true : false;
        if(!$isbooking || $isbookingpurchase == true){
            return $cart_item;
        }

        $startDate = $cart_item['sibooking-from'];
        $endDate = $cart_item['sibooking-to'];

        $price = $this->priceCalculation->calculatePrice($product->get_id(), $startDate, $endDate);

        $product->set_price($price);

        $cart_item['data'] = $product;

        return $cart_item;
    }

```

#### Advanced Custom Fields for Rental Bookings
Advanced Custom Fields Pro was used for building the rental bookings settings panel. We highly recommend using it especially with the repeator fields that are available and date time fields. 

### Multi Vendor WooCommerce Rental Booking Plugin
For more information about our [rental booking plugin for WooCommerce](https://rentalbookingsoftware.com) please visit our web site.