🏨 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>
466 lines
14 KiB
PHP
466 lines
14 KiB
PHP
<?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();
|
|
}
|