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,465 @@
<?php
/**
* CMB2 Tabs.
*
* @package WordPress\Plugins\CMB2 Tabs
* @author Team StackAdroit <stackstudio@stackadroit.com>
* @link https://stackadroit.com
* @version 1.0.6
*
* @copyright 2017 Team StackAdroit
* @license http://creativecommons.org/licenses/GPL/2.0/ GNU General Public License, version 3 or higher
*
* @wordpress-plugin
* Plugin Name: CMB2 Tabs
* Plugin URI: https://github.com/stackadroit/cmb2-extensions
* Description: CMB2 Tabs is an extension for CMB2 which allow you to organize fields into tabs.
* Author: Team StackAdroit <stackstudio@stackadroit.com>
* Author URI: https://stackadroit.com
* Github Plugin URI: https://github.com/stackadroit/cmb2-extensions
* Github Branch: master
* Version: 1.0.6
* License: GPL v3
*
* Copyright (C) 2017, Team StackAdroit - stackstudio@stackadroit.com
*
* GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/3.0/>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
if (!class_exists('CMB2_Tabs', false)) {
/**
* Class CMB2_Tabs
*
* @since 1.0.0
*
* @category WordPress_Plugin
* @package CMB2 Tabs
* @author Team StackAdroit
* @license GPL-3.0+
* @link https://stackadroit.com
*/
class CMB2_Tabs {
/**
* Priority on which our actions are hooked in.
*
* @const int
* @since 1.0.0
*/
const PRIORITY = 99996;
/**
* Current version number
*
* @const string
* @since 1.0.0
*/
const VERSION = '1.0.6';
/**
* The url which is used to load local resources
*
* @var string
* @since 1.0.0
*/
protected static $url = '';
/**
* Current CMB2 instance
*
* @var CMB2
* @since 1.0.0
*/
protected static $cmb = '';
/**
* Indicate that the instance of the class is working on a meta box that has tabs or not
* It will be set 'true' BEFORE meta box is display and 'false' AFTER
*
* @var boolean
* @since 1.0.0
*/
public $active = false;
/**
* Active Panel
*
* @var string
* @since 1.0.0
*/
public $active_panel = '';
/**
* Deactive Conditional tabs "show_on_cb"
*
* @var array
* @since 1.0.0
*/
public $conditional = array();
/**
* Store all output of fields
* This is used to put fields in correct <div> for tabs
*
* @var array
* @since 1.0.0
*/
public $fields_output = array();
/**
* Initialize the hooking into CMB2
*
* @since 1.0.0
*/
public function __construct() {
// Hook all the functions
add_action('cmb2_before_form', array($this, 'opening_div'), 10, 4);
add_action('cmb2_after_form', array($this, 'closing_div'), 20, 4);
add_action('cmb2_before_form', array($this, 'render_nav'), 20, 4);
add_action('cmb2_after_form', array($this, 'show_panels'), 10, 4);
add_filter('cmb2_wrap_classes', array($this, 'panel_wraper_class'), 10, 2);
add_filter('cmb_output_html_row', array($this, 'capture_fields'), 10, 3);
}
/**
* Display opening div for tabs for meta box
*
* @since 1.0.0
*/
public function opening_div($cmb_id, $object_id, $object_type, $cmb)
{
if (!$cmb->prop("tabs")) {
return;
}
$tab_style = $cmb->prop("tab_style");
$class = 'cmb-tabs clearfix';
if (isset($tab_style) && 'default' != $tab_style) {
$class .= ' cmb-tabs-'.$tab_style;
}
echo '<div class="'.$class.'">';
// Current cmb2 instance
CMB2_Tabs::$cmb = $cmb;
// Add cmb2_tabs custome render callback to instance
CMB2_Field::$callable_fields[] = 'cmb2_tabs_render_row_cb';
// Set 'true' to let us know that we're working on a meta box that has tabs
$this->active = true;
//setup style and script for tabs
$this->setup_admin_scripts();
}
/**
* Display closing div for tabs for meta box
*
* @since 1.0.0
*/
public function closing_div()
{
if (!$this->active) {
return;
}
echo '</div>';
// Reset to initial state to be ready for other meta boxes
$this->active = false;
$this->fields_output = array();
}
/**
* Render Navigration
*
* @since 1.0.0
*/
public function render_nav($cmb_id, $object_id, $object_type, $cmb)
{
$tabs = $cmb->prop("tabs");
if ($tabs) {
echo '<ul class="cmb-tab-nav">';
$active_nav = true;
foreach ($tabs as $key => $tab_data)
{
if (is_string($tab_data))
{
$tab_data = array('label' => $tab_data);
}
$tab_data = wp_parse_args($tab_data, array(
'icon' => '',
'label' => '',
'show_on_cb' => null,
));
if ($tab_data['show_on_cb'] && $this->do_callback($tab_data['show_on_cb'])) {
$this->conditional[] = $key;
continue;
}
//set icon defult it it's emty
$tab_data['icon'] = $tab_data['icon'] ? $tab_data['icon'] : "dashicons-admin-post";
// If icon is URL to image
if (filter_var($tab_data['icon'], FILTER_VALIDATE_URL))
{
$icon = '<img src="'.$tab_data['icon'].'">';
}
// If icon is icon font
else
{
// If icon is dashicon, auto add class 'dashicons' for users
if (false !== strpos($tab_data['icon'], 'dashicons'))
{
$tab_data['icon'] .= ' dashicons';
}
// Remove duplicate classes
$tab_data['icon'] = array_filter(array_map('trim', explode(' ', $tab_data['icon'])));
$tab_data['icon'] = implode(' ', array_unique($tab_data['icon']));
$icon = $tab_data['icon'] ? '<i class="'.$tab_data['icon'].'"></i>' : '';
}
$class = "cmb-tab-$key";
if ($active_nav) {
$class .= ' cmb-tab-active';
$this->active_panel = $key;
$active_nav = false;
}
printf(
'<li class="%s" data-panel="%s"><a href="#">%s<span>%s</span></a></li>',
$class,
$key,
$icon,
$tab_data['label']
);
}
echo '</ul>';
}
}
/**
* Add class to wraper div of CMB2 panel
*
* @since 1.0.0
*/
public function panel_wraper_class($classes, $box)
{
if ($this->active) {
$classes[] = 'cmb-tabs-panel';
}
if ($this->active && $this->fields_output) {
$classes[] = 'cmb2-wrap-tabs';
}
return array_unique($classes);
}
/**
* Modified CMB2 render row function to capture rows in a output string
*
* @since 1.0.0
*/
public static function tabs_render_row_cb($field_args, $field)
{
// Ok, callback is good, let's run it and store the result.
ob_start();
if ( 'group' === $field_args['type'] ) {
self::tabs_render_group_row_cb($field_args, $field);
}else{
if ($field->args( 'cmb2_tabs_render_row_cb' )) {
CMB2_Tabs::$cmb->peform_param_callback( 'cmb2_tabs_render_row_cb' );
} else {
$field->render_field_callback();
}
}
// Grab the result from the output buffer and store it.
// Custom fix for show_on
if (isset($returned)) {
$returned = $returned;
} else {
$returned = '';
}
$echoed = ob_get_clean();
$outer_html = $echoed ? $echoed : $returned;
$outer_html = apply_filters('cmb_output_html_row', $outer_html, $field_args, $field);
echo $outer_html;
//return $field;
}
/**
* Modified CMB2 render row function to capture Group rows in a output string
*
* @since 1.0.5
*/
public static function tabs_render_group_row_cb($field_args, $field_group)
{
// Ok, callback is good, let's run it and store the result.
ob_start();
if ($field_group->args( 'cmb2_tabs_render_row_cb' )) {
CMB2_Tabs::$cmb->render_group_callback( 'cmb2_tabs_render_row_cb' );
} else {
CMB2_Tabs::$cmb->render_group_callback($field_args, $field_group);
}
// Grab the result from the output buffer and store it.
$echoed = ob_get_clean();
$outer_html = $echoed ? $echoed : $returned;
$outer_html = apply_filters('cmb_output_html_row', $outer_html, $field_args, $field_group);
echo $outer_html;
//return $field_group;
}
/**
* Display tab navigation for meta box
* Note that: this public function is hooked to 'cmb2_after_form', when all fields are outputted
* (and captured by 'capture_fields' public function)
*
* @since 1.0.0
*/
public function show_panels($cmb_id, $object_id, $object_type, $cmb)
{
if (!$this->active) {return; }
echo '<div class="', esc_attr($cmb->box_classes()), '"><div id="cmb2-metabox-', sanitize_html_class($cmb_id), '" class="cmb2-metabox cmb-field-list">';
foreach ($this->fields_output as $tab => $fields)
{
if (!in_array($tab, $this->conditional, TRUE)) {
$active_panel = $this->active_panel == $tab ? "show" : "";
echo '<div class="'.$active_panel.' cmb-tab-panel cmb-tab-panel-'.$tab.'">';
echo implode('', $fields);
echo '</div>';
}
}
echo '</div></div>';
}
/**
* Save field output into class variable to output later
*
* @since 1.0.0
*/
public function capture_fields($output, $field_args, $field)
{
// If meta box doesn't have tabs, do nothing
if (!$this->active || !isset($field_args['tab'])) { return $output; }
$tab = $field_args['tab'];
if (!isset($this->fields_output[$tab])) {
$this->fields_output[$tab] = array();
}
$this->fields_output[$tab][] = $output;
// Return empty string to let Meta Box plugin echoes nothing
return '';
}
/**
* Enqueue scripts and styles
*
* @since 1.0.0
*/
public function setup_admin_scripts() {
wp_register_script('cmb-tabs-js', self::url('js/tabs.js'), array('jquery'), self::VERSION);
wp_enqueue_script('cmb-tabs-js');
wp_enqueue_style('cmb2-tabs-style', self::url('css/tabs.css'), array(), self::VERSION);
wp_enqueue_style('cmb2-tabs-style');
}
/**
* Defines the url which is used to load local resources. Based on, and uses,
* the CMB2_Utils class from the CMB2 library.
*
* @since 1.0.0
*/
public static function url($path = '') {
if (self::$url) { return self::$url.$path; }
/**
* Set the variable cmb2_tabs_dir
*/
$cmb2_tabs_dir = trailingslashit(dirname(__FILE__));
/**
* Use CMB2_Utils to gather the url from cmb2_tabs_dir
*/
$cmb2_tabs_url = CMB2_Utils::get_url_from_dir($cmb2_tabs_dir);
/**
* Filter the CMB2 FPSA location url
*/
self::$url = trailingslashit(apply_filters('cmb2_tabs_url', $cmb2_tabs_url, self::VERSION));
return self::$url.$path;
}
/**
* Handles metabox property callbacks, and passes this $cmb object as property.
*
* @since 1.0.0
*/
protected function do_callback($cb) {
return call_user_func($cb, CMB2_Tabs::$cmb, $this);
}
}
//Boot the hole thing
$cmb2_tabs = new CMB2_Tabs();
}

