. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
| Server IP : 94.23.64.18 / Your IP :
216.73.216.185 [
Web Server : Apache System : Linux webm005.cluster107.gra.hosting.ovh.net 5.15.167-ovh-vps-grsec-zfs-classid #1 SMP Tue Sep 17 08:14:20 UTC 2024 x86_64 User : villadal ( 6036) PHP Version : 7.4.33 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl Domains : 2 Domains MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/villadal/www/old/wp-content/plugins/quitenicebooking/includes/ |
Upload File : |
<?php
/**
* Quitenicebooking main (controller) class
*
* @package quitenicebooking
* @author Quite Nice Stuff
* @copyright Copyright (c) 2013 Quite Nice Stuff
* @link http://quitenicestuff.com
* @version 2.5.9
* @since 2.0.0
*/
class Quitenicebooking {
/**
* Class properties ========================================================
*/
/**
* Accommodation custom post class
* @var Quitenicebooking_Accommodation_Post
*/
public $accommodation_post;
/**
* Booking custom post class
* @var Quitenicebooking_Booking_Post
*/
public $booking_post;
/**
* An array of settings from Quitenicebooking_Settings
* @var array
*/
public $settings;
/**
* An instance of the Quitenicebooking_Settings class
* @var Quitenicebooking_Settings
*/
public $settings_class;
/**
* Class methods ===========================================================
*/
/**
* Constructor
*
* Adds hooks to initialize the class
*/
public function __construct() {
// autoload classes when needed
spl_autoload_register( array( $this, 'autoload' ) );
// save activation errors
add_action( 'activated_plugin' , array( $this, 'save_error' ) );
// installation hook
register_activation_hook( QUITENICEBOOKING_MAIN_FILE, array( 'Quitenicebooking_Settings', 'activate' ) );
if ( get_option( 'quitenicebooking' ) === FALSE && get_option( 'qns_booking_booking1_url' ) !== FALSE) {
// an old installation exists; plugin needs to be deactivated and reactivated
add_action( 'admin_notices', array( 'Quitenicebooking_Settings', 'upgrade_notice_100' ) );
} elseif ( get_option( 'quitenicebooking' ) !== FALSE ) {
// continue to instantiate the class
// detect need for backup before upgrade
$get = filter_input_array( INPUT_GET );
if ( Quitenicebooking_Settings::recommend_backup() === FALSE || ( isset( $get['upgrade'] ) && $get['upgrade'] == '1' ) ) {
// upgrade hook
add_action( 'admin_init', array( 'Quitenicebooking_Settings', 'upgrade' ) );
// detect whether pages need to be created
add_action( 'admin_init', array( $this, 'install_pages' ) );
// sessions
add_action( 'init', array( $this, 'qns_session_start' ), 1 );
add_action( 'wp_logout', array( $this, 'qns_session_destroy' ) );
add_action( 'wp_login', array( $this, 'qns_session_destroy' ) );
// load languages
add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
// initialization hook
add_action( 'init', array( $this, 'init' ) );
// add settings link to admin
add_filter('plugin_action_links_'.plugin_basename(QUITENICEBOOKING_BASE), array($this, 'plugin_action_links'));
// initialize widget
add_action( 'widgets_init', array( $this, 'register_widget' ) );
// register ajax scripts
if ( is_admin() ) {
add_action( 'wp_ajax_quitenicebooking_ajax_check_availability', array( $this, 'ajax_check_availability' ) );
add_action( 'wp_ajax_nopriv_quitenicebooking_ajax_check_availability', array( $this, 'ajax_check_availability' ) );
add_action('wp_ajax_quitenicebooking_booking_status', array($this, 'ajax_set_booking_status'));
}
add_action('wp_ajax_quitenicebooking_ajax_calendar_availability', array($this, 'ajax_calendar_availability'));
add_action('wp_ajax_nopriv_quitenicebooking_ajax_calendar_availability', array($this, 'ajax_calendar_availability'));
add_action('wp_ajax_quitenicebooking_ajax_step_3_breakdown', array($this, 'ajax_step_3_breakdown'));
add_action('wp_ajax_nopriv_quitenicebooking_ajax_step_3_breakdown', array($this, 'ajax_step_3_breakdown'));
if (is_admin()) {
// additional notices
if (isset($get['pages_installed']) && $get['pages_installed'] == 1) {
add_action('admin_notices', array('Quitenicebooking_Settings', 'pages_installed_notice'));
}
add_action('admin_notices', array('Quitenicebooking_Settings', 'database_privilege_notice'));
// enqueue scripts
add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
}
// instantiate custom post types
$this->accommodation_post = new Quitenicebooking_Accommodation_Post();
$this->booking_post = new Quitenicebooking_Booking_Post();
// load premium plugins
$this->coupon_class = new Quitenicebooking_Coupon_Post();
$this->service_class = new Quitenicebooking_Service_Post();
}
}
}
/**
* Class autoloader. All class filenames should be lower case
*
* @param string $class
*/
public function autoload( $class ) {
$class = strtolower( $class );
if ( strpos( $class, 'quitenicebooking' ) !== FALSE
&& strpos( $class, 'quitenicebooking_premium' ) === FALSE ) {
include_once dirname( __FILE__ ) . '/' . $class . '.class.php';
}
}
/**
* Starts the PHP session
*/
public function qns_session_start() {
if ( ! session_id() ) {
session_start();
}
}
/**
* Destroys the PHP session
*/
public function qns_session_destroy() {
session_destroy();
}
/**
* Load language files
*/
public function plugins_loaded() {
// localization
load_plugin_textdomain( 'quitenicebooking', FALSE, dirname( plugin_basename( __FILE__ ) ) . '/../languages/' );
}
/**
* Initialization
*/
public function init() {
// instantiate settings class
$this->settings_class = new Quitenicebooking_Settings();
$this->settings_class->read_settings();
$this->settings = $this->settings_class->settings;
// pass settings to post types. they will init() after $this->init() finishes
$this->accommodation_post->settings = $this->settings;
$this->booking_post->settings = $this->settings;
$this->booking_post->accommodation_post = $this->accommodation_post;
$this->coupon_class->settings = $this->settings;
$this->service_class->settings = $this->settings;
$this->service_class->accommodations = $this->accommodation_post->get_all_accommodations(TRUE);
// add shortcodes
add_shortcode( 'booking_form', array( $this, 'booking_form_shortcode' ) );
add_shortcode( 'booking_step_1', array( $this, 'booking_step_1_shortcode' ) );
add_shortcode( 'booking_step_2', array( $this, 'booking_step_2_shortcode' ) );
add_shortcode( 'booking_step_3', array( $this, 'booking_step_3_shortcode' ) );
add_shortcode( 'booking_step_4', array( $this, 'booking_step_4_shortcode' ) );
add_shortcode( 'payment_fail', array( $this, 'payment_fail_shortcode' ) );
add_shortcode( 'payment_success', array( $this, 'payment_success_shortcode' ) );
add_shortcode( 'accommodation', array( $this, 'accommodation_shortcode' ) );
// legacy support for quitenicebooking 1.0
add_shortcode( 'booking_step1', array( $this, 'booking_step_1_shortcode' ) );
add_shortcode( 'booking_step2', array( $this, 'booking_step_2_shortcode' ) );
add_shortcode( 'booking_step3', array( $this, 'booking_step_3_shortcode' ) );
add_shortcode( 'booking_step4', array( $this, 'booking_step_4_shortcode' ) );
add_shortcode( 'booking_fail', array( $this, 'payment_fail_shortcode' ) );
add_shortcode( 'booking_success', array( $this, 'payment_success_shortcode' ) );
// enqueue scripts for front-end
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
// validation
add_action( 'template_redirect', array( $this, 'booking_steps_controller' ) );
// set image size for accommodation admin slideshow thumbnail
if ( function_exists( 'add_image_size' ) ) {
add_image_size( 'image1', 72, 72, TRUE );
}
// debugging shortcode
// add_shortcode( 'debug', array( $this, 'debug') );
}
/**
* Adds settings link to admin
*/
public function plugin_action_links($links) {
array_unshift($links, '<a href="http://themes.quitenicestuff.com/docs/sohohotelwp/">Docs</a>');
array_unshift($links, '<a href="'.admin_url('admin.php?page=quitenicebooking_settings').'">Settings</a>');
return $links;
}
/**
* Saves activation errors, if any, into options table
*/
public function save_error() {
update_option( 'quitenicebooking_error', ob_get_contents() );
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts() {
// jquery ui
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'jquery-ui-accordion' );
wp_enqueue_script( 'jquery-ui-tabs' );
wp_enqueue_script( 'jquery-effects-pulsate' );
// prettyphoto lightbox
wp_register_script( 'prettyphoto', QUITENICEBOOKING_URL . 'assets/js/jquery.prettyPhoto.js' );
wp_enqueue_script( 'prettyphoto' );
wp_register_style( 'prettyphoto', QUITENICEBOOKING_URL . 'assets/css/prettyPhoto.css' );
wp_enqueue_style( 'prettyphoto' );
// include datepicker translations from separate file
include dirname( __FILE__ ) . '/datepicker_translations.php';
// the script file used by booking steps 1-3
wp_register_script( 'quitenicebooking-settings', QUITENICEBOOKING_URL . 'assets/js/jquery-settings.js' );
wp_enqueue_script( 'quitenicebooking-settings' );
// determine whether to show the live calendar
$enable_calendar_availability = 0;
global $post;
if (isset($post->ID)) {
if ($post->ID == $this->settings['step_1_page_id']) {
$enable_calendar_availability = $this->settings['enable_live_calendar_step_1'];
} elseif (get_post_type() == 'accommodation') {
$enable_calendar_availability = $this->settings['enable_live_calendar_single'];
} else {
$enable_calendar_availability = $this->settings['enable_live_calendar_widget'];
}
}
wp_localize_script( 'quitenicebooking-settings', 'quitenicebooking', array(
'date_format' => $this->settings['date_format'],
'validationerror_requiredfield' => __( 'Please fill out the required fields marked with *', 'quitenicebooking' ),
'validationerror_email' => __( 'Please enter a valid email address', 'quitenicebooking' ),
'validationerror_paymentmethod' => __( 'Please select a payment method', 'quitenicebooking' ),
'validationerror_tos' => __( 'Please accept the terms and conditions', 'quitenicebooking' ),
'input_checkin' => __( 'Check In', 'quitenicebooking' ),
'input_checkout' => __( 'Check Out', 'quitenicebooking' ),
'alert_select_dates' => __( 'Please Select Booking Dates', 'quitenicebooking' ),
'alert_cannot_be_same_day' => __( 'Check In and Check Out Dates Cannot Be On The Same Day', 'quitenicebooking' ),
'alert_checkin_before_checkout' => __( 'Check In Date Must Be Before Check Out Date', 'quitenicebooking' ),
'alert_at_least_1_guest' => __( 'Please select at least 1 guest for Room', 'quitenicebooking' ),
'alert_at_least_1_adult' => __( 'Booking must have at least 1 adult', 'quitenicebooking' ),
'ajax_url' => admin_url( 'admin-ajax.php', isset( $_SERVER['HTTPS'] ) ? 'https://' : 'http://' ),
'plugin_url' => QUITENICEBOOKING_URL,
'enable_calendar_availability' => $enable_calendar_availability,
'minimum_stay' => $this->settings['minimum_stay'],
'alert_minimum_stay' => sprintf(__('Booking requires a minimum stay of %d nights', 'quitenicebooking'), $this->settings['minimum_stay']),
'currency_unit' => $this->settings['currency_unit'],
'currency_unit_suffix' => $this->settings['currency_unit_suffix'],
'currency_thousands_separator' => $this->settings['currency_thousands_separator'],
'currency_decimal_separator' => $this->settings['currency_decimal_separator'],
'currency_decimals' => $this->settings['currency_decimals'],
'deposit_type' => $this->settings['deposit_type'],
'deposit_percentage' => $this->settings['deposit_percentage']
));
}
/**
* Admin enqueue scripts
*/
public function admin_enqueue_scripts() {
global $pagenow;
$get = filter_input_array(INPUT_GET);
// forms tab
if ($pagenow == 'admin.php' && $get['page'] == 'quitenicebooking_settings' && (!empty($get['tab']) && $get['tab'] == 'forms')) {
wp_enqueue_style('quitenicebooking-admin-reservation-form', QUITENICEBOOKING_URL.'assets/css/admin/reservation_form.css');
wp_enqueue_script('quitenicebooking-admin-reservation-form', QUITENICEBOOKING_URL.'assets/js/admin/reservation_form.js');
}
// maintenance tab
if ($pagenow == 'admin.php' && (!empty($get['tab']) && $get['tab'] == 'maintenance')) {
wp_enqueue_style('quitenicebooking-maintenance', QUITENICEBOOKING_URL.'assets/css/admin/maintenance.css');
wp_register_script('quitenicebooking-maintenance', QUITENICEBOOKING_URL . 'assets/js/admin/maintenance.js');
wp_enqueue_script('quitenicebooking-maintenance');
wp_localize_script('quitenicebooking-maintenance', 'quitenicebooking', array(
'ajax_url' => admin_url('admin-ajax.php', isset($_SERVER['HTTPS']) ? 'https://' : 'http://')
));
}
}
/**
* Detects whether pages are installed
* Prompt if they don't already exist
* Install if argument supplied
*/
public function install_pages() {
// re-read the settings in case the upgrade procedure was automatically run
$this->settings_class->read_settings();
$this->settings = $this->settings_class->settings;
// detect pages
if ( empty( $this->settings['step_1_page_id'] ) && empty( $this->settings['step_2_page_id'] )
&& empty ( $this->settings['step_3_page_id'] ) && empty( $this->settings['step_4_page_id'] )
&& empty ( $this->settings['payment_success_page_id'] ) && empty( $this->settings['payment_fail_page_id'] )
&& empty ( $this->settings['accommodation_page_id'] ) ) {
// check whether to ignore the prompt
Quitenicebooking_Settings::ignore_install_pages_notice();
$current_user = wp_get_current_user();
if ( ! get_user_meta( $current_user->ID, 'quitenicebooking_ignore_install_pages_notice', TRUE ) ) {
// show prompt
add_action( 'admin_notices', array( 'Quitenicebooking_Settings', 'install_pages_notice' ) );
}
// else ignore prompt
// check whether to install pages
Quitenicebooking_Settings::install_pages();
}
}
/**
* Widgets =================================================================
*/
/**
* Registers the reservation form widget
*/
public function register_widget() {
include QUITENICEBOOKING_PATH . 'includes/quitenicebooking_reservation_form_widget.class.php';
register_widget( 'Quitenicebooking_Reservation_Form_Widget' );
}
/**
* Redirection controller ==================================================
*/
/**
* Handles the incoming request
* Redirects it to the appropriate validation and processing functions
*/
public function booking_steps_controller() {
if ( filter_input( INPUT_POST, 'booking_step_1_submit') !== NULL ) {
$this->booking_step_1_validation( filter_input_array( INPUT_POST ) );
} elseif ( filter_input( INPUT_POST, 'booking_step_2_submit') !== NULL ) {
$this->booking_step_2_validation( filter_input_array( INPUT_POST ) );
} elseif ( filter_input( INPUT_POST, 'booking_step_3_submit') !== NULL ) {
$this->booking_step_3_validation( filter_input_array( INPUT_POST ) );
} elseif (filter_input(INPUT_POST, 'apply_coupon') !== NULL) {
$this->booking_step_3_validation(filter_input_array(INPUT_POST));
} elseif (filter_input(INPUT_POST, 'remove_coupon') !== NULL) {
$this->booking_step_3_validation(filter_input_array(INPUT_POST));
}
}
/**
* Shortcodes ==============================================================
*/
/**
* The booking form shortcode
*
* Shows the form on the homepage (used by Soho Hotel's template-homepage-*.php templates), and submits to step_1
*/
public function booking_form_shortcode() {
return $this->view( 'views/booking_form.htm.php', $this->settings );
}
/**
* Step 1 shortcode
*/
public function booking_step_1_shortcode() {
$booking = array();
$errors = array();
if ( isset( $_SESSION['booking'] ) ) {
$booking = $_SESSION['booking'];
}
if ( isset( $_SESSION['errors'] ) ) {
$errors = $_SESSION['errors'];
}
if (!empty($_GET['highlight'])) {
$booking['highlight'] = $_GET['highlight'];
} else {
if (!empty($booking['highlight'])) {
unset($booking['highlight']);
}
}
$calendar_availability = new stdClass();
if (!empty($this->settings['enable_live_calendar_step_1'])) {
$calendar_availability = $this->preload_calendar_availability(date('Y'), date('n'), array(-1, 0, 1, 2, 3, 4, 5), !empty($_GET['highlight']) ? $_GET['highlight'] : FALSE);
}
return $this->view( 'views/booking_step_1.htm.php', array_merge(
$this->settings, $booking, array('errors' => $errors), array('calendar_availability' => $calendar_availability)
) );
}
/**
* Step 2 shortcode
*/
public function booking_step_2_shortcode() {
$data = array();
$errors = array();
if ( isset ( $_SESSION['data'] ) ) {
$data = $_SESSION['data'];
unset( $_SESSION['data'] );
}
if ( isset( $_SESSION['errors'] ) ) {
$errors = $_SESSION['errors'];
unset ( $_SESSION['errors'] );
}
// handle GET here
// if $data is empty, load error page to redirect to step 1
if ( count($data) == 0 ) {
$data['errors'] = array( __( 'Please do not reload the page. You will be redirected to the first booking step momentarily.', 'quitenicebooking' ) );
$data['redirect'] = $this->settings['step_1_url'];
return $this->view( 'views/error.htm.php', $data );
} else {
$data['beds'] = new Quitenicebooking_Beds();
return $this->view( 'views/booking_step_2.htm.php', array_merge(
$this->settings, $data, array('errors' => $errors)
) );
}
}
/**
* Step 3 shortcode
*/
public function booking_step_3_shortcode() {
$data = array();
$post = array(); // containing customer posted data
$errors = array();
if ( isset( $_SESSION['data'] ) ) {
$data = $_SESSION['data'];
unset( $_SESSION['data'] );
}
if ( isset( $_SESSION['post'] ) ) {
$post = $_SESSION['post'];
unset( $_SESSION['post'] );
}
if ( isset( $_SESSION['errors'] ) ) {
$errors = $_SESSION['errors'];
if (isset($data['errors'])) {
$errors = array_merge($errors, $data['errors']);
}
unset ( $_SESSION['errors'] );
}
// handle GET here
if ( ! isset( $_SESSION['booking'] ) || count( $_SESSION['booking'] ) == 0 || ! isset( $_SESSION['booking']['room_qty'] ) ) {
$data['errors'] = array( __( 'Please do not reload the page. You will be redirected to the first booking step momentarily.', 'quitenicebooking' ) );
$data['redirect'] = $this->settings['step_1_url'];
return $this->view( 'views/error.htm.php', $data );
} elseif ( count($data) == 0 ) {
// if $data is empty, load error page and redirect to step 1
$data['errors'] = array( __( 'Please do not reload the page. You will be redirected to the first booking step momentarily.', 'quitenicebooking' ) );
$data['redirect'] = $this->settings['step_1_url'];
return $this->view( 'views/error.htm.php', $data );
} else {
if (!empty($this->settings['enable_coupons'])) {
$data['coupons_enabled'] = TRUE;
}
$data['form_fields'] = Quitenicebooking_Utilities::decode_reservation_form($this->settings);
// get services
$service_rooms = array();
$booking = $_SESSION['booking'];
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
$m = array();
preg_match('/type=(.+)&bed=(.+)/', $booking['room_'.$n.'_selection'], $m);
$service_rooms[] = $m[1];
}
$service_rooms = array_unique($service_rooms);
$data['service_choices'] = $this->service_class->get_services($service_rooms);
return $this->view( 'views/booking_step_3.htm.php', array_merge(
$this->settings, $data, $post, array('errors' => $errors)
) );
}
}
/**
* Step 4 shortcode
*/
public function booking_step_4_shortcode() {
$data = array();
if ( isset( $_SESSION['data'] ) ) {
$data = $_SESSION['data'];
unset( $_SESSION['data'] );
}
// handle GET here
if ( ! isset( $_SESSION['booking'] ) || count( $_SESSION['booking'] ) == 0 || ! isset( $_SESSION['booking']['room_qty'] ) ) {
$data['errors'] = array( __( 'Please do not reload the page. You will be redirected to the first booking step momentarily.', 'quitenicebooking' ) );
$data['redirect'] = $this->settings['step_1_url'];
return $this->view( 'views/error.htm.php', $data );
} elseif ( count($data) == 0 ) {
// if $data is empty, load error page and redirect to step 1
$data['errors'] = array(
__( 'Please do not reload the page. If you have already made a reservation, your reservation details have been emailed to you.', 'quitenicebooking' )
);
return $this->view( 'views/error.htm.php', $data );
} else {
// destroy the session
session_unset();
// check if there is a redirect to paypal
if ( isset( $data['redirect'] ) ) {
return $this->view( 'views/booking_step_4_redirect.htm.php', array_merge(
$this->settings, $data )
);
}
else {
return $this->view( 'views/booking_step_4.htm.php', array_merge(
$this->settings, $data )
);
}
}
}
/**
* Payment success shortcode
*
* Query string must include parameters 'booking_id' and 'key'
* @global wpdb $wpdb The wpdb object
*/
public function payment_success_shortcode() {
global $wpdb;
$get = filter_input_array( INPUT_GET );
// verify arguments are set
if (!isset($get['booking_id']) || !isset($get['key'])) {
$data['errors'] = array($this->settings['payment_success_message']);
return $this->view('views/payment_success_error.htm.php', array_merge($this->settings, $data));
}
// get the booking id and hash
$booking_id = $get['booking_id'];
$hash = $get['key'];
// pull the booking info out of the database
$query = new WP_Query(array(
'post_type' => 'booking',
'p' => $booking_id
));
// use wpdb to override WPML language
$query = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $wpdb->posts WHERE post_type = %s AND ID = %d",
'booking',
$booking_id
));
$query = (array) $query[0];
if (count($query) > 0) {
$post_meta = get_post_meta($booking_id);
foreach ($post_meta as &$p) {
$p = $p[0];
}
unset($p);
if ($hash != substr(sha1('quitenicebooking'.$post_meta['quitenicebooking_guest_last_name'].$post_meta['quitenicebooking_guest_first_name']), 0, 6)) {
$data['errors'] = array($this->settings['payment_success_message']);
}
$data['deposit'] = $post_meta['quitenicebooking_deposit_amount'];
$data['total'] = $post_meta['quitenicebooking_total_price'];
$booked_rooms = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}qns_bookings WHERE post_id = $booking_id", ARRAY_A);
$num_rooms = $booked_rooms == NULL ? 0 : count($booked_rooms);
$data['total_rooms'] = $num_rooms;
$data['summary'] = array();
$data['breakdown'] = array();
$all_rooms = $this->accommodation_post->get_all_accommodations(TRUE);
for ($n = 1; $n <= $num_rooms; $n ++) {
$data['summary'][$n]['type'] = $all_rooms[$booked_rooms[$n-1]['type']]['title'];
$data['summary'][$n]['checkin'] = substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], strtotime($booked_rooms[$n-1]['checkin'])), 0, -9);
$data['summary'][$n]['checkout'] = substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], strtotime($booked_rooms[$n-1]['checkout'])), 0, -9);
$data['summary'][$n]['guests'] = '';
$data['summary'][$n]['guests'] .= !empty($booked_rooms[$n-1]['adults']) ? $booked_rooms[$n-1]['adults'].' '._n('Adult', 'Adults', $booked_rooms[$n-1]['adults'], 'quitenicebooking').', ' : '';
$data['summary'][$n]['guests'] .= !empty($booked_rooms[$n-1]['children']) ? $booked_rooms[$n-1]['children'].' '._n('Child', 'Children', $booked_rooms[$n-1]['children'], 'quitenicebooking').', ' : '';
$data['summary'][$n]['guests'] = substr($data['summary'][$n]['guests'], 0, -2);
$data['breakdown'][] = $this->make_price_breakdown(
$all_rooms[$booked_rooms[$n-1]['type']],
array(
'checkin' => substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], strtotime($booked_rooms[$n-1]['checkin'])), 0, -9),
'checkout' => substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], strtotime($booked_rooms[$n-1]['checkout'])), 0, -9),
'adults' => $booked_rooms[$n-1]['adults'],
'children' => $booked_rooms[$n-1]['children'],
'total_guests' => $booked_rooms[$n-1]['adults'] + $booked_rooms[$n-1]['children']
)
);
}
// put all the coupons in an array
if (isset($this->coupon_class) && is_object($this->coupon_class)) {
if (!empty($post_meta['quitenicebooking_coupon_codes'])) {
$coupons = explode(', ', $post_meta['quitenicebooking_coupon_codes']);
foreach ($coupons as $p) {
$applied_coupons[] = $p;
}
}
}
$discount_amt = 0;
// apply the coupons and collect errors for those that cannot be applied because of requirement checks
if (isset($applied_coupons)) {
// calculate total
$total = 0;
foreach ($data['breakdown'] as $b) {
$total += $b['total'];
}
// iterate through the breakdown and apply the discount
$discounts = array();
$coupon_errors = array();
$discount_amt = $this->coupon_class->apply_discount($applied_coupons, $data['breakdown'], $total, $discounts, $coupon_errors);
$data['discounts'] = $discounts;
// $data['errors'] = $coupon_errors;
$data['applied_coupons'] = $applied_coupons;
}
// apply the services
if (!empty($post_meta['quitenicebooking_services'])) {
$data['services'] = json_decode($post_meta['quitenicebooking_services'], TRUE);
}
// calculate tax
$total_float = Quitenicebooking_Utilities::user_price_to_float(str_replace($this->settings['currency_thousands_separator'], '', $post_meta['quitenicebooking_total_price']), $this->settings);
echo 'total_float =>'.$total_float;
$data['tax'] = !empty($this->settings['tax_percentage']) ? round($total_float - ($total_float / (1 + ($this->settings['tax_percentage'] / 100))), $this->settings['currency_decimals']) : 0;
} else {
$data['errors'] = array($this->settings['payment_success_message']);
}
if (isset($data['errors'])) {
return $this->view('views/payment_success_error.htm.php', array_merge($this->settings, $data));
} else {
return $this->view('views/payment_success.htm.php', array_merge($this->settings, $data));
}
}
/**
* Payment fail shortcode
*/
public function payment_fail_shortcode() {
return $this->view('views/payment_fail.htm.php', $this->settings );
}
/**
* Accommodation shortcode
*/
public function accommodation_shortcode() {
$rooms = array();
// setup the query here and collect the data into $rooms
$query = new WP_Query(array(
'post_type' => 'accommodation',
'posts_per_page' => $this->settings['rooms_per_page'],
'paged' => get_query_var('paged') ? get_query_var('paged') : 1,
'order' => isset($this->settings['rooms_order']) ? ($this->settings['rooms_order'] == 'newest' ? 'DESC' : 'ASC') : 'DESC',
));
$pagedata['max_num_pages'] = $query->max_num_pages;
$pagedata['next_posts_link'] = get_next_posts_link(__('More rooms →', 'quitenicebooking'), $query->max_num_pages);
$pagedata['previous_posts_link'] = get_previous_posts_link(__('← More rooms', 'quitenicebooking'));
$pagedata['query'] = $query; // for wp_pagenavi
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
// header: get the title, title attribute, post thumbnail, permalink
$room['id'] = $query->post->ID;
$room['title'] = get_the_title();
$room['title_attribute'] = the_title_attribute(array('echo' => FALSE));
$room['post_thumbnail'] = wp_get_attachment_image_src( get_post_thumbnail_id($query->post->ID), 'image-style3');
$room['post_thumbnail'] = $room['post_thumbnail'][0];
$room['permalink'] = get_permalink();
// content: get the types of beds, max occupancy, room size, view, lowest of the adult weekday/weekend/room price
$post_meta = get_post_meta($query->post->ID);
// extract values out of arrays
foreach ($post_meta as &$p) {
$p = $p[0];
}
$beds = new Quitenicebooking_Beds();
foreach ($beds->keys['beds'] as $bed => $def) {
$room['beds_available'][$bed]['available'] = $post_meta[$def['meta_key']] > 0 ? TRUE : FALSE;
$room['beds_available'][$bed]['description'] = $def['description'];
}
$room['max_occupancy'] = $post_meta['quitenicebooking_max_occupancy'];
$room['room_size'] = $post_meta['quitenicebooking_room_size'];
$room['room_view'] = $post_meta['quitenicebooking_room_view'];
$room['num_bedrooms'] = $post_meta['quitenicebooking_num_bedrooms'];
$room['base_price'] = $this->accommodation_post->get_lowest_price($query->post->ID);
// add to $rooms
$rooms[] = $room;
unset($room);
}
} else {
$pagedata['errors'][] = __('No rooms have been added yet', 'quitenicebooking');
}
// pass the query data into the view
return $this->view('views/accommodation.htm.php', array_merge(array('rooms' => $rooms), $this->settings, $pagedata));
}
/**
* Validation ==============================================================
*/
/**
* Step 1 validation
*
* Note: The validation method also performs redirection
* @param array $post The $_POST array
*/
public function booking_step_1_validation( $post ) {
// array to collect errors
$errors = array();
// validate all required form fields to make sure they exist
if ( ! isset( $post['room_all_checkin'] )
|| ! isset( $post['room_all_checkout'] )
|| ! isset( $post['room_qty'] )
|| ! isset( $post['room_1_adults'] )
|| ! isset( $post['room_1_children'] ) ) {
$errors[] = __('Please fill in all the required fields', 'quitenicebooking');
}
// validate date
// check for no values, default values, or invalid values
// the above regex will cover all cases since it's looking for a specific date format
if ( ! preg_match( $this->settings['date_format_strings'][ $this->settings['date_format'] ]['regex'], $post['room_all_checkin'] )
|| ! preg_match( $this->settings['date_format_strings'][ $this->settings['date_format'] ]['regex'], $post['room_all_checkout'] ) ) {
$errors[] = sprintf( __( 'Select booking dates. They must be in format %s', 'quitenicebooking' ), $this->settings['date_format_strings'][ $this->settings['date_format'] ]['display'] );
} else {
// check whether the checkout is the same as, or before the checkin
// convert the times to unix and compare them
$room_all_checkin = Quitenicebooking_Utilities::to_timestamp($post['room_all_checkin'], $this->settings);
$room_all_checkout = Quitenicebooking_Utilities::to_timestamp($post['room_all_checkout'], $this->settings);
if ( $room_all_checkin >= $room_all_checkout ) {
$errors[] = __( 'Check out date must be after check in date', 'quitenicebooking' );
} elseif (($room_all_checkout - $room_all_checkin) < $this->settings['minimum_stay'] * (60*60*24)) {
$errors[] = sprintf(__('Booking requires a minimum stay of %d nights', 'quitenicebooking'), $this->settings['minimum_stay']);
}
}
// validate rooms
// there must be at least 1 room
if ( $post['room_qty'] < 1 ) {
$errors[] = __( 'Select at least 1 room for this booking', 'quitenicebooking' );
} else {
// validate guests
// each room must have at least 1 guest
for ( $r = 1; $r <= $post['room_qty']; $r++ ) {
if ( $post['room_'.$r.'_adults'] + $post['room_'.$r.'_children'] < 1 ) {
$errors[] = sprintf( __( 'Select at least 1 guest for Room %d', 'quitenicebooking' ), $r );
}
}
// entire booking must have at least 1 adult
$total_adults = 0;
for ( $r = 1; $r <= $post['room_qty']; $r++ ) {
$total_adults += $post['room_'.$r.'_adults'];
}
if ($total_adults < 1) {
$errors[] = __('Booking must have at least 1 adult', 'quitenicebooking');
}
}
// store the booking data into the session
$_SESSION['booking'] = $post;
// redirect to the appropriate page
if ( count( $errors ) > 0 ) {
$_SESSION['errors'] = $errors;
wp_redirect( $this->settings['step_1_url'] ); exit;
} else {
$this->booking_step_2_processing( $post );
}
}
/**
* Step 2 validation
* @param array $post The filtered $_POST superglobal
*/
public function booking_step_2_validation( $post ) {
// array to collect errors
$errors = array();
// determine which form is being submitted -- "edit reservation" or "select bed/room"
// first, collect all $post keys into a string
$post_keys = implode(' ', array_keys($post));
// do a regex search
$m = array();
if ( preg_match( '/room_(\d+)_selection/', $post_keys, $m ) ) {
// "select bed/room" being submitted
// from "select bed/room" form: type=<id>&bed=<bed>
$n = $m[1];
$type = $post['room_'.$n.'_selection'];
$t = array();
if ( preg_match('/type=(.+)&bed=(.+)/', $post['room_'.$n.'_selection'], $t) ) {
// validate id exists and is of type 'accommodation'
if ( ! empty( $t[1] ) && get_post_type( $t[1] ) != 'accommodation' ) {
$errors[] = __( 'Invalid room selected', 'quitenicebooking' );
}
if ( ! empty( $t[2] ) ) {
// validate bed exists and is one of the defined, or 0 if room. don't allow any other values
$found = FALSE;
$beds = new Quitenicebooking_Beds();
foreach (array_keys($beds->keys['beds']) as $bed ) {
if ( $t[2] == $bed ) {
$found = TRUE;
break;
}
}
if ( $t[2] == '0' ) {
// room selected
$found = TRUE;
}
if ( ! $found ) {
$errors[] = __( 'Invalid bed/room selected', 'quitenicebooking' );
}
}
} else {
// match failed, or editing previous selection
if ( $post['room_'.$n.'_selection'] != '' ) {
$errors[] = __( 'Invalid bed/room selection', 'quitenicebooking' );
}
// else, empty selection is valid, so don't do anything
}
} else {
if ( preg_match( '/room_(\d+)_checkin/', $post_keys, $m ) ) {
// "edit reservation" being submitted
// $m[1] contains the substep (room) number, move it out to $n
$n = $m[1];
// validate required fields
if ( ! isset( $post['room_'.$n.'_checkin'] )
|| ! isset( $post['room_'.$n.'_checkout'] )
|| ! isset( $post['room_'.$n.'_adults'] )
|| ! isset( $post['room_'.$n.'_children'] ) ) {
$errors[] = __('Please fill in all the required fields', 'quitenicebooking');
}
// validate dates
// check for no values, default values, or invalid values
if ( ! preg_match( $this->settings['date_format_strings'][ $this->settings['date_format'] ]['regex'], $post['room_'.$n.'_checkin'] )
|| ! preg_match( $this->settings['date_format_strings'][ $this->settings['date_format'] ]['regex'], $post['room_'.$n.'_checkout'] ) ) {
$errors[] = __( 'Please fill in all the required fields', 'quitenicebooking' );
} else {
// check whether checkout is the same as, or before the checkin
// convert times to unix and compare them
$checkin = Quitenicebooking_Utilities::to_timestamp($post['room_'.$n.'_checkin'], $this->settings);
$checkout = Quitenicebooking_Utilities::to_timestamp($post['room_'.$n.'_checkout'], $this->settings);
if ( $checkin >= $checkout ) {
$errors[] = __( 'Check out date must be after check in date', 'quitenicebooking' );
} elseif (($checkout - $checkin) < $this->settings['minimum_stay'] * (60*60*24)) {
$errors[] = sprintf(__('Booking requires a minimum stay of %d nights', 'quitenicebooking'), $this->settings['minimum_stay']);
}
}
// validate number of guests
// adults + children >= 1
if ( $post['room_'.$n.'_adults'] + $post['room_'.$n.'_children'] < 1 ) {
$errors[] = __( 'Room must have at least 1 guest', 'quitenicebooking' );
}
} else {
// case where checkin is missing, or submission is simply unknown
$errors[] = __( 'Please fill in all the required fields', 'quitenicebooking' );
}
} // end validation checks
// if there were errors, put the errors array into the session
// don't touch the $_SESSION booking data
// don't do redirection here. have the step_2_processing detect the presence of errors; if errors exist, ignore post
if (count($errors) > 0) {
$_SESSION['errors'] = $errors;
}
$this->booking_step_2_processing( $post );
}
/**
* Step 3 validation
* @param array $post The filtered $_POST superglobal
*/
public function booking_step_3_validation( $post ) {
// array to collect errors
$errors = array();
foreach ( $post as $key => &$val ) {
if (is_string($val)) {
$val = trim($val);
}
}
unset( $val );
$missing_required_fields = FALSE;
// skip form validation if coupon codes being applied
if (!(isset($this->coupon_class) && is_object($this->coupon_class) && (isset($post['apply_coupon']) || isset($post['remove_coupon'])))) {
// validate all required form fields to make sure they exist
if (empty($post['guest_first_name'])
|| empty($post['guest_last_name'])
|| empty($post['guest_email']) ) {
$missing_required_fields = TRUE;
}
$form_fields = Quitenicebooking_Utilities::decode_reservation_form($this->settings);
foreach ($form_fields as $field) {
if (!empty($field['required']) && empty($post['guest_details_'.$field['id']])) {
$missing_required_fields = TRUE;
}
}
if ($missing_required_fields) {
$errors[] = __( 'Please fill in all the required fields', 'quitenicebooking' );
}
// validate email address
if ( ! is_email( $post['guest_email'] ) ) {
$errors[] = __( 'Please enter a valid email address', 'quitenicebooking' );
}
// validate a payment method has been chosen, if enabled
if ( !empty($this->settings['deposit_type']) && ( ( !empty($this->settings['accept_paypal']) && !empty($this->settings['paypal_email_address']) ) || !empty($this->settings['accept_bank_transfer']) ) ) {
if ( ! isset( $post['payment_method'] ) || $post['payment_method'] != 'paypal' && $post['payment_method'] != 'bank_transfer' ) {
$errors[] = __( 'Please select a payment method', 'quitenicebooking' );
}
}
}
// validate coupon code
if (isset($this->coupon_class) && is_object($this->coupon_class)) {
// collect all coupons in an array to check for duplicates
$coupons = array();
// iterate through the posted coupons
foreach ($post as $p => $v) {
if (preg_match('/^coupon_code_\d+/', $p) && !empty($v)) {
if (!empty($post['remove_coupon']) && $post['remove_coupon'] == $p) {
// remove any coupons
unset($post[$p]);
} elseif (!$this->coupon_class->validate_coupon($v)) {
$errors[] = __('The coupon code is not valid', 'quitenicebooking');
unset($post[$p]);
}
// check for duplicates
if (in_array($v, $coupons)) {
$errors[] = __('Coupon may only be used once per booking', 'quitenicebooking');
unset($post[$p]);
}
$coupons[] = $v;
}
}
}
// if validation fails, go to step 3
// if validation fails, or a coupon code is posted, go to step 3
if (count($errors) > 0 || isset($post['apply_coupon']) || isset($post['remove_coupon'])) {
$_SESSION['errors'] = $errors;
$_SESSION['post'] = $post;
$this->booking_step_3_processing($post);
} else {
// if validation succeeds, go to step 4
// merge the booking with post
$_SESSION['booking'] = array_merge(isset($_SESSION['booking']) ? $_SESSION['booking'] : array(), $post);
$this->booking_step_4_processing();
}
}
/**
* Processing ==============================================================
*/
/**
* Step 2 processing
* @param array $post The filtered $_POST superglobal
*/
public function booking_step_2_processing( $post ) {
// 1.
// if the request came from step 1, clear the session data
if (isset($post['booking_step_1_submit'])) {
session_unset();
unset($post['booking_step_1_submit']);
}
// 2A.
// check if errors exist. don't touch $post if errors exist
if (isset($_SESSION['errors']) && count($_SESSION['errors']) > 0) {
$booking = isset($_SESSION['booking']) ? $_SESSION['booking'] : array();
} else {
// 2.
// make an array that contains the accumulated booking request by
// retrieving any booking data from $_SESSION and merging it with $_REQUEST, overwriting $_SESSION
$booking = array_merge(isset($_SESSION['booking']) ? $_SESSION['booking'] : array(), $post);
}
// 3.
// initialization from step 1
// if $room_all_checkin and $room_all_checkout are set, assign them to all the rooms
// but, do not overwrite if they don't already exist
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
if (isset($booking['room_all_checkin'])) {
if (!isset($booking['room_'.$n.'_checkin'])) {
$booking['room_'.$n.'_checkin'] = $booking['room_all_checkin'];
}
}
if (isset($booking['room_all_checkout'])) {
if (!isset($booking['room_'.$n.'_checkout'])) {
$booking['room_'.$n.'_checkout'] = $booking['room_all_checkout'];
}
}
}
// 4.
// initialization from step 1, continued
// prune room_n_adults and room_n_children to room_qty
for ($n = $booking['room_qty'] + 1; $n <= $this->settings['max_rooms']; $n ++) {
unset($booking['room_'.$n.'_adults']);
unset($booking['room_'.$n.'_children']);
}
// 5.
// now check how many rooms have been requested, and how many are done
// put the rooms that need to be selected in $pending
// put the rooms that have been booked in $live_bookings and $selected
$pending = array();
$selected = array();
$live_bookings = array();
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
if (!isset($booking['room_'.$n.'_selection']) || empty($booking['room_'.$n.'_selection'])) {
$pending[] = $n;
} else {
$selected[] = $n;
$m = array();
// capture type into $m[1], bed into $m[2]
// if selecting a room with no beds, $m[2] will be 0
preg_match('/type=(.+)&bed=(.+)/', $booking['room_'.$n.'_selection'], $m);
$live_bookings[$n]['checkin'] = $booking['room_'.$n.'_checkin'];
$live_bookings[$n]['checkout'] = $booking['room_'.$n.'_checkout'];
$live_bookings[$n]['type'] = $m[1];
$live_bookings[$n]['bed'] = $m[2];
$live_bookings[$n]['adults'] = $booking['room_'.$n.'_adults'];
$live_bookings[$n]['children'] = $booking['room_'.$n.'_children'];
}
}
// 6.A.
// if there are no pending rooms left, go to step 3
if (count($pending) == 0) {
$_SESSION['booking'] = $booking;
$this->booking_step_3_processing($post);
// issue exit from step 3's redirect
}
// 6.
// now check for availabilty for the first pending room
$current_room['checkin'] = $booking['room_'.$pending[0].'_checkin'];
$current_room['checkout'] = $booking['room_'.$pending[0].'_checkout'];
$current_room['adults'] = $booking['room_'.$pending[0].'_adults'];
$current_room['children'] = $booking['room_'.$pending[0].'_children'];
$current_room['total_guests'] = $current_room['adults'] + $current_room['children'];
$available_rooms = $this->booking_step_2_find_rooms($current_room['checkin'], $current_room['checkout'], $current_room['total_guests'], $live_bookings);
// 7.
// calculate price breakdown for each available room
foreach ($available_rooms as &$available_room) {
$available_room['price_breakdown'] = $this->make_price_breakdown($available_room, $current_room);
}
unset($available_room);
// 8.
// prepare a form to show available rooms for the first pending room
$data['current_substep'] = $booking['room_qty'] - count($pending) + 1;
$data['total_substeps'] = $booking['room_qty'];
$data['room_number'] = count($pending) > 0 ? $pending[0] : NULL;
$data['prev_room_number'] = count($selected) > 0 ? $selected[count($selected) - 1] : NULL;
$data['room_'.$pending[0].'_checkin'] = $booking['room_'.$pending[0].'_checkin'];
$data['room_'.$pending[0].'_checkout'] = $booking['room_'.$pending[0].'_checkout'];
$data['room_'.$pending[0].'_adults'] = $booking['room_'.$pending[0].'_adults'];
$data['room_'.$pending[0].'_children'] = $booking['room_'.$pending[0].'_children'];
$data['available_rooms'] = $available_rooms;
if ( isset( $booking['highlight'] ) && ! empty( $booking['highlight'] ) ) {
// 8.A.
// get the highlighted room, regardless of availability
$data['highlight_room'] = $this->accommodation_post->get_single_accommodation( $booking['highlight'] );
if ( count( $data['highlight_room'] ) > 0 ) {
// allow the room to be displayed only if found/valid
$data['highlight_room'][$booking['highlight']]['lowest_price'] = $this->accommodation_post->get_lowest_price($booking['highlight']);
$data['highlight'] = $booking['highlight'];
}
}
// 9.
// update the booking in the $_SESSION
$_SESSION['booking'] = $booking;
// 10.1.
// calculate lowest price for each room
foreach ($data['available_rooms'] as &$a) {
$a['lowest_price'] = $this->accommodation_post->get_lowest_price($a['id']);
}
unset($a);
// 10.
// store the data into $_SESSION and redirect/GET step 2 url
$_SESSION['data'] = $data;
wp_redirect( $this->settings['step_2_url'] ); exit;
}
/**
* Step 2 processing -- find rooms
*
* @global $wpdb The wpdb object
* @param $checkin string The checkin date
* @param $checkout string The checkout date
* @param $totalguests int The total number of guests
* @param array $live_bookings An array of live bookings:
* [ 0 => // int key
* ['checkin' => // string The checkin date
* 'checkout' => // string The checkout date
* 'type' => // string The room type
* 'bed' => // string The bed type
* 'adults' => // int Number of adults
* 'children' => // int Number of children
* ]
* ]
* @return array An array of available rooms
*/
public function booking_step_2_find_rooms($checkin, $checkout, $total_guests, $live_bookings) {
global $wpdb;
// 1.
// create a pivot table of the existing bookings
$this->booking_post->create_live_bookings_table();
// 2.
// for each of the live bookings made, insert them into the pivot table
for ($l = 1; $l <= count($live_bookings); $l ++) {
// convert dates to unix time
$live_bookings[$l]['checkin'] = Quitenicebooking_Utilities::to_timestamp($live_bookings[$l]['checkin'], $this->settings);
$live_bookings[$l]['checkout'] = Quitenicebooking_Utilities::to_timestamp($live_bookings[$l]['checkout'], $this->settings);
$this->booking_post->add_live_booking($live_bookings[$l]);
}
// 3.
// bring all the accomodations into all_rooms
// it's needed as an argument to the collision summary table
// all_rooms is also needed later when the collisions are subtracted from it
$all_rooms = $this->accommodation_post->get_all_accommodations();
// 4.
// get the collision summary table
// convert all dates to unix time
$datefrom = Quitenicebooking_Utilities::to_timestamp($checkin, $this->settings);
$dateto = Quitenicebooking_Utilities::to_timestamp($checkout, $this->settings);
$usage = $this->booking_post->make_collision_summary($datefrom, $dateto, array_keys($all_rooms));
// 5.
// now, to subtract the collision summary data from the accommodation data
// interate through all_rooms, and delete any rooms where:
// 1. total_guests > room_occupancy
// 2. $usage > rooms_available
// 3. blocked
// for any rooms that don't get deleted, decrement the bed counter and room counter
foreach ($all_rooms as $room => &$v) {
// delete room if: 3. blocked
if (is_object($this->booking_post->booking_block) && $this->booking_post->booking_block->is_blocked($datefrom, $dateto, $v['id'])) {
unset($all_rooms[$room]);
} elseif ($total_guests > $v['quitenicebooking_max_occupancy']) {
// delete room if: 1. total_guests > room_occupancy
unset($all_rooms[$room]);
} elseif ($usage[$room]['max_concurrent_rooms'] >= $v['quitenicebooking_room_quantity']) {
// delete room if: 2. $usage > rooms_available
unset($all_rooms[$room]);
} else {
// retain room
// decrement room counter
$v['quitenicebooking_room_quantity'] -= $usage[$room]['max_concurrent_rooms'];
}
}
unset($v);
return $all_rooms;
}
/**
* Validate all requested rooms are available
*
* @global WPDB $wpdb
* @param array $live_bookings
* @return boolean TRUE if all are available, FALSE if any are not
*/
public function step_4_validate_rooms($live_bookings) {
global $wpdb;
// create live bookigns table
$this->booking_post->create_live_bookings_table();
// add live bookings
foreach ($live_bookings as $l) {
$this->booking_post->add_live_booking(array(
'checkin' => Quitenicebooking_Utilities::to_timestamp($l['checkin'], $this->settings),
'checkout' => Quitenicebooking_Utilities::to_timestamp($l['checkout'], $this->settings),
'type' => $l['type'],
'bed' => $l['bed'],
'adults' => $l['adults'],
'children' => $l['children']
));
}
// get all accommodations
$all_rooms = $this->accommodation_post->get_all_accommodations();
// get collision summary for each room
foreach ($live_bookings as $l) {
// check if room is blocked
if (is_object($this->booking_post->booking_block) && $this->booking_post->booking_block->is_blocked(Quitenicebooking_Utilities::to_timestamp($l['checkin'], $this->settings), Quitenicebooking_Utilities::to_timestamp($l['checkout'], $this->settings), $l['type'])) {
return false;
}
$usage = $this->booking_post->make_collision_summary(
Quitenicebooking_Utilities::to_timestamp($l['checkin'], $this->settings),
Quitenicebooking_Utilities::to_timestamp($l['checkout'], $this->settings),
array($l['type'])
);
// check if room is overbooked
if ($usage[$l['type']]['max_concurrent_rooms'] > $all_rooms[$l['type']]['quitenicebooking_room_quantity']) {
return false;
}
}
return true;
}
/**
* Generate a price breakdown
*
* @param array $room The room to generate the breakdown for
* @param array $booking A single booking for a single room
* ['checkin'] string The checkin date string (not unix timestamp)
* ['checkout'] string The checkout date string (not unix timestamp)
* ['adults'] int The number of adults
* ['children'] int The number of children
* ['total_guests']int The total number of guests
* @return array The price breakdown
* [$type] array The name of the room
* ['breakdown'] array The breakdown
* ['day'] array The day number (unix timestamp)
* ['date_string'] string The long form of the date to be displayed
* ['price_string'] string The price breakdown as a string
* ['subtotal'] float The day's subtotal
* ['surcharge'] float The surcharge
* ['total'] float The grand total for the room
*/
public function make_price_breakdown($room, $booking) {
$breakdown = array();
$breakdown['type'] = $room['title'];
$breakdown['breakdown'] = array();
// get unix timestamps
$checkin = Quitenicebooking_Utilities::to_timestamp($booking['checkin'], $this->settings);
$checkout = Quitenicebooking_Utilities::to_timestamp($booking['checkout'], $this->settings);
$booking_price = $this->accommodation_post->calc_booking_price($checkin, $checkout, $room['id'], array('adults' => $booking['adults'], 'children' => $booking['children']));
foreach ($booking_price['subtotals'] as $day => $subtotal) {
/* translators: Date format in price breakdown, see http://codex.wordpress.org/Formatting_Date_and_Time */
$breakdown['breakdown'][$day]['date_string'] = date_i18n(__('D jS F Y', 'quitenicebooking'), $day);
$breakdown['breakdown'][$day]['subtotal'] = $subtotal;
// create price string
$price_string = '';
if ($booking['adults'] > 0) {
$price_string .= $booking['adults'].' '._n('Adult', 'Adults', $booking['adults'], 'quitenicebooking').', ';
}
if ($booking['children'] > 0) {
$price_string .= $booking['children'].' '._n('Child', 'Children', $booking['children'], 'quitenicebooking').', ';
}
$price_string = substr($price_string, 0, -2);
$price_string .= ': '.Quitenicebooking_Utilities::format_price(Quitenicebooking_Utilities::float_to_user_price($subtotal, $this->settings, TRUE), $this->settings);
$breakdown['breakdown'][$day]['price_string'] = $price_string;
}
$breakdown['surcharge'] = $booking_price['surcharge'];
$breakdown['total'] = $booking_price['total'];
return $breakdown;
}
/**
* Calculates the deposit for a booking
*
* @param array $breakdown A breakdown of the booking
* [type] int The room type
* ['breakdown'] array The breakdown
* [day] int The unix timestamp
* [subtotal] float The subtotal
* @param float $discount_amt The discount amount
* @param float $extra_amt Any extra amount to be added before discounts
* @param float $tax_percentage float Tax amount to be applied after everything else
* @return float The deposit amount
*/
public function calculate_deposit($breakdown, $discount_amt = 0, $extra_amt = 0, $tax_percentage = 0) {
// disabled
if (empty($this->settings['deposit_type'])
|| ((empty($this->settings['accept_paypal']) || empty($this->settings['paypal_email_address'])) && empty($this->settings['accept_bank_transfer'])) ) {
return 0;
}
// full amount
if ($this->settings['deposit_type'] == 'full') {
$total = 0;
foreach ($breakdown as $b) {
foreach ($b['breakdown'] as $d) {
$total += $d['subtotal'];
}
if (!empty($b['surcharge'])) {
$total += $b['surcharge'];
}
}
$total += $extra_amt;
$total -= $discount_amt;
$tax = round($total * ($tax_percentage / 100), $this->settings['currency_decimals']);
$total = $total + $tax;
if ($total <= 0) {
return 0;
}
return round($total, $this->settings['currency_decimals']);
}
// percentage
if ($this->settings['deposit_type'] == 'percentage') {
$total = 0;
foreach ($breakdown as $b) {
foreach ($b['breakdown'] as $d) {
$total += $d['subtotal'];
}
if (!empty($b['surcharge'])) {
$total += $b['surcharge'];
}
}
$total += $extra_amt;
$total -= $discount_amt;
$tax = round($total * ($tax_percentage / 100), $this->settings['currency_decimals']);
$total = $total + $tax;
if ($total <= 0) {
return 0;
}
return round(floatval($this->settings['deposit_percentage']) / 100 * $total, $this->settings['currency_decimals']);
}
// flat
if ($this->settings['deposit_type'] == 'flat') {
return $this->settings['deposit_flat'];
}
// duration
if ($this->settings['deposit_type'] == 'duration') {
$total = 0;
foreach ($breakdown as $b) {
$day_count = 0;
foreach ($b['breakdown'] as $d) {
$total += $d['subtotal'];
$day_count ++;
if ($day_count >= $this->settings['deposit_duration']) {
break;
}
}
if (!empty($b['surcharge'])) {
$total += $b['surcharge'];
}
}
$total += $extra_amt;
$tax = round($total * ($tax_percentage / 100), $this->settings['currency_decimals']);
$total = $total + $tax;
return round($total, $this->settings['currency_decimals']);
}
}
/**
* Step 3 processing
*
* @param array $post The $_POST superglobal
*/
public function booking_step_3_processing($post) {
// booking data should be all validated now. don't merge it with $post
$booking = $_SESSION['booking'];
// put all the coupons in an array
if (isset($this->coupon_class) && is_object($this->coupon_class)) {
foreach ($post as $p => $v) {
if (preg_match('/^coupon_code_\d+/', $p) && !empty($v)) {
$applied_coupons[] = $v;
}
}
}
// put live booking into $live_bookings
$live_booking = array();
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
$m = array();
preg_match('/type=(.+)&bed=(.+)/', $booking['room_'.$n.'_selection'], $m);
$live_booking[$n]['checkin'] = $booking['room_'.$n.'_checkin'];
$live_booking[$n]['checkout'] = $booking['room_'.$n.'_checkout'];
$live_booking[$n]['type'] = $m[1];
$live_booking[$n]['bed'] = $m[2];
$live_booking[$n]['adults'] = $booking['room_'.$n.'_adults'];
$live_booking[$n]['children'] = $booking['room_'.$n.'_children'];
$live_booking[$n]['total_guests'] = $live_booking[$n]['adults'] + $live_booking[$n]['children'];
}
// get all rooms
$all_rooms = $this->accommodation_post->get_all_accommodations();
$breakdown = array();
$total = 0;
// foreach live booking, calculate
foreach ($live_booking as $l) {
$row = array();
$row = $this->make_price_breakdown($all_rooms[$l['type']], $l);
$total += $row['total'];
$breakdown[] = $row;
}
$discount_amt = 0;
// apply the coupons and collect errors for those that cannot be applied because of requirement checks
if (isset($applied_coupons)) {
// iterate through the breakdown and apply the discount
$discounts = array();
$coupon_errors = array();
$discount_amt = $this->coupon_class->apply_discount($applied_coupons, $breakdown, $total, $discounts, $coupon_errors);
$data['discounts'] = $discounts;
$data['errors'] = $coupon_errors;
$data['applied_coupons'] = $applied_coupons;
}
$data['breakdown'] = $breakdown;
// calculate tax
$tax = !empty($this->settings['tax_percentage']) ? round(($this->settings['tax_percentage'] / 100) * $total, $this->settings['currency_decimals']) : 0;
$total += $tax;
$data['tax'] = $tax;
$data['total'] = $total;
$data['deposit'] = $this->calculate_deposit($breakdown, $discount_amt, 0, $this->settings['tax_percentage']);
$data['total_rooms'] = $booking['room_qty'];
foreach ($live_booking as $l) {
$row = array();
$row['type'] = $all_rooms[$l['type']]['title'];
$row['checkin'] = $l['checkin'];
$row['checkout'] = $l['checkout'];
$row['guests'] = !empty($l['adults']) ? $l['adults'].' '._n('Adult', 'Adults', $l['adults'], 'quitenicebooking').', ' : '';
$row['guests'] .= !empty($l['children']) ? $l['children'].' '._n('Child', 'Children', $l['children'], 'quitenicebooking').', ' : '';
$row['guests'] = substr($row['guests'], 0, -2);
$data['summary'][] = $row;
}
$_SESSION['data'] = $data;
wp_redirect( $this->settings['step_3_url'] ); exit;
}
/**
* Step 4 processing
*
* @global WPDB $wpdb
*/
public function booking_step_4_processing() {
if ( ! isset( $_SESSION['booking'] ) || count( $_SESSION['booking'] ) == 0 || ! isset( $_SESSION['booking']['room_qty'] ) ) {
return;
}
$booking = $_SESSION['booking'];
// put all the coupons in an array
if (isset($this->coupon_class) && is_object($this->coupon_class)) {
foreach ($booking as $p => $v) {
if (preg_match('/^coupon_code_\d+/', $p) && !empty($v)) {
$applied_coupons[] = $v;
}
}
}
// begin summary table
$live_booking = array();
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
$m = array();
preg_match('/type=(.+)&bed=(.+)/', $booking['room_'.$n.'_selection'], $m);
$live_booking[$n]['checkin'] = $booking['room_'.$n.'_checkin'];
$live_booking[$n]['checkout'] = $booking['room_'.$n.'_checkout'];
$live_booking[$n]['type'] = $m[1];
$live_booking[$n]['bed'] = $m[2];
$live_booking[$n]['adults'] = $booking['room_'.$n.'_adults'];
$live_booking[$n]['children'] = $booking['room_'.$n.'_children'];
$live_booking[$n]['total_guests'] = $live_booking[$n]['adults'] + $live_booking[$n]['children'];
}
// do the live check one last time, if there's an error (any rooms or beds returning false) popup an error message to go back to step 2
$rooms_valid = $this->step_4_validate_rooms($live_booking);
if (!$rooms_valid) {
$errors[] = __('One or more of your requested rooms have become unavailable since the booking process began. Please reselect your rooms. We apologize for the inconvenience.', 'quitenicebooking');
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
unset($booking["room_{$n}_selection"]);
}
if (isset($booking['guest_email'])) {
// remove the email so that validation can kick in again at step 3
unset($booking['guest_email']);
}
$_SESSION['booking'] = $booking;
$_SESSION['errors'] = $errors;
$this->booking_step_2_processing($_POST);
}
$all_rooms = $this->accommodation_post->get_all_accommodations();
$breakdown = array();
$total = 0;
foreach ($live_booking as $l) {
$row = array();
$row = $this->make_price_breakdown($all_rooms[$l['type']], $l);
$total += $row['total'];
$breakdown[] = $row;
}
// apply the services
$extras = 0;
$services = array();
if (isset($booking['services'])) {
foreach ($booking['services'] as $s) {
$ss = $this->service_class->get_service($s);
$services[] = array('title' => $ss['post_title'], 'price' => $ss['quitenicebooking_service_price']);
$extras += $ss['quitenicebooking_service_price'];
}
$total += $extras;
}
$discount_amt = 0;
// apply the coupons and collect errors for those that cannot be applied because of requirement checks
if (isset($applied_coupons)) {
// iterate through the breakdown and apply the discount
$discounts = array();
$coupon_errors = array();
$discount_amt = $this->coupon_class->apply_discount($applied_coupons, $breakdown, $total, $discounts, $coupon_errors);
$data['discounts'] = $discounts;
// $data['errors'] = $coupon_errors;
$data['applied_coupons'] = $applied_coupons;
}
$data['breakdown'] = $breakdown;
// calculate tax
$tax = !empty($this->settings['tax_percentage']) ? round(($this->settings['tax_percentage'] / 100) * $total, $this->settings['currency_decimals']) : 0;
$total += $tax;
$data['tax'] = $tax;
$data['total'] = $total;
$data['deposit'] = $this->calculate_deposit($breakdown, $discount_amt, $extras, $this->settings['tax_percentage']);
$data['total_rooms'] = $booking['room_qty'];
$data['guest_first_name'] = $booking['guest_first_name'];
$data['guest_last_name'] = $booking['guest_last_name'];
$data['guest_email'] = $booking['guest_email'];
$data['services'] = $services;
foreach ($live_booking as $l) {
$row = array();
$row['type'] = $all_rooms[$l['type']]['title'];
$row['checkin'] = $l['checkin'];
$row['checkout'] = $l['checkout'];
$row['guests'] = !empty($l['adults']) ? $l['adults'].' '._n('Adult', 'Adults', $l['adults'], 'quitenicebooking').', ' : '';
$row['guests'] .= !empty($l['children']) ? $l['children'].' '._n('Child', 'Children', $l['children'], 'quitenicebooking').', ' : '';
$row['guests'] = substr($row['guests'], 0, -2);
$data['summary'][] = $row;
}
// end summary table
$form_fields = Quitenicebooking_Utilities::decode_reservation_form($this->settings);
$guest_details = array();
foreach ($form_fields as $field) {
if (in_array($field['type'], array('guest_first_name', 'guest_last_name', 'guest_email'))) {
continue;
}
$guest_details[$field['id']]['label'] = $field['label'];
if (!empty($booking['guest_details_'.$field['id']])) {
if (is_array($booking['guest_details_'.$field['id']])) {
$guest_details[$field['id']]['value'] = implode(', ', $booking['guest_details_'.$field['id']]);
} else {
$guest_details[$field['id']]['value'] = $booking['guest_details_'.$field['id']];
}
} else {
$guest_details[$field['id']]['value'] = '';
}
$guest_details[$field['id']]['type'] = $field['type'];
}
// add booking
if (empty($this->settings['disable_database'])) {
// get the earliest and latest booking date
$first_checkin = NULL;
$last_checkout = NULL;
foreach ($live_booking as $l) {
if ($first_checkin === NULL || Quitenicebooking_Utilities::to_timestamp($l['checkin'], $this->settings) < $first_checkin) {
$first_checkin = Quitenicebooking_Utilities::to_timestamp($l['checkin'], $this->settings);
}
if ($last_checkout === NULL || Quitenicebooking_Utilities::to_timestamp($l['checkout'], $this->settings) > $last_checkout) {
$last_checkout = Quitenicebooking_Utilities::to_timestamp($l['checkout'], $this->settings);
}
}
$first_checkin = substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], $first_checkin), 0, -9);
$last_checkout = substr(date($this->settings['date_format_strings'][$this->settings['date_format']]['php'], $last_checkout), 0, -9);
$post_id = wp_insert_post(array(
'post_title' => sprintf(__('%d room booking for %s %s (%s - %s)', 'quitenicebooking'), $booking['room_qty'], $booking['guest_first_name'], $booking['guest_last_name'], $first_checkin, $last_checkout),
'post_type' => 'booking',
'post_status' => !empty($this->settings['manually_confirm_bookings']) ? 'pending' : 'publish'
));
// booking id
$data['booking_id'] = $post_id;
add_post_meta($post_id, 'quitenicebooking_booking_id', $post_id);
// guest details
add_post_meta($post_id, 'quitenicebooking_guest_last_name', $booking['guest_last_name']);
add_post_meta($post_id, 'quitenicebooking_guest_first_name', $booking['guest_first_name']);
add_post_meta($post_id, 'quitenicebooking_guest_email', $booking['guest_email']);
add_post_meta($post_id, 'quitenicebooking_guest_details', wp_slash(json_encode($guest_details)));
// booking details
global $wpdb;
for ($l = 1; $l <= count($live_booking); $l ++) {
// if WPML is enabled, insert rows into database for the room type and all its translations
if (defined('ICL_SITEPRESS_VERSION')) {
global $sitepress;
// get the trid (group) of the accommodation
$trid = $sitepress->get_element_trid($live_booking[$l]['type'], 'post_accommodation');
// find all translations of the accommodation
$accommodation_translations = $sitepress->get_element_translations($trid, 'post_accommodation');
// add a db row for each of the translations
foreach ($accommodation_translations as $at) {
$wpdb->insert("{$wpdb->prefix}qns_bookings", array(
'post_id' => $post_id,
'room_booking_id' => $l,
'checkin' => date('Y-m-d H:i:s', Quitenicebooking_Utilities::to_timestamp($live_booking[$l]['checkin'], $this->settings)),
'checkout' => date('Y-m-d H:i:s', Quitenicebooking_Utilities::to_timestamp($live_booking[$l]['checkout'], $this->settings)),
'type' => $at->element_id,
'bed' => $live_booking[$l]['bed'],
'adults' => $live_booking[$l]['adults'],
'children' => $live_booking[$l]['children']
));
}
} else {
// insert row into database
$wpdb->insert("{$wpdb->prefix}qns_bookings", array(
'post_id' => $post_id,
'room_booking_id' => $l,
'checkin' => date('Y-m-d H:i:s', Quitenicebooking_Utilities::to_timestamp($live_booking[$l]['checkin'], $this->settings)),
'checkout' => date('Y-m-d H:i:s', Quitenicebooking_Utilities::to_timestamp($live_booking[$l]['checkout'], $this->settings)),
'type' => $live_booking[$l]['type'],
'bed' => $live_booking[$l]['bed'],
'adults' => $live_booking[$l]['adults'],
'children' => $live_booking[$l]['children']
));
}
}
// payment details
add_post_meta($post_id, 'quitenicebooking_deposit_amount', Quitenicebooking_Utilities::float_to_user_price(round($data['deposit'], $this->settings['currency_decimals']), $this->settings, TRUE));
add_post_meta($post_id, 'quitenicebooking_total_price', Quitenicebooking_Utilities::float_to_user_price(round($data['total'], $this->settings['currency_decimals']), $this->settings, TRUE));
add_post_meta($post_id, 'quitenicebooking_deposit_method', isset($booking['payment_method']) ? $booking['payment_method'] : '');
// coupon details
if (isset($applied_coupons)) {
add_post_meta($post_id, 'quitenicebooking_coupon_codes', implode(', ', $data['applied_coupons']));
$this->coupon_class->use_coupons($data['applied_coupons']);
}
// service details
if (!empty($services)) {
$sj = array();
foreach ($services as $s) {
$sj[$s['title']] = $s['price'];
}
add_post_meta($post_id, 'quitenicebooking_services', wp_slash(json_encode($sj)));
}
}
// set post_id to 0 if database disabled
if (!empty($this->settings['disable_database'])) {
$post_id = 0;
}
// payment method
$data['payment_method'] = isset($booking['payment_method']) ? $booking['payment_method'] : '';
if (isset($booking['payment_method']) && $booking['payment_method'] == 'paypal') {
$return_url = !empty($this->settings['payment_success_url']) ? $this->settings['payment_success_url'] : get_site_url();
$hash = substr(sha1('quitenicebooking'.$booking['guest_last_name'].$booking['guest_first_name']), 0, 6);
if (!empty($this->settings['payment_success_url'])) {
if (strpos($this->settings['payment_success_url'], '?') !== FALSE) {
// if permalink structure already contains a query string, append to it
$return_url .= '&booking_id='.$post_id.'&key='.$hash;
} else {
// else, supply it as the first argument
$return_url .= '?booking_id='.$post_id.'&key='.$hash;
}
}
// build paypal query string
$paypal_query_str = array(
'business' => $this->settings['paypal_email_address'],
'cmd' => '_xclick',
'item_name' => get_bloginfo('name').' '.sprintf(__('Room deposit for %s %s (Booking ID: %s)'), $booking['guest_first_name'], $booking['guest_last_name'], $post_id),
'amount' => $data['deposit'],
'currency_code' => $this->settings['paypal_currency'],
'no_shipping' => 1,
'return' => $this->settings['payment_success_url'],
'cancel_return' => !empty($this->settings['payment_fail_url']) ? $this->settings['payment_fail_url'] : get_site_url(),
);
$data['redirect'] = 'https://www.paypal.com/cgi-bin/webscr?'.http_build_query($paypal_query_str);
}
// email guest
$mailfrom = array('email' => !empty($this->settings['email_address']) ? $this->settings['email_address'] : get_bloginfo('admin_email'), 'name' => get_bloginfo('name'));
$mailto = array('email' => $booking['guest_email']);
$mailsubject = html_entity_decode(sprintf(__('%s booking confirmation', 'quitenicebooking'), get_bloginfo('name')));
$mailmessage = $this->settings['email_message'];
$mailmessage = preg_replace(
array(
'/\[CUSTOMER_FIRST_NAME\]/i',
'/\[CUSTOMER_LAST_NAME\]/i',
'/\[HOTEL_NAME\]/i'
),
array(
$booking['guest_first_name'],
$booking['guest_last_name'],
get_bloginfo('name')
),
$mailmessage
);
// premium email
$data['guest_details'] = $guest_details;
if (class_exists('Quitenicebooking_Email_Templates')) {
$premium_email = new Quitenicebooking_Email_Templates($this->settings);
$mailbody = $premium_email->generate_email($data);
} else {
ob_start();
include QUITENICEBOOKING_PATH.'views/mail_user.mail.php';
$mailbody = ob_get_clean();
}
$mailerror = Quitenicebooking_Utilities::send_mail($mailfrom, $mailto, $mailsubject, $mailbody, $this->settings);
// email admin
$mailto = array('email' => !empty($this->settings['email_address']) ? $this->settings['email_address'] : get_bloginfo('admin_email'), 'name' => get_bloginfo('name'));
$mailsubject = html_entity_decode(sprintf(__('New %d room booking for %s %s', 'quitenicebooking'), $booking['room_qty'], $booking['guest_first_name'], $booking['guest_last_name']));
ob_start();
include QUITENICEBOOKING_PATH.'views/mail_admin.mail.php';
$mailbody = ob_get_clean();
$mailerror = Quitenicebooking_Utilities::send_mail($mailfrom, $mailto, $mailsubject, $mailbody, $this->settings);
if ($mailerror !== TRUE) {
$data['errors'] = array(
__( 'Your reservation has been made, but the system was unable to send out a confirmation email. Please contact us for assistance.', 'quitenicebooking' ),
sprintf( __( 'The error message was: %s', 'quitenicebooking' ), $mailerror )
);
}
// store data in session
$_SESSION['data'] = $data;
// redirect to step 4
wp_redirect( $this->settings['step_4_url'] ); exit;
}
/**
* Ajax availability checker for a single room
*
* $post should contain:
* current_room = [ checkin, checkout, type, bed, adults, children ]
* (optional) checked_rooms = [ 1 => [ checkin, checkout, type, bed, adults, children ],
* 2 => [ checkin, checkout, type, bed, adults, children ],
* n => [ ... ]
* ]
* post_id = post_id
*
* checkin and checkout are in display format; they must be converted back into timestamps
*/
public function ajax_check_availability() {
$post = filter_input_array( INPUT_POST );
$found = TRUE;
// 1. build pivot table
$this->booking_post->create_live_bookings_table();
// 1.A. remove bookings belonging to this post_id from pivot table
$this->booking_post->remove_live_booking( $post['post_id'] );
// 2. if checked_rooms exist, add as live booking
if ( isset( $post['checked_rooms'] ) && count( $post['checked_rooms'] ) > 0 ) {
// convert dates to unix time
foreach ( $post['checked_rooms'] as $checked_room ) {
$checked_room['checkin'] = Quitenicebooking_Utilities::to_timestamp($checked_room['checkin'], $this->settings);
$checked_room['checkout'] = Quitenicebooking_Utilities::to_timestamp($checked_room['checkout'], $this->settings);
$this->booking_post->add_live_booking($checked_room);
}
}
// 3. get the requested room
$this_room = $this->accommodation_post->get_single_accommodation($post['current_room']['type'], TRUE);
// 4. make collision summary
// convert dates to unix time
$post['current_room']['checkin'] = Quitenicebooking_Utilities::to_timestamp($post['current_room']['checkin'], $this->settings);
$post['current_room']['checkout'] = Quitenicebooking_Utilities::to_timestamp($post['current_room']['checkout'], $this->settings);
$usage = $this->booking_post->make_collision_summary(
$post['current_room']['checkin'],
$post['current_room']['checkout'],
array_keys($this_room)
);
// 5.A. check if room is blocked
if (is_object($this->booking_post->booking_block) && $this->booking_post->booking_block->is_blocked($post['current_room']['checkin'], $post['current_room']['checkout'], $post['current_room']['type'])) {
$found = 'room_blocked';
}
// 5. subtract collision summary from this_room
// if total_guests > room_occupancy
if ( $usage[ $post['current_room']['type'] ]['max_concurrent_rooms'] >= $this_room[ $post['current_room']['type'] ]['quitenicebooking_room_quantity']) {
// if usage > rooms_available
$found = 'room_qty';
} elseif ( $post['current_room']['adults'] + $post['current_room']['children'] > $this_room[ $post['current_room']['type'] ]['quitenicebooking_max_occupancy'] ) {
$found = 'guest_qty';
}
// return the result
echo json_encode( $found );
exit;
}
/**
* Updates booking status via admin screen
* Performs availability check for before updating
* Shows pop if fail
* Redirects (refreshes) the booking list if successful
*
* @param array $_GET
* string id
* string status
*/
public function ajax_set_booking_status() {
$get = filter_input_array(INPUT_GET);
$id = $get['id'];
$status = $get['status'];
// verify post exists and is booking
$post = get_post($id);
if (!is_admin() || !$post || $post->post_type != 'booking' || !in_array($status, array('publish', 'pending', 'private'))) {
exit;
}
// do an availability check before setting status to publish
if ($status == 'publish') {
// get the post's booking data
// create live bookings table
$this->booking_post->create_live_bookings_table();
// get rooms belonging to this booking from table (not from live table, since it excludes non published entries
global $wpdb;
$unverified_rooms = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}qns_bookings WHERE post_id = %d",
$id
), ARRAY_A);
// remove rooms belonging to this booking from table
$this->booking_post->remove_live_booking($id);
// make collision summary
foreach ($unverified_rooms as $u) {
$room = $this->accommodation_post->get_single_accommodation($u['type'], TRUE);
$usage = $this->booking_post->make_collision_summary(strtotime($u['checkin']), strtotime($u['checkout']), array($u['type']));
// check if dates blocked
if ($this->booking_post->booking_block->is_blocked(strtotime($u['checkin']), strtotime($u['checkout']), $u['type'])) {
wp_redirect(admin_url('edit.php?post_type=booking&booking_error=blocked'));
exit;
}
// check if overbooked
if ($usage[$u['type']]['max_concurrent_rooms'] >= $room[$u['type']]['quitenicebooking_room_quantity']) {
wp_redirect(admin_url('edit.php?post_type=booking&booking_error=overbooked'));
exit;
} else {
$this->booking_post->add_live_booking(array(
'checkin' => strtotime($u['checkin']),
'checkout' => strtotime($u['checkout']),
'type' => $u['type'],
'bed' => $u['bed'],
'adults' => $u['adults'],
'children' => $u['children']
));
}
}
}
wp_update_post(array('ID' => $id, 'post_status' => $status));
// redirect to booking page
wp_redirect(admin_url('edit.php?post_type=booking&booking_status='.$status));
exit;
}
/**
* Ajax availability checker for Datepicker calendar
*
* $_GET should contain:
* year = int
* month = int
* type = int (optional)
*/
public function ajax_calendar_availability() {
$get = filter_input_array(INPUT_GET);
$year = $get['year'];
$month = $get['month'];
$type = !empty($get['type']) ? $get['type'] : FALSE;
$blocked = $this->preload_calendar_availability($year, $month, array(0, 1, 2, 3, 4, 5), $type);
echo json_encode($blocked);
exit;
}
/**
* Preload availability for Datepicker calendar
*
* @param int $startyear Year to start from
* @param int $startmonth Month to start from
* @param array $monthdiffs An array of months relative to the start to check
* @param int $type Accommodation ID, if false, check all rooms
*/
public function preload_calendar_availability($startyear, $startmonth, $monthdiffs, $type = FALSE) {
$startdate = strtotime($startyear.'-'.$startmonth.'-01');
$blocked_months = array();
// create an array of months to check for, based on $monthdiffs
$months = array();
for ($i = 0; $i < count($monthdiffs); $i ++) {
$months[$i] = getdate($startdate);
if ($months[$i]['mon'] + $monthdiffs[$i] == 0) {
$months[$i]['mon'] = 12;
$months[$i]['year'] --;
} elseif ($months[$i]['mon'] + $monthdiffs[$i] > 12) {
$months[$i]['mon'] = $months[$i]['mon'] + $monthdiffs[$i] - 12;
$months[$i]['year'] ++;
} else {
$months[$i]['mon'] += $monthdiffs[$i];
}
}
// cache the accommodation data so db only needs to be hit once
if ($type === FALSE) {
$db_all_rooms = $this->accommodation_post->get_all_accommodations();
} else {
$db_all_rooms = $this->accommodation_post->get_single_accommodation($type, TRUE);
}
foreach ($db_all_rooms as $r) {
$all_rooms_template[$r['id']]['id'] = $r['id'];
$all_rooms_template[$r['id']]['quitenicebooking_room_quantity'] = $r['quitenicebooking_room_quantity'];
}
// for each day in the month:
// 1. build bookings table
// 2. get all rooms
// 3. get collisions
// 4. subtract collisions from all rooms
// 5. if no rooms available, append this day to the array
for ($i = 0; $i < count($monthdiffs); $i ++) {
$month = $months[$i]['mon'];
$year = $months[$i]['year'];
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
// skip availability check if the date is in the past
if ($monthdiffs[$i] < 0 && $startyear == date('Y') && $startmonth == date('n')) {
$blocked_months['y'.$year]['m'.$month] = range(1, $days_in_month);
continue;
}
$blocked_days = array();
$this->booking_post->create_live_bookings_table();
for ($day = 1; $day <= $days_in_month; $day ++) {
// skip availability check if the date is in the past
if ($year == date('Y') && $month == date('n') && $day < date('j')) {
$blocked_days[] = $day;
continue;
}
$all_rooms = $all_rooms_template;
$usage = $this->booking_post->make_collision_summary(strtotime($year.'-'.$month.'-'.$day), strtotime($year.'-'.$month.'-'.$day) + 86400, array_keys($all_rooms));
foreach ($all_rooms as $room => $v) {
if (is_object($this->booking_post->booking_block) && $this->booking_post->booking_block->is_blocked(strtotime($year.'-'.$month.'-'.$day), strtotime($year.'-'.$month.'-'.$day) + 86400, $v['id'])) {
// remove blocked rooms
unset($all_rooms[$room]);
} elseif ($usage[$room]['max_concurrent_rooms'] >= $v['quitenicebooking_room_quantity']) {
// remove fully booked rooms
unset($all_rooms[$room]);
}
}
if (count($all_rooms) == 0) {
// if there are no rooms left for the day
$blocked_days[] = $day;
}
}
$blocked_months['y'.$year]['m'.$month] = $blocked_days;
}
return $blocked_months;
}
/**
* Ajax breakdown generator
* Generates breakdown, deposit, and total
*
* $_POST should contain:
* array $services = array($id, ...)
* array $coupons = array($id, ...)
* return json encoded object
* breakdown_html
* deposit_html
* total_html
*/
public function ajax_step_3_breakdown() {
$post = filter_input_array(INPUT_POST);
$booking = $_SESSION['booking'];
// put all the coupons in an array
if (isset($post['coupons'])) {
foreach ($post['coupons'] as $v) {
$applied_coupons[] = $v;
}
}
// put live booking into $live_bookings
$live_booking = array();
for ($n = 1; $n <= $booking['room_qty']; $n ++) {
$m = array();
preg_match('/type=(.+)&bed=(.+)/', $booking['room_'.$n.'_selection'], $m);
$live_booking[$n]['checkin'] = $booking['room_'.$n.'_checkin'];
$live_booking[$n]['checkout'] = $booking['room_'.$n.'_checkout'];
$live_booking[$n]['type'] = $m[1];
$live_booking[$n]['bed'] = $m[2];
$live_booking[$n]['adults'] = $booking['room_'.$n.'_adults'];
$live_booking[$n]['children'] = $booking['room_'.$n.'_children'];
$live_booking[$n]['total_guests'] = $live_booking[$n]['adults'] + $live_booking[$n]['children'];
}
// get all rooms
$all_rooms = $this->accommodation_post->get_all_accommodations();
// make breakdown
$breakdown = array();
$total = 0;
// foreach live booking, calculate breakdown
foreach ($live_booking as $l) {
$row = array();
$row = $this->make_price_breakdown($all_rooms[$l['type']], $l);
$total += $row['total'];
$breakdown[] = $row;
}
// apply the services
$extras = 0;
$services = array();
if (isset($post['services'])) {
foreach ($post['services'] as $s) {
$ss = $this->service_class->get_service($s);
$services[] = array('title' => $ss['post_title'], 'price' => $ss['quitenicebooking_service_price']);
$extras += $ss['quitenicebooking_service_price'];
}
$total += $extras;
}
$data['services'] = $services;
$discount_amt = 0;
// apply the coupons and collect errors for those that cannot be applied because of requirement checks
if (isset($applied_coupons)) {
// iterate through the breakdown and apply the discount
$discounts = array();
$coupon_errors = array();
$discount_amt = $this->coupon_class->apply_discount($applied_coupons, $breakdown, $total, $discounts, $coupon_errors);
$data['discounts'] = $discounts;
// $data['errors'] = $coupon_errors;
$data['applied_coupons'] = $applied_coupons;
}
$data['breakdown'] = $breakdown;
// calculate tax
$tax = !empty($this->settings['tax_percentage']) ? round(($this->settings['tax_percentage'] / 100) * $total, $this->settings['currency_decimals']) : 0;
$total += $tax;
$data['tax'] = $tax;
$data['total'] = $total;
$data['deposit'] = $this->calculate_deposit($breakdown, $discount_amt, $extras, $this->settings['tax_percentage']);
$data['total_rooms'] = $booking['room_qty'];
foreach ($live_booking as $l) {
$row = array();
$row['type'] = $all_rooms[$l['type']]['title'];
$row['checkin'] = $l['checkin'];
$row['checkout'] = $l['checkout'];
$row['guests'] = !empty($l['adults']) ? $l['adults'].' '._n('Adult', 'Adults', $l['adults'], 'quitenicebooking').', ' : '';
$row['guests'] .= !empty($l['children']) ? $l['children'].' '._n('Child', 'Children', $l['children'], 'quitenicebooking').', ' : '';
$row['guests'] = substr($row['guests'], 0, -2);
$data['summary'][] = $row;
}
ob_start();
include QUITENICEBOOKING_PATH.'views/booking_step_3_breakdown_ajax.htm.php';
$breakdown_html = ob_get_clean();
echo json_encode(array(
'breakdown_html' => $breakdown_html,
'deposit' => Quitenicebooking_Utilities::format_price(Quitenicebooking_Utilities::float_to_user_price($data['deposit'], $this->settings, TRUE), $this->settings),
'total' => Quitenicebooking_Utilities::format_price(Quitenicebooking_Utilities::float_to_user_price($data['total'], $this->settings, TRUE), $this->settings)
));
exit;
}
/**
* Utilities ===============================================================
*/
/**
* Renders a view
*
* @param string $template Path to the template
* @param array $data Any data that needs to be passed to the template
*/
protected function view($template, $data = NULL) {
ob_start();
if ( ! is_null( $data ) && count( $data ) > 0) {
extract( $data, EXTR_PREFIX_ALL, 'quitenicebooking' );
}
include QUITENICEBOOKING_PATH . $template;
return ob_get_clean();
}
/**
* Debugging methods =======================================================
*/
/**
* Prints out debugging information inside the content
*
* @param type $content
* @return string
*/
public function debug( $content ) {
ob_start();
// insert debugging code here
$out = ob_get_clean();
return $content.$out;
}
}