PATH:
home
/
letacommog
/
laindinois
/
wp-content
/
plugins
/
woocommerce-bookings
/
includes
/
integrations
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Google Calendar Integration. */ class WC_Bookings_Google_Calendar_Integration extends WC_Integration { /** * Init and hook in the integration. */ public function __construct() { $this->plugin_id = 'wc_bookings_'; $this->id = 'google_calendar'; $this->method_title = __( 'Google Calendar', 'woocommerce-bookings' ); $this->method_description = __( 'WooCommerce Bookings - Google Calendar integration.', 'woocommerce-bookings' ); // API. $this->oauth_uri = 'https://accounts.google.com/o/oauth2/'; $this->calendars_uri = 'https://www.googleapis.com/calendar/v3/calendars/'; $this->api_scope = 'https://www.googleapis.com/auth/calendar'; $this->redirect_uri = WC()->api_request_url( 'wc_bookings_google_calendar' ); // Define user set variables. $this->client_id = $this->get_option( 'client_id' ); $this->client_secret = $this->get_option( 'client_secret' ); $this->calendar_id = $this->get_option( 'calendar_id' ); $this->debug = $this->get_option( 'debug' ); // Load the settings. $this->init_form_fields(); $this->init_settings(); // Actions. add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_admin_options' ) ); add_action( 'woocommerce_api_wc_bookings_google_calendar' , array( $this, 'oauth_redirect' ) ); add_action( 'woocommerce_booking_confirmed', array( $this, 'sync_new_booking' ) ); add_action( 'woocommerce_booking_paid', array( $this, 'sync_new_booking' ) ); add_action( 'woocommerce_booking_complete', array( $this, 'sync_new_booking' ) ); add_action( 'woocommerce_booking_cancelled', array( $this, 'remove_booking' ) ); add_action( 'woocommerce_booking_process_meta', array( $this, 'sync_edited_booking' ) ); add_action( 'trashed_post', array( $this, 'remove_booking' ) ); add_action( 'untrashed_post', array( $this, 'sync_untrashed_booking' ) ); if ( is_admin() ) { add_action( 'admin_notices', array( $this, 'admin_notices' ) ); } // Active logs. if ( 'yes' === $this->debug ) { if ( class_exists( 'WC_Logger' ) ) { $this->log = new WC_Logger(); } else { $this->log = WC()->logger(); } } if ( isset( $_POST['wc_bookings_google_calendar_redirect'] ) && $_POST['wc_bookings_google_calendar_redirect'] && empty( $_POST['save'] ) ) { add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_calendar_redirect' ) ); } } /** * Process Calendar redirect * * @since 1.11 */ public function process_calendar_redirect() { $oauth_url = add_query_arg( array( 'scope' => $this->api_scope, 'redirect_uri' => $this->redirect_uri, 'response_type' => 'code', 'client_id' => $this->get_option( 'client_id' ), 'approval_prompt' => 'force', 'access_type' => 'offline', ), $this->oauth_uri . 'auth' ); wp_redirect( $oauth_url ); exit; } /** * Initialize integration settings form fields. * * @return void */ public function init_form_fields() { $this->form_fields = array( 'client_id' => array( 'title' => __( 'Client ID', 'woocommerce-bookings' ), 'type' => 'text', 'description' => __( 'Enter with your Google Client ID.', 'woocommerce-bookings' ), 'desc_tip' => true, 'default' => '', ), 'client_secret' => array( 'title' => __( 'Client Secret', 'woocommerce-bookings' ), 'type' => 'text', 'description' => __( 'Enter with your Google Client Secret.', 'woocommerce-bookings' ), 'desc_tip' => true, 'default' => '', ), 'calendar_id' => array( 'title' => __( 'Calendar ID', 'woocommerce-bookings' ), 'type' => 'text', 'description' => __( 'Enter with your Calendar ID.', 'woocommerce-bookings' ), 'desc_tip' => true, 'default' => '', ), 'authorization' => array( 'title' => __( 'Authorization', 'woocommerce-bookings' ), 'type' => 'google_calendar_authorization', ), 'testing' => array( 'title' => __( 'Testing', 'woocommerce-bookings' ), 'type' => 'title', 'description' => '', ), 'debug' => array( 'title' => __( 'Debug Log', 'woocommerce-bookings' ), 'type' => 'checkbox', 'label' => __( 'Enable logging', 'woocommerce-bookings' ), 'default' => 'no', /* translators: 1: log file path */ 'description' => sprintf( __( 'Log Google Calendar events, such as API requests, inside %s', 'woocommerce-bookings' ), '<code>woocommerce/logs/' . $this->id . '-' . sanitize_file_name( wp_hash( $this->id ) ) . '.txt</code>' ), ), ); } /** * Validate the Google Calendar Authorization field. * * @param mixed $key * * @return string */ public function validate_google_calendar_authorization_field( $key ) { return ''; } /** * Generate the oogle Calendar Authorization field. * * @param mixed $key * @param array $data * * @return string */ public function generate_google_calendar_authorization_html( $key, $data ) { $options = $this->plugin_id . $this->id . '_'; $client_id = isset( $_POST[ $options . 'client_id' ] ) ? sanitize_text_field( $_POST[ $options . 'client_id' ] ) : $this->client_id; $client_secret = isset( $_POST[ $options . 'client_secret' ] ) ? sanitize_text_field( $_POST[ $options . 'client_secret' ] ) : $this->client_secret; $calendar_id = isset( $_POST[ $options . 'calendar_id' ] ) ? sanitize_text_field( $_POST[ $options . 'calendar_id' ] ) : $this->calendar_id; $access_token = $this->get_access_token(); ob_start(); ?> <tr valign="top"> <th scope="row" class="titledesc"> <?php echo wp_kses_post( $data['title'] ); ?> </th> <td class="forminp"> <input type="hidden" name="wc_bookings_google_calendar_redirect" id="wc_bookings_google_calendar_redirect"> <?php if ( ! $access_token && ( $client_id && $client_secret && $calendar_id ) ) : ?> <p class="submit"><a class="button button-primary" onclick="jQuery('#wc_bookings_google_calendar_redirect').val('1'); jQuery('#mainform').submit();"><?php _e( 'Connect with Google', 'woocommerce-bookings' ); ?></a></p> <?php elseif ( $access_token ) : ?> <p><?php _e( 'Successfully authenticated.', 'woocommerce-bookings' ); ?></p> <p class="submit"><a class="button button-primary" href="<?php echo esc_url( add_query_arg( array( 'logout' => 'true' ), $this->redirect_uri ) ); ?>"><?php _e( 'Disconnect', 'woocommerce-bookings' ); ?></a></p> <?php else : ?> <p><?php _e( 'Unable to authenticate, you must enter with your <strong>Client ID</strong>, <strong>Client Secret</strong> and <strong>Calendar ID</strong>.', 'woocommerce-bookings' ); ?></p> <?php endif; ?> </td> </tr> <?php return ob_get_clean(); } /** * Admin Options. * * @return string */ public function admin_options() { echo '<h3>' . $this->method_title . '</h3>'; echo wpautop( $this->method_description ); /* translators: 1: link to google developers console 2: WP redirect uri */ echo '<p>' . sprintf( __( 'To use this integration you need create a project in %1$s. Once your project has been created, you must enable the <strong>Google Calendar API</strong> in <strong>Your Project > Library</strong>, finally in <strong>Your Project > Credentials > Create Credentials</strong> you must create an OAuth Client ID for a <strong>Web application</strong> and set the <strong>Authorized redirect URIs</strong> as <code>%2$s</code>.', 'woocommerce-bookings' ), '<a href="https://console.developers.google.com/project" target="_blank">' . __( 'Google Developers Console', 'woocommerce-bookings' ) . '</a>', $this->redirect_uri ) . '</p>'; echo '<table class="form-table">'; $this->generate_settings_html(); echo '</table>'; echo '<div><input type="hidden" name="section" value="' . $this->id . '" /></div>'; } /** * Get Access Token. * * @param string $code Authorization code. * * @return string Access token. */ protected function get_access_token( $code = '' ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Getting Google API Access Token...' ); } $access_token = get_transient( 'wc_bookings_gcalendar_access_token' ); if ( ! $code && false !== $access_token ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Access Token recovered by transients: ' . print_r( $access_token, true ) ); } return $access_token; } $refresh_token = get_option( 'wc_bookings_gcalendar_refresh_token' ); if ( ! $code && $refresh_token ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Generating a new Access Token...' ); } $data = array( 'client_id' => $this->client_id, 'client_secret' => $this->client_secret, 'refresh_token' => $refresh_token, 'grant_type' => 'refresh_token', ); $params = array( 'body' => http_build_query( $data ), 'sslverify' => false, 'timeout' => 60, 'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded', ), ); $response = wp_remote_post( $this->oauth_uri . 'token', $params ); if ( ! is_wp_error( $response ) && 200 == $response['response']['code'] && 'OK' == $response['response']['message'] ) { $response_data = json_decode( $response['body'] ); $access_token = sanitize_text_field( $response_data->access_token ); if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Google API Access Token generated successfully: ' . print_r( $access_token, true ) ); } // Set the transient. set_transient( 'wc_bookings_gcalendar_access_token', $access_token, 3500 ); return $access_token; } else { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error while generating the Access Token: ' . print_r( $response, true ) ); } } } elseif ( '' != $code ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Renewing the Access Token...' ); } $data = array( 'code' => $code, 'client_id' => $this->client_id, 'client_secret' => $this->client_secret, 'redirect_uri' => $this->redirect_uri, 'grant_type' => 'authorization_code', ); $params = array( 'body' => http_build_query( $data ), 'sslverify' => false, 'timeout' => 60, 'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded', ), ); $response = wp_remote_post( $this->oauth_uri . 'token', $params ); if ( ! is_wp_error( $response ) && 200 == $response['response']['code'] && 'OK' == $response['response']['message'] ) { $response_data = json_decode( $response['body'] ); $access_token = sanitize_text_field( $response_data->access_token ); // Add refresh token. update_option( 'wc_bookings_gcalendar_refresh_token', $response_data->refresh_token ); // Set the transient. set_transient( 'wc_bookings_gcalendar_access_token', $access_token, 3500 ); if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Google API Access Token renewed successfully: ' . print_r( $access_token, true ) ); } return $access_token; } else { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error while renewing the Access Token: ' . print_r( $response, true ) ); } } } if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Failed to retrieve and generate the Access Token' ); } return ''; } /** * Makes an http request to the Google Calendar API. * * @param string $api_url API Url to make the request against * @param array $params Array of parameters that will be used when making the request * @version 1.11 * @since 1.11 * @return object Response object from the request */ protected function make_gcal_request( $api_url, $params = array() ) { if ( ! isset( $api_url ) ) { return false; } $access_token = $this->get_access_token(); $method = ( isset( $params['method'] ) ) ? strtoupper( $params['method'] ) : 'GET'; $params['sslverify'] = false; $params['timeout'] = 60; $params['headers'] = array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $access_token, ); if ( isset( $params['querystring'] ) ) { $api_url .= '?' . http_build_query( $params['querystring'] ); } if ( 'GET' === $params['method'] ) { unset( $params['body'] ); } $response = wp_remote_request( $api_url, $params ); if ( ! is_wp_error( $response ) && 200 == $response['response']['code'] && 'OK' == $response['response']['message'] ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Google calendar request successful!' ); } } elseif ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error while making Google Calendar request for ' . $api_url . ': ' . print_r( $response, true ) ); } return $response; } /** * OAuth Logout. * * @return bool */ protected function oauth_logout() { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Leaving the Google Calendar app...' ); } $refresh_token = get_option( 'wc_bookings_gcalendar_refresh_token' ); if ( $refresh_token ) { $params = array( 'sslverify' => false, 'timeout' => 60, 'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded', ), ); $response = wp_remote_get( $this->oauth_uri . 'revoke?token=' . $refresh_token, $params ); if ( ! is_wp_error( $response ) && 200 == $response['response']['code'] && 'OK' == $response['response']['message'] ) { delete_option( 'wc_bookings_gcalendar_refresh_token' ); delete_transient( 'wc_bookings_gcalendar_access_token' ); if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Leave the Google Calendar app successfully' ); } return true; } else { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error when leaving the Google Calendar app: ' . print_r( $response, true ) ); } } } if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Failed to leave the Google Calendar app' ); } return false; } /** * Process the oauth redirect. * * @return void */ public function oauth_redirect() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'Permission denied!', 'woocommerce-bookings' ) ); } $redirect_args = array( 'page' => 'wc-settings', 'tab' => 'integration', 'section' => $this->id, ); // OAuth. if ( isset( $_GET['code'] ) ) { $code = sanitize_text_field( $_GET['code'] ); $access_token = $this->get_access_token( $code ); if ( '' != $access_token ) { $redirect_args['wc_gcalendar_oauth'] = 'success'; wp_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ), 301 ); exit; } } if ( isset( $_GET['error'] ) ) { $redirect_args['wc_gcalendar_oauth'] = 'fail'; wp_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ), 301 ); exit; } // Logout. if ( isset( $_GET['logout'] ) ) { $logout = $this->oauth_logout(); $redirect_args['wc_gcalendar_logout'] = ( $logout ) ? 'success' : 'fail'; wp_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ), 301 ); exit; } wp_die( __( 'Invalid request!', 'woocommerce-bookings' ) ); } /** * Display admin screen notices. * * @return string */ public function admin_notices() { $screen = get_current_screen(); if ( 'woocommerce_page_wc-settings' == $screen->id && isset( $_GET['wc_gcalendar_oauth'] ) ) { if ( 'success' == $_GET['wc_gcalendar_oauth'] ) { echo '<div class="updated fade"><p><strong>' . __( 'Google Calendar', 'woocommerce-bookings' ) . '</strong> ' . __( 'Account connected successfully!', 'woocommerce-bookings' ) . '</p></div>'; } else { echo '<div class="error fade"><p><strong>' . __( 'Google Calendar', 'woocommerce-bookings' ) . '</strong> ' . __( 'Failed to connect to your account, please try again, if the problem persists, turn on Debug Log option and see what is happening.', 'woocommerce-bookings' ) . '</p></div>'; } } if ( 'woocommerce_page_wc-settings' == $screen->id && isset( $_GET['wc_gcalendar_logout'] ) ) { if ( 'success' == $_GET['wc_gcalendar_logout'] ) { echo '<div class="updated fade"><p><strong>' . __( 'Google Calendar', 'woocommerce-bookings' ) . '</strong> ' . __( 'Account disconnected successfully!', 'woocommerce-bookings' ) . '</p></div>'; } else { echo '<div class="error fade"><p><strong>' . __( 'Google Calendar', 'woocommerce-bookings' ) . '</strong> ' . __( 'Failed to disconnect to your account, please try again, if the problem persists, turn on Debug Log option and see what is happening.', 'woocommerce-bookings' ) . '</p></div>'; } } } /** * Sync new Booking with Google Calendar. * * @param int $booking_id Booking ID * * @return void */ public function sync_new_booking( $booking_id ) { if ( $this->is_edited_from_meta_box() || 'wc_booking' !== get_post_type( $booking_id ) ) { return; } $this->sync_booking( $booking_id ); } /** * Check if Google Calendar settings are supplied. * * @return bool True is calendar is set, false otherwise. */ public function is_calendar_set() { return ! empty( $this->client_id ) && ! empty( $this->client_secret ) && ! empty( $this->calendar_id ); } /** * Sync an event resource with Google Calendar. * https://developers.google.com/google-apps/calendar/v3/reference/events * * @param int $booking_id Booking ID * @param array $params Set of parameters to be passed to the http request * @param array $data Optional set of data for writeable syncs * @since 1.11 * @version 1.11 * @return object|boolean Parsed JSON data from the http request or false if error */ public function sync_event_resource( $booking_id = -1, $params = array(), $data = array() ) { if ( $booking_id < 0 ) { return false; } $booking = get_wc_booking( $booking_id ); $event_id = $booking->get_google_calendar_event_id(); $api_url = $this->calendars_uri . $this->calendar_id . '/events' . ( ( $event_id ) ? '/' . $event_id : '' ); $json_data = false; if ( isset( $params['method'] ) && 'GET' !== $params['method'] ) { $params['body'] = json_encode( apply_filters( 'woocommerce_bookings_gcalendar_sync', $data, $booking ) ); } try { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Synchronizing booking #' . $booking->get_id() . ' with Google Calendar...' ); } $response = $this->make_gcal_request( $api_url, $params ); $json_data = json_decode( $response['body'], true ); } catch ( Exception $e ) { $json_data = false; if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error while getting data for ' . $api_url . ': ' . print_r( $response, true ) ); } } return $json_data; } /** * Sync Booking with Google Calendar. * * @param int $booking_id Booking ID */ public function sync_booking( $booking_id ) { if ( ! $this->is_calendar_set() || 'wc_booking' !== get_post_type( $booking_id ) ) { return; } // Booking data. $booking = get_wc_booking( $booking_id ); $event_id = $booking->get_google_calendar_event_id(); $product_id = $booking->get_product_id(); $order = $booking->get_order(); $product = wc_get_product( $product_id ); $resource = wc_booking_get_product_resource( $product_id, $booking->get_resource_id() ); $timezone = wc_booking_get_timezone_string(); $description = ''; $description_does_exist = false; $description_has_been_edited = false; $booking_data = array( __( 'Booking ID', 'woocommerce-bookings' ) => $booking_id, __( 'Booking Type', 'woocommerce-bookings' ) => is_object( $resource ) ? $resource->get_title() : '', __( 'Persons', 'woocommerce-bookings' ) => $booking->has_persons() ? array_sum( $booking->get_persons() ) : 0, ); foreach ( $booking_data as $key => $value ) { if ( empty( $value ) ) { continue; } $description .= sprintf( '%s: %s', rawurldecode( $key ), rawurldecode( $value ) ) . PHP_EOL; } if ( is_a( $order, 'WC_Order' ) ) { foreach ( $order->get_items() as $order_item ) { foreach ( $order_item->get_meta_data() as $order_meta_data ) { $the_meta_data = $order_meta_data->get_data(); if ( is_serialized( $the_meta_data['value'] ) ) { continue; } $description .= sprintf( '%s: %s', html_entity_decode( $the_meta_data['key'] ), html_entity_decode( $the_meta_data['value'] ) ) . PHP_EOL; } } } if ( $event_id ) { $response_data = $this->sync_event_resource( $booking_id, array( 'method' => 'GET', 'querystring' => array( 'fields' => 'description', ), ) ); $description_does_exist = isset( $response_data['description'] ) && ( '' !== trim( $response_data['description'] ) ); $description_has_been_edited = isset( $response_data['description'] ) && $response_data['description'] !== $description; // If the user edited the description on the Google Calendar side we want to keep that data intact. if ( $description_does_exist && $description_has_been_edited ) { $description = $response_data['description']; } } // Set the event data $data = array( 'summary' => wp_kses_post( '#' . $booking->get_id() . ' - ' . ( $product ? html_entity_decode( $product->get_title() ) : __( 'Booking', 'woocommerce-bookings' ) ) ), 'description' => wp_kses_post( $description ), ); // Set the event start and end dates if ( $booking->is_all_day() ) { // 1440 min = 24 hours. Bookings includes 'end' in its set of days, where as GCal uses that // as the cut off, so we need to add 24 hours to include our final 'end' day. // https://developers.google.com/google-apps/calendar/v3/reference/events/insert $data['end'] = array( 'date' => date( 'Y-m-d', ( $booking->get_end() + 1440 ) ), ); $data['start'] = array( 'date' => date( 'Y-m-d', $booking->get_start() ), ); } else { $data['end'] = array( 'dateTime' => date( 'Y-m-d\TH:i:s', $booking->get_end() ), 'timeZone' => $timezone, ); $data['start'] = array( 'dateTime' => date( 'Y-m-d\TH:i:s', $booking->get_start() ), 'timeZone' => $timezone, ); } $response_data = $this->sync_event_resource( $booking_id, array( 'method' => ( $event_id ) ? 'PUT' : 'POST', ), $data ); $booking->set_google_calendar_event_id( wc_clean( $response_data['id'] ) ); /** * Save booking also calls $booking->status_transition() in which * infinite loop could happens. * * @see https://github.com/woocommerce/woocommerce-bookings/pull/1048 */ $booking->skip_status_transition_events(); $booking->save(); } /** * Sync Booking with Google Calendar when booking is edited. * * @param int $booking_id Booking ID * * @return void */ public function sync_edited_booking( $booking_id ) { if ( ! $this->is_edited_from_meta_box() ) { return; } $this->maybe_sync_booking_from_status( $booking_id ); } /** * Sync Booking with Google Calendar when booking is untrashed. * * @param int $booking_id Booking ID * * @return void */ public function sync_untrashed_booking( $booking_id ) { $this->maybe_sync_booking_from_status( $booking_id ); } /** * Remove/cancel the booking in Google Calendar * * @param int $booking_id Booking ID * * @return void */ public function remove_booking( $booking_id ) { if ( 'wc_booking' !== get_post_type( $booking_id ) ) { return; } $booking = get_wc_booking( $booking_id ); $event_id = $booking->get_google_calendar_event_id(); if ( $event_id ) { $api_url = $this->calendars_uri . $this->calendar_id . '/events/' . $event_id; $access_token = $this->get_access_token(); $params = array( 'method' => 'DELETE', 'sslverify' => false, 'timeout' => 60, 'headers' => array( 'Authorization' => 'Bearer ' . $access_token, ), ); if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Removing booking #' . $booking->get_id() . ' with Google Calendar...' ); } $response = wp_remote_post( $api_url, $params ); if ( ! is_wp_error( $response ) && 204 == $response['response']['code'] ) { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Booking removed successfully!' ); } // Remove event ID $booking->set_google_calendar_event_id( '' ); $booking->save(); } else { if ( 'yes' === $this->debug ) { $this->log->add( $this->id, 'Error while removing the booking #' . $booking->get_id() . ': ' . print_r( $response, true ) ); } } } } /** * Maybe remove / sync booking based on booking status. * * @param int $booking_id Booking ID * * @return void */ public function maybe_sync_booking_from_status( $booking_id ) { global $wpdb; $status = $wpdb->get_var( $wpdb->prepare( "SELECT post_status FROM $wpdb->posts WHERE post_type = 'wc_booking' AND ID = %d", $booking_id ) ); if ( 'cancelled' == $status ) { $this->remove_booking( $booking_id ); } elseif ( in_array( $status, array( 'confirmed', 'paid', 'complete' ) ) ) { $this->sync_booking( $booking_id ); } } /** * Is edited from post.php's meta box. * * @return bool */ public function is_edited_from_meta_box() { return ( ! empty( $_POST['wc_bookings_details_meta_box_nonce'] ) && wp_verify_nonce( $_POST['wc_bookings_details_meta_box_nonce'], 'wc_bookings_details_meta_box' ) ); } }
[+]
..
[-] class-wc-bookings-addons.php
[edit]
[-] class-wc-bookings-google-calendar-integration.php
[edit]
[-] class-wc-bookings-deposits.php
[edit]