Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No programatic way to add booking products to the cart #368

Open
2 tasks
rtpHarry opened this issue Sep 5, 2023 · 6 comments
Open
2 tasks

No programatic way to add booking products to the cart #368

rtpHarry opened this issue Sep 5, 2023 · 6 comments
Labels
priority: low The issue/PR is low priority—not many people are affected or there’s a workaround, etc. type: enhancement The issue is a request for an enhancement.

Comments

@rtpHarry
Copy link

rtpHarry commented Sep 5, 2023

What I expected

I want to be able to add the items to the cart from PHP code.

This is because I want to be able to show related safaris to go alongside my accommodation bookings. The user can select which safari they are interested in during their stay, and add them as they add the booking to the cart.

I've built the whole thing, right down to the last bit where I need to add it to the cart.

I have the required data to identify it but there doesn't seem to be any code that I can access. Its either fenced off inside other classes, or in a class that registers tons of actions and filters in its constructor so cannot be used, or relies on $GET or $POST.

I just want a simple way to pass in the required bits of meta and say add to cart.

It's looking like I'm going to have to copy chunks of the internal code to do this which is going to be time consuming and make my code brittle.

What happened instead

Can't find any api that is accessible that I can use to add a bookable product to the cart.

Steps to reproduce the issue

I wrote this and hooked it to woocommerce_add_to_cart but the page reloads and the products are not in the cart.

    function add_booking_products_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data)
    {
        if (isset($_POST['_knepp_safari_suggest'])) {
            // get data
            $safaris = json_decode(stripslashes($_POST['_knepp_safari_suggest']), true);
            $persons = isset($_POST['wc_bookings_field_persons']) ? $_POST['wc_bookings_field_persons'] : 1;

            // add to cart
            foreach ($safaris as $safari) {
                $product_id = $safari['product_id'];
                $quantity = 1; // not used by bookable products
                $start_date_time = $safari['wc_bookings_field_start_date_time'];

                $cart_item_data = array(
                    'wc_bookings_field_start_date_time' => $start_date_time,
                    'wc_bookings_field_persons' => $persons,
                );

                WC()->cart->add_to_cart($product_id, $quantity, 0, array(), $cart_item_data);
            }
        }
    }

  • Issue assigned to next milestone.
  • Issue assigned a priority (will be assessed by maintainers).
@vikrampm1 vikrampm1 added priority: low The issue/PR is low priority—not many people are affected or there’s a workaround, etc. type: enhancement The issue is a request for an enhancement. labels Sep 6, 2023
@rtpHarry
Copy link
Author

rtpHarry commented Sep 6, 2023

does priority low mean that I'm right and there is no way to add it to the cart from code?

@rtpHarry
Copy link
Author

rtpHarry commented Sep 6, 2023

Well if somebody else comes along looking to solve this problem, here is how I've done it.

Added this action:

        add_action('woocommerce_add_to_cart', array($this, 'process_safari_suggest_bookings'), 10, 6);

And added these functions to my plugin:

    /**
     * Add extra booking items to cart
     * @since 1.0.0
     */
    function process_safari_suggest_bookings($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data)
    {
        if (isset($_POST['_knepp_safari_suggest'])) {
            // get data
            $safaris = json_decode(stripslashes($_POST['_knepp_safari_suggest']), true);
            $persons = isset($_POST['wc_bookings_field_persons']) ? $_POST['wc_bookings_field_persons'] : 1;

            // add to cart
            foreach ($safaris as $safari) {
                $this->add_safari_to_cart($safari, $persons);
            }

            // clear value so its only run once this request
            unset($_POST['_knepp_safari_suggest']);
        }
    }

    /**
     * Add bookable product to cart
     * Code taken from WC_Booking_Cart_Manager->add_cart_item_data()
     * @since 1.0.0
     */
    private function add_safari_to_cart($safari, $persons)
    {
        $product_id = $safari['product_id'];
        $quantity = 1; // not used by bookable products
        $start_date_time = $safari['wc_bookings_field_start_date_time'];

        $simulated_post = array(
            'wc_bookings_field_start_date_time' => $start_date_time,
            'wc_bookings_field_persons' => $persons,
        );
        $cart_item_meta = array();

        $product = wc_get_product($product_id);

        if (!is_wc_booking_product($product)) {
            return;
        }

        $cart_item_meta['booking'] = wc_bookings_get_posted_data($simulated_post, $product);
        $cart_item_meta['booking']['_cost'] = WC_Bookings_Cost_Calculation::calculate_booking_cost($cart_item_meta['booking'], $product);

        if ($cart_item_meta['booking']['_cost'] instanceof WP_Error) {
            throw new Exception($cart_item_meta['booking']['_cost']->get_error_message());
        }

        // Create the new booking
        $new_booking = $this->create_booking_from_cart_data($cart_item_meta, $product_id);

        // Store in cart
        $cart_item_meta['booking']['_booking_id'] = $new_booking->get_id();

        // Schedule this item to be removed from the cart if the user is inactive.
        $this->schedule_cart_removal($new_booking->get_id());

        WC()->cart->add_to_cart($product_id, $quantity, 0, array(), $cart_item_meta);
    }

    /**
     * Create booking from cart data
     *
     * @param        $cart_item_meta
     * @param        $product_id
     * @param string $status
     *
     * @return WC_Booking
     */
    private function create_booking_from_cart_data($cart_item_meta, $product_id, $status = 'in-cart')
    {
        // Create the new booking
        $new_booking_data = array(
            'product_id'     => $product_id, // Booking ID
            'cost'           => $cart_item_meta['booking']['_cost'], // Cost of this booking
            'start_date'     => $cart_item_meta['booking']['_start_date'],
            'end_date'       => $cart_item_meta['booking']['_end_date'],
            'all_day'        => $cart_item_meta['booking']['_all_day'],
            'local_timezone' => $cart_item_meta['booking']['_local_timezone'],
        );

        // Check if the booking has resources
        if (isset($cart_item_meta['booking']['_resource_id'])) {
            $new_booking_data['resource_id'] = $cart_item_meta['booking']['_resource_id']; // ID of the resource
        }

        // Checks if the booking allows persons
        if (isset($cart_item_meta['booking']['_persons'])) {
            $new_booking_data['persons'] = $cart_item_meta['booking']['_persons']; // Count of persons making booking
        }

        $new_booking = get_wc_booking($new_booking_data);
        $new_booking->create($status);

        return $new_booking;
    }

    /**
     * Schedule booking to be deleted if inactive
     */
    public function schedule_cart_removal($booking_id)
    {
        $minutes = apply_filters('woocommerce_bookings_remove_inactive_cart_time', 60);

        /**
         * If this has been emptied, or set to 0, it will just exit. This means that in-cart bookings will need to be manually removed.
         * Also take note that if the $minutes var is set to 5 or less, this means that it is possible for the in-cart booking to be
         * removed before the customer is able to check out.
         */
        if (empty($minutes)) {
            return;
        }

        $timestamp = time() + MINUTE_IN_SECONDS * (int) $minutes;

        wp_schedule_single_event($timestamp, 'wc-booking-remove-inactive-cart', array($booking_id));
    }

