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:
Hotel Raxa Dev
2025-07-11 07:43:22 +02:00
commit 5b1e2453c7
9816 changed files with 2784509 additions and 0 deletions

View File

@@ -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' );

View File

@@ -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.' );

View File

@@ -0,0 +1,8 @@
<?php
/**
* Silence is golden.
*
* @package Redux Framework
*/
echo null;

View File

@@ -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 */

View File

@@ -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"]}

View File

@@ -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>&nbsp;&nbsp;' + icon.text + '</span>';
}
};
})( jQuery );

View 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>&nbsp;&nbsp;'+e.text+"</span>"}}(jQuery);

View File

@@ -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;
}
}