View File

@@ -0,0 +1,292 @@
/**
* CMB2 Tabs Styling
*/
/*--------------------------------------------------------------
Helper
--------------------------------------------------------------*/
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.clearfix { display: inline-block; }
/* start commented backslash hack \*/
* html .clearfix { height: 1%; }
.clearfix { display: block; }
/* close commented backslash hack */
/*--------------------------------------------------------------
Base style
--------------------------------------------------------------*/
.cmb-tabs .cmb-th label {
color: #555;
font-size: 12px;
}
.cmb-tabs .cmb-type-group .cmb-row,
.cmb-tabs .cmb2-postbox .cmb-row {
margin: 0 0 0.8em;
padding: 0 0 0.8em;
}
.cmb-tabs span.cmb2-metabox-description {
display: block;
}
.cmb-tabs .cmb-remove-row-button {
background-color: #e60000;
border: medium none;
border-radius: 25px;
color: #fff;
height: 20px;
padding: 0;
text-indent: -999em;
width: 20px;
position: relative;
-webkit-box-shadow: none;
box-shadow: none;
}
/*.cmb-tabs .cmb-remove-row-button:hover {
background-color: #ff0000;
color: #fff;
}
.cmb-tabs .cmb-remove-row-button:after {
box-shadow: none;
content: "";
cursor: help;
font-family: Dashicons;
font-variant: normal;
height: 100%;
width: 100%;
left: 0;
line-height: 1;
margin: 0;
padding: 4px 0;
position: absolute;
text-align: center;
text-indent: 0;
top: 0;
vertical-align: middle;
}*/
.cmb-tabs .cmb-repeat-row{
position: relative;
}
.cmb-tabs .cmb-remove-row {
display: inline;
margin: 0;
padding: 0;
}
.cmb-tabs .cmb-repeat-row .cmb-td{
display: inline-block;
}
/*--------------------------------------------------------------
CMB2 Tabs
--------------------------------------------------------------*/
.cmb-tabs {
margin: -6px -12px -12px;
overflow: hidden;
}
.cmb-tabs ul.cmb-tab-nav:after {
background-color: #fafafa;
border-right: 1px solid #eee;
bottom: -9999em;
content: "";
display: block;
height: 9999em;
left: 0;
position: absolute;
width: calc(100% - 1px);
}
.cmb-tabs ul.cmb-tab-nav{
background-color: #fafafa;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: block;
line-height: 1em;
margin: 0;
padding: 0;
position: relative;
width: 20%;
float: left;
}
.cmb-tabs ul.cmb-tab-nav li {
display: block;
margin: 0;
padding: 0;
position: relative;
}
.cmb-tabs i,
.cmb-tabs i:before {
font-size: 16px;
vertical-align: middle;
}
.cmb-tabs ul.cmb-tab-nav li a {
border-right: 1px solid #eee;
border-left: 2px solid #fafafa;
-webkit-box-shadow: none;
box-shadow: none;
display: block;
line-height: 20px;
margin: 0;
padding: 10px;
text-decoration: none;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-weight: 600;
}
.cmb-tabs ul.cmb-tab-nav li i {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.cmb-tabs ul.cmb-tab-nav li i,
.cmb-tabs ul.cmb-tab-nav li img{
padding: 0 5px 0 0px;
}
.cmb-tabs ul.cmb-tab-nav li a {
color: #555;
border: 1px solid transparent;
}
.cmb-tabs ul.cmb-tab-nav li.cmb-tab-active a {
background-color: #fff;
position: relative;
border: 1px solid #eee;
border-left: 3px solid #00a0d2;
border-right-color: #fff;
}
.cmb-tabs ul.cmb-tab-nav li:first-of-type.cmb-tab-active a {
border-top: none;
}
.cmb-tabs .cmb-tabs-panel {
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #555;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
width: 80%;
padding: 0;
}
.cmb-tabs .cmb2-metabox{
display: block;
width: 100%;
}
.cmb-tabs .cmb-th {
width: 18%;
}
.cmb-tabs .cmb-th,
.cmb-tabs .cmb-td {
padding: 0 2% 0 2%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.cmb-tabs .cmb-th + .cmb-td,
.cmb-tabs .cmb-th + .cmb-td {
float: right;
width: 82%;
}
.cmb2-wrap-tabs .cmb-tab-panel{
display: none;
}
.cmb2-wrap-tabs .cmb-tab-panel.show{
display: block;
}
/*--------------------------------------------------------------
Classic Tab
--------------------------------------------------------------*/
.cmb-tabs.cmb-tabs-classic ul.cmb-tab-nav {
width: 100%;
float: none;
background-color: #fafafa;
border-right: medium none;
padding: 0;
border-bottom: 1px solid #dedede;
padding-top: 15px;
}
.cmb-tabs.cmb-tabs-classic .cmb-tab-nav li {
background: #ebebeb none repeat scroll 0 0;
margin: 0 5px -1px 5px;
display: inline-block;
}
.cmb-tabs.cmb-tabs-classic .cmb-tab-nav li:first-of-type {
margin-left: 18px;
}
.cmb-tabs.cmb-tabs-classic ul.cmb-tab-nav::after {
display: none;
}
.cmb-tabs.cmb-tabs-classic .cmb-tabs-panel {
width: 100%;
}
.cmb-tabs.cmb-tabs-classic .cmb-tab-panel{
/*background: #ebebeb none repeat scroll 0 0;*/
padding-top: 10px;
}
.cmb-tabs.cmb-tabs-classic ul.cmb-tab-nav li a{
padding: 8px 12px;
background-color: #fafafa;
border: none;
border-bottom: 1px solid #dedede;
}
.cmb-tabs.cmb-tabs-classic ul.cmb-tab-nav li.cmb-tab-active a{
background-color: #fff;
border-color: #fff;
border: none;
border-top: 2px solid #00a0d2;
border-bottom: 1px solid #fff;
}
/*--------------------------------------------------------------
Media Query
--------------------------------------------------------------*/
@media (max-width: 750px) {
.cmb-tabs ul.cmb-tab-nav {
width: 10%;
}
.cmb-tabs .cmb-tabs-panel {
width: 90%;
}
.cmb-tabs ul.cmb-tab-nav li i,
.cmb-tabs ul.cmb-tab-nav li img {
padding: 0;
margin: 0 auto;
text-align: center;
display: block;
max-width: 25px;
}
.cmb-tabs ul.cmb-tab-nav li span{
padding: 10px;
position: relative;
text-indent: -999px;
display: none;
}
}
@media (max-width: 500px) {
.cmb-tabs .cmb-th,
.cmb-tabs .cmb-th + .cmb-td,
.cmb-tabs .cmb-th + .cmb-td {
float: none;
width: 96%;
}
.cmb-tabs .cmb-repeat-row .cmb-td {
width: auto;
}
}

View File

@@ -0,0 +1,21 @@
/* global jQuery */
jQuery( function ( $ )
{
'use strict';
$( '.cmb-tab-nav' ).on( 'click', 'a', function ( e )
{
e.preventDefault();
var $li = $( this ).parent(),
panel = $li.data( 'panel' ),
$wrapper = $li.parents( ".cmb-tabs" ).find( '.cmb2-wrap-tabs' ),
$panel = $wrapper.find( '.cmb-tab-panel-' + panel );
$li.addClass( 'cmb-tab-active' ).siblings().removeClass( 'cmb-tab-active' );
$panel.addClass('show').siblings().removeClass('show');
} );
});