And the way it works is that I've used the slots api to generate a list of options, and then use some angular code to json encode the values into a hidden field on booking form so that it gets submitted on the add to cart.

The site im building this on is elementor, jet woo builder, based and it works with the ajax add to cart as well.

From a plugin point of view, if I could have just got a way to access the instantiated WC_Booking_Cart_Manager then I could have called WC_Booking_Cart_Manager->add_cart_item_data() and done this without having to adopt a bunch of the plugins code and then be responsible for maintaining it.

However, it is just instantiated by the main class as a simple new WC_Booking_Cart_Manager(), without assigning to to a variable, and no way to get a reference to it.

@rtpHarry
Copy link
Author

rtpHarry commented Sep 6, 2023

I put a modification into it where I'm passing in a random number id now and stuffing that in a transient. Otherwise, sometimes it would end up adding in multiple times to the cart.

Here is the updated function:

    /**
     * Add extra booking items to cart
     * @since 1.0.0
     */
    function process_safari_suggest_bookings($cart)
    {
        if (isset($_POST['_knepp_safari_suggest_id']) && isset($_POST['_knepp_safari_suggest'])) {

            $safariId = sanitize_text_field($_POST['_knepp_safari_suggest_id']);

            if (false === get_transient($safariId)) {

                set_transient($safariId, true, 60);

                $safaris = json_decode(stripslashes($_POST['_knepp_safari_suggest']), true);
                $persons = isset($_POST['wc_bookings_field_persons']) ? $_POST['wc_bookings_field_persons'] : 1;

                foreach ($safaris as $safari) {
                    $this->add_safari_to_cart($safari, $persons);
                }
            }
        }
    }

@faisal-alvi
Copy link
Member

@rtpHarry thank you for the report.

does priority low mean that I'm right and there is no way to add it to the cart from code?

No, the priority low means that the ticket does not greatly impact the plugin and the users. Hence, it should be prioritized as "low".

Well if somebody else comes along looking to solve this problem, here is how I've done it.

I'm glad to know that you found a solution. Thanks for sharing the details.

it is just instantiated by the main class as a simple new WC_Booking_Cart_Manager(), without assigning to to a variable, and no way to get a reference to it.

The plugin never required assigning variables to initiated classes so far. Your case is unique so I recommend you initialize the required class and assign in a variable in your custom plugin/functions file.


Q: If you feel there are no code changes required in the Bookings plugin, should I go ahead and close the ticket?

@rtpHarry
Copy link
Author

I cannot instantiate my own copy of WC_Booking_Cart_Manager as it registers a large amount of actions / filters in its constructor.

At the moment I have a working solution but it is not a good solution as I have had to adopt chunks of the plugin code to make it work.

It would be better if there was an API to add bookings to the cart, or a way to get access to the WC_Booking_Cart_Manager class which would have given me a basic API.

At the moment, if you change any parts of your plugin, then my code could break, so it is a brittle solution to the problem.

@IvanBalakirev
Copy link

Hey,

I have the same request and ended up almost with the same solution, should've googled first ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: low The issue/PR is low priority—not many people are affected or there’s a workaround, etc. type: enhancement The issue is a request for an enhancement.
Projects
None yet
Development

No branches or pull requests

4 participants