Hotel Raxa - Advanced Booking System Implementation
🏨 Hotel Booking Enhancements: - Implemented Eagle Booking Advanced Pricing add-on - Added Booking.com-style rate management system - Created professional calendar interface for pricing - Integrated deals and discounts functionality 💰 Advanced Pricing Features: - Dynamic pricing models (per room, per person, per adult) - Base rates, adult rates, and child rates management - Length of stay discounts and early bird deals - Mobile rates and secret deals implementation - Seasonal promotions and flash sales 📅 Availability Management: - Real-time availability tracking - Stop sell and restriction controls - Closed to arrival/departure functionality - Minimum/maximum stay requirements - Automatic sold-out management 💳 Payment Integration: - Maintained Redsys payment gateway integration - Seamless integration with existing Eagle Booking - No modifications to core Eagle Booking plugin 🛠️ Technical Implementation: - Custom database tables for advanced pricing - WordPress hooks and filters integration - AJAX-powered admin interface - Data migration from existing Eagle Booking - Professional calendar view for revenue management 📊 Admin Interface: - Booking.com-style management dashboard - Visual rate and availability calendar - Bulk operations for date ranges - Statistics and analytics dashboard - Modal dialogs for quick editing 🔧 Code Quality: - WordPress coding standards compliance - Secure database operations with prepared statements - Proper input validation and sanitization - Error handling and logging - Responsive admin interface 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
/**
|
||||
* Select Field.
|
||||
*
|
||||
* @package ReduxFramework/Fields
|
||||
* @author Dovy Paukstys & Kevin Provance (kprovance)
|
||||
* @version 4.0.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'Redux_Select', false ) ) {
|
||||
|
||||
/**
|
||||
* Class Redux_Select
|
||||
*/
|
||||
class Redux_Select extends Redux_Field {
|
||||
|
||||
/**
|
||||
* Set field defaults.
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$defaults = array(
|
||||
'options' => array(),
|
||||
'width' => '40%',
|
||||
'multi' => false,
|
||||
'sortable' => false,
|
||||
'ajax' => false,
|
||||
'min-input-length' => 1,
|
||||
'placeholder' => '',
|
||||
);
|
||||
|
||||
$this->field = wp_parse_args( $this->field, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Field Render Function.
|
||||
* Takes the vars and outputs the HTML for the field in the settings
|
||||
*
|
||||
* @since ReduxFramework 1.0.0
|
||||
*/
|
||||
public function render() {
|
||||
$sortable = ( isset( $this->field['sortable'] ) && true === (bool) $this->field['sortable'] ) ? ' select2-sortable' : '';
|
||||
|
||||
if ( ! empty( $sortable ) ) { // Dummy proofing :P.
|
||||
$this->field['multi'] = true;
|
||||
}
|
||||
|
||||
if ( ! empty( $this->field['data'] ) && empty( $this->field['options'] ) ) {
|
||||
if ( empty( $this->field['args'] ) ) {
|
||||
$this->field['args'] = array();
|
||||
}
|
||||
|
||||
if ( 'elusive-icons' === $this->field['data'] || 'elusive-icon' === $this->field['data'] || 'elusive' === $this->field['data'] ) {
|
||||
$icons_file = Redux_Core::$dir . 'lib/elusive-icons.php';
|
||||
|
||||
/**
|
||||
* Filter 'redux-font-icons-file}'
|
||||
*
|
||||
* @param array $icon_file The File for the icons
|
||||
*/
|
||||
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidHookName
|
||||
$icons_file = apply_filters( 'redux-font-icons-file', $icons_file );
|
||||
|
||||
/**
|
||||
* Filter 'redux/{opt_name}/field/font/icons/file'
|
||||
*
|
||||
* @param array $icon_file The file for the icons
|
||||
*/
|
||||
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidHookName
|
||||
$icons_file = apply_filters( "redux/{$this->parent->args['opt_name']}/field/font/icons/file", $icons_file );
|
||||
|
||||
if ( file_exists( $icons_file ) ) {
|
||||
include_once $icons_file;
|
||||
}
|
||||
}
|
||||
|
||||
// First one get with AJAX.
|
||||
$ajax = false;
|
||||
if ( isset( $this->field['ajax'] ) && $this->field['ajax'] ) {
|
||||
$ajax = true;
|
||||
}
|
||||
$this->field['options'] = $this->parent->wordpress_data->get( $this->field['data'], $this->field['args'], $this->parent->args['opt_name'], $this->value, $ajax );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->field['data'] ) && in_array( $this->field['data'], array( 'elusive-icons', 'elusive-icon', 'elusive', 'dashicons', 'dashicon', 'dash' ), true ) ) {
|
||||
$this->field['class'] .= ' font-icons';
|
||||
}
|
||||
|
||||
if ( ! empty( $this->field['options'] ) ) {
|
||||
$multi = ( isset( $this->field['multi'] ) && $this->field['multi'] ) ? ' multiple="multiple"' : '';
|
||||
|
||||
if ( ! empty( $this->field['width'] ) ) {
|
||||
$width = ' style="width:' . esc_attr( $this->field['width'] ) . '"';
|
||||
} else {
|
||||
$width = ' style="width:40%;"';
|
||||
}
|
||||
|
||||
$name_brackets = '';
|
||||
if ( ! empty( $multi ) ) {
|
||||
$name_brackets = '[]';
|
||||
}
|
||||
|
||||
$placeholder = ( isset( $this->field['placeholder'] ) ) ? esc_attr( $this->field['placeholder'] ) : esc_html__( 'Select an item', 'redux-framework' );
|
||||
|
||||
$select2_width = 'resolve';
|
||||
if ( '' !== $multi ) {
|
||||
$select2_width = '100%';
|
||||
}
|
||||
$this->select2_config['width'] = $select2_width;
|
||||
$this->select2_config['allowClear'] = true;
|
||||
|
||||
if ( isset( $this->field['ajax'] ) && $this->field['ajax'] && isset( $this->field['data'] ) && '' !== $this->field['data'] ) {
|
||||
$this->select2_config['ajax'] = true;
|
||||
$this->select2_config['min-input-length'] = $this->field['min_input_length'] ?? 1;
|
||||
$this->select2_config['action'] = "redux_{$this->parent->args['opt_name']}_select2";
|
||||
if ( isset( $this->field['args'] ) ) {
|
||||
$this->select2_config['args'] = wp_json_encode( $this->field['args'] );
|
||||
}
|
||||
$this->select2_config['nonce'] = wp_create_nonce( "redux_{$this->parent->args['opt_name']}_select2" );
|
||||
$this->select2_config['wp-data'] = $this->field['data'];
|
||||
}
|
||||
|
||||
if ( isset( $this->field['select2'] ) ) {
|
||||
$this->field['select2'] = wp_parse_args( $this->field['select2'], $this->select2_config );
|
||||
} else {
|
||||
$this->field['select2'] = $this->select2_config;
|
||||
}
|
||||
|
||||
$this->field['select2'] = Redux_Functions::sanitize_camel_case_array_keys( $this->field['select2'] );
|
||||
|
||||
$select2_data = Redux_Functions::create_data_string( $this->field['select2'] );
|
||||
|
||||
if ( isset( $this->field['multi'] ) && $this->field['multi'] && isset( $this->field['sortable'] ) && $this->field['sortable'] && ! empty( $this->value ) && is_array( $this->value ) ) {
|
||||
$orig_option = $this->field['options'];
|
||||
$this->field['options'] = array();
|
||||
|
||||
foreach ( $this->value as $value ) {
|
||||
$this->field['options'][ $value ] = $orig_option[ $value ];
|
||||
}
|
||||
|
||||
if ( count( $this->field['options'] ) < count( $orig_option ) ) {
|
||||
foreach ( $orig_option as $key => $value ) {
|
||||
if ( ! in_array( $key, $this->field['options'], true ) ) {
|
||||
$this->field['options'][ $key ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sortable = ( isset( $this->field['sortable'] ) && $this->field['sortable'] ) ? ' select2-sortable' : '';
|
||||
|
||||
echo '<select ' .
|
||||
esc_html( $multi ) . '
|
||||
id="' . esc_attr( $this->field['id'] ) . '-select"
|
||||
data-placeholder="' . esc_attr( $placeholder ) . '"
|
||||
name="' . esc_attr( $this->field['name'] . $this->field['name_suffix'] ) . esc_attr( $name_brackets ) . '"
|
||||
class="redux-select-item ' . esc_attr( $this->field['class'] ) . esc_attr( $sortable ) . '"' .
|
||||
$width . ' rows="6"' . esc_attr( $select2_data ) . '>'; // phpcs:ignore WordPress.Security.EscapeOutput
|
||||
|
||||
echo '<option></option>';
|
||||
|
||||
foreach ( $this->field['options'] as $k => $v ) {
|
||||
if ( is_array( $v ) ) {
|
||||
echo '<optgroup label="' . esc_attr( $k ) . '">';
|
||||
|
||||
foreach ( $v as $opt => $val ) {
|
||||
$this->make_option( (string) $opt, $val );
|
||||
}
|
||||
|
||||
echo '</optgroup>';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->make_option( (string) $k, $v );
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
} else {
|
||||
echo '<strong>' . esc_html__( 'No items of this type were found.', 'redux-framework' ) . '</strong>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile option HTML.
|
||||
*
|
||||
* @param string $id HTML ID.
|
||||
* @param mixed $value Value array.
|
||||
*/
|
||||
private function make_option( string $id, $value ) {
|
||||
if ( is_array( $this->value ) ) {
|
||||
$selected = ( in_array( $id, $this->value, true ) ) ? ' selected="selected"' : '';
|
||||
} else {
|
||||
$selected = selected( $this->value, $id, false );
|
||||
}
|
||||
|
||||
echo '<option value="' . esc_attr( $id ) . '" ' . esc_html( $selected ) . '>' . esc_attr( $value ) . '</option>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Do enqueue for each field instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function always_enqueue() {
|
||||
if ( isset( $this->field['sortable'] ) && $this->field['sortable'] ) {
|
||||
wp_enqueue_script( 'jquery-ui-sortable' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue Function.
|
||||
* If this field requires any scripts, or css define this function and register/enqueue the scripts/css
|
||||
*
|
||||
* @since ReduxFramework 1.0.0
|
||||
*/
|
||||
public function enqueue() {
|
||||
wp_enqueue_style( 'select2-css' );
|
||||
|
||||
wp_enqueue_script(
|
||||
'redux-field-select',
|
||||
Redux_Core::$url . 'inc/fields/select/redux-select' . Redux_Functions::is_min() . '.js',
|
||||
array( 'jquery', 'select2-js', 'redux-js' ),
|
||||
$this->timestamp,
|
||||
true
|
||||
);
|
||||
|
||||
if ( $this->parent->args['dev_mode'] ) {
|
||||
wp_enqueue_style(
|
||||
'redux-field-select',
|
||||
Redux_Core::$url . 'inc/fields/select/redux-select.css',
|
||||
array(),
|
||||
$this->timestamp
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class_alias( 'Redux_Select', 'ReduxFramework_Select' );
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Silence is golden.
|
||||
*
|
||||
* @package Redux Framework
|
||||
*/
|
||||
|
||||
_deprecated_file( 'field_select.php', '4.3', 'class-redux-select.php', 'This file has been renamed and is no longer used in Redux 4. Please change any references to it as it will be removed in future versions of Redux.' );
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Silence is golden.
|
||||
*
|
||||
* @package Redux Framework
|
||||
*/
|
||||
|
||||
echo null;
|
||||
@@ -0,0 +1,7 @@
|
||||
.redux-container-select li.ui-state-highlight { height: 20px; margin-top: 2px; margin-left: 5px; width: 64px; margin-bottom: 0; }
|
||||
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove, .select2-container--classic .select2-selection--single .select2-selection__clear { font-size: 1.2em; }
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVkdXgtc2VsZWN0LmNzcyIsInNvdXJjZXMiOlsicmVkdXgtc2VsZWN0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQUFDSSx1QkFEbUIsQ0FDbkIsRUFBRSxBQUFBLG1CQUFtQixDQUFDLEVBQ2xCLE1BQU0sRUFBRSxJQUFJLEVBQ1osVUFBVSxFQUFFLEdBQUcsRUFDZixXQUFXLEVBQUUsR0FBRyxFQUNoQixLQUFLLEVBQUUsSUFBSSxFQUNYLGFBQWEsRUFBRSxDQUFDLEdBQ25COztBQUdMLEFBQ0ksMkJBRHVCLENBQ3ZCLDRCQUE0QixDQUFDLGtDQUFrQyxFQURuRSwyQkFBMkIsQ0FFdkIsMEJBQTBCLENBQUMseUJBQXlCLENBQUMsRUFDakQsU0FBUyxFQUFFLEtBQUssR0FDbkIifQ== */
|
||||
|
||||
/*# sourceMappingURL=redux-select.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["redux-select.scss","redux-select.css"],"names":[],"mappings":"AAAA,gDAUA,YAAA,ECPQ,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAAA;;AAIxB,gLAGQ,gBAAgB,EAAA;;AATxB,qiBAAqiB","file":"redux-select.css","sourcesContent":[".redux-container-select {\n li.ui-state-highlight {\n height: 20px;\n margin-top: 2px;\n margin-left: 5px;\n width: 64px;\n margin-bottom: 0;\n }\n}\n\n.select2-container--classic {\n .select2-selection--multiple .select2-selection__choice__remove,\n .select2-selection--single .select2-selection__clear {\n font-size: 1.2em;\n }\n}\n",".redux-container-select li.ui-state-highlight { height: 20px; margin-top: 2px; margin-left: 5px; width: 64px; margin-bottom: 0; }\n\n.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove, .select2-container--classic .select2-selection--single .select2-selection__clear { font-size: 1.2em; }\n\n/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVkdXgtc2VsZWN0LmNzcyIsInNvdXJjZXMiOlsicmVkdXgtc2VsZWN0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsQUFDSSx1QkFEbUIsQ0FDbkIsRUFBRSxBQUFBLG1CQUFtQixDQUFDLEVBQ2xCLE1BQU0sRUFBRSxJQUFJLEVBQ1osVUFBVSxFQUFFLEdBQUcsRUFDZixXQUFXLEVBQUUsR0FBRyxFQUNoQixLQUFLLEVBQUUsSUFBSSxFQUNYLGFBQWEsRUFBRSxDQUFDLEdBQ25COztBQUdMLEFBQ0ksMkJBRHVCLENBQ3ZCLDRCQUE0QixDQUFDLGtDQUFrQyxFQURuRSwyQkFBMkIsQ0FFdkIsMEJBQTBCLENBQUMseUJBQXlCLENBQUMsRUFDakQsU0FBUyxFQUFFLEtBQUssR0FDbkIifQ== */\n\n/*# sourceMappingURL=redux-select.css.map */\n"]}
|
||||
@@ -0,0 +1,139 @@
|
||||
/* global redux_change, redux, ajaxurl, jQuery */
|
||||
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
redux.field_objects = redux.field_objects || {};
|
||||
redux.field_objects.select = redux.field_objects.select || {};
|
||||
|
||||
redux.field_objects.select.init = function( selector ) {
|
||||
selector = $.redux.getSelector( selector, 'select' );
|
||||
|
||||
$( selector ).each(
|
||||
function() {
|
||||
var default_params = {};
|
||||
var el = $( this );
|
||||
var parent = el;
|
||||
|
||||
if ( ! el.hasClass( 'redux-field-container' ) ) {
|
||||
parent = el.parents( '.redux-field-container:first' );
|
||||
}
|
||||
|
||||
if ( parent.is( ':hidden' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( parent.hasClass( 'redux-field-init' ) ) {
|
||||
parent.removeClass( 'redux-field-init' );
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
el.find( 'select.redux-select-item' ).each(
|
||||
function() {
|
||||
var action;
|
||||
var nonce;
|
||||
var wpdata;
|
||||
var min;
|
||||
var data_args;
|
||||
|
||||
if ( $( this ).hasClass( 'font-icons' ) ) {
|
||||
default_params = $.extend(
|
||||
{},
|
||||
{
|
||||
templateResult: redux.field_objects.select.addIcon,
|
||||
templateSelection: redux.field_objects.select.addIcon,
|
||||
escapeMarkup: function( m ) {
|
||||
return m;
|
||||
}
|
||||
},
|
||||
default_params
|
||||
);
|
||||
}
|
||||
if ( $( this ).data( 'ajax' ) ) {
|
||||
action = $( this ).data( 'action' );
|
||||
nonce = $( this ).data( 'nonce' );
|
||||
wpdata = $( this ).data( 'wp-data' );
|
||||
min = $( this ).data( 'min-input-length' );
|
||||
data_args = {};
|
||||
if ( $( this ).data( 'args' ) ) {
|
||||
data_args = JSON.stringify( $( this ).data( 'args' ) );
|
||||
}
|
||||
|
||||
if ( 'true' === min ) {
|
||||
min = 1;
|
||||
}
|
||||
|
||||
default_params = {
|
||||
minimumInputLength: min,
|
||||
ajax: {
|
||||
url: ajaxurl,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function( params ) {
|
||||
return {
|
||||
nonce: nonce,
|
||||
data: wpdata,
|
||||
q: params.term,
|
||||
page: params.page || 1,
|
||||
action: action,
|
||||
data_args: data_args
|
||||
};
|
||||
},
|
||||
processResults: function( data, params ) {
|
||||
params.page = params.page || 1;
|
||||
|
||||
if ( true === data.success ) {
|
||||
return {
|
||||
results: data.data // ,
|
||||
// We'll revisit this later.
|
||||
// pagination: {
|
||||
// more: ( params.page * 20 ) < data.data.length
|
||||
// }.
|
||||
};
|
||||
} else if ( false === data.success ) {
|
||||
alert( data.data );
|
||||
|
||||
return {
|
||||
results: data.data
|
||||
};
|
||||
}
|
||||
},
|
||||
cache: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$( this ).select2( default_params );
|
||||
|
||||
el.find( '.select2-search__field' ).width( 'auto' );
|
||||
|
||||
if ( $( this ).hasClass( 'select2-sortable' ) ) {
|
||||
|
||||
default_params = {};
|
||||
default_params.bindOrder = 'sortableStop';
|
||||
default_params.sortableOptions = { axis: 'x', placeholder: false };
|
||||
|
||||
$( this ).select2Sortable( default_params );
|
||||
}
|
||||
|
||||
$( this ).on(
|
||||
'select2:select',
|
||||
function() {
|
||||
redux_change( $( $( this ) ) );
|
||||
|
||||
$( this ).select2SortableOrder();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
redux.field_objects.select.addIcon = function( icon ) {
|
||||
if ( icon.hasOwnProperty( 'id' ) ) {
|
||||
return '<span class="elusive"><i class="' + icon.id + '"></i> ' + icon.text + '</span>';
|
||||
}
|
||||
};
|
||||
})( jQuery );
|
||||
1
wp-content/plugins/eagle-booking/include/redux/inc/fields/select/redux-select.min.js
vendored
Normal file
1
wp-content/plugins/eagle-booking/include/redux/inc/fields/select/redux-select.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(c){"use strict";redux.field_objects=redux.field_objects||{},redux.field_objects.select=redux.field_objects.select||{},redux.field_objects.select.init=function(e){e=c.redux.getSelector(e,"select"),c(e).each(function(){var d={},n=c(this),e=n;(e=n.hasClass("redux-field-container")?e:n.parents(".redux-field-container:first")).is(":hidden")||e.hasClass("redux-field-init")&&(e.removeClass("redux-field-init"),n.find("select.redux-select-item").each(function(){var t,s,a,e,i;c(this).hasClass("font-icons")&&(d=c.extend({},{templateResult:redux.field_objects.select.addIcon,templateSelection:redux.field_objects.select.addIcon,escapeMarkup:function(e){return e}},d)),c(this).data("ajax")&&(t=c(this).data("action"),s=c(this).data("nonce"),a=c(this).data("wp-data"),e=c(this).data("min-input-length"),i={},c(this).data("args")&&(i=JSON.stringify(c(this).data("args"))),d={minimumInputLength:e="true"===e?1:e,ajax:{url:ajaxurl,dataType:"json",delay:250,data:function(e){return{nonce:s,data:a,q:e.term,page:e.page||1,action:t,data_args:i}},processResults:function(e,t){return t.page=t.page||1,!0===e.success?{results:e.data}:!1===e.success?(alert(e.data),{results:e.data}):void 0},cache:!0}}),c(this).select2(d),n.find(".select2-search__field").width("auto"),c(this).hasClass("select2-sortable")&&(d={bindOrder:"sortableStop",sortableOptions:{axis:"x",placeholder:!1}},c(this).select2Sortable(d)),c(this).on("select2:select",function(){redux_change(c(c(this))),c(this).select2SortableOrder()})}))})},redux.field_objects.select.addIcon=function(e){if(e.hasOwnProperty("id"))return'<span class="elusive"><i class="'+e.id+'"></i> '+e.text+"</span>"}}(jQuery);
|
||||
@@ -0,0 +1,16 @@
|
||||
.redux-container-select {
|
||||
li.ui-state-highlight {
|
||||
height: 20px;
|
||||
margin-top: 2px;
|
||||
margin-left: 5px;
|
||||
width: 64px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container--classic {
|
||||
.select2-selection--multiple .select2-selection__choice__remove,
|
||||
.select2-selection--single .select2-selection__clear {